1/* 2 * Copyright (c) 2022-2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import router from '@ohos.router'; 17import image from '@ohos.multimedia.image'; 18import prompt from '@ohos.promptAction'; 19import common from '@ohos.app.ability.common'; 20import { CameraService } from '../CameraService'; 21import { QRCodeScanConst, ImageAttribute, DecodeResultAttribute } from '../QRCodeScanConst'; 22import { QRCodeParser } from '../QRCodeParser'; 23import Logger from '../../utils/Logger'; 24import { BusinessError } from '@ohos.base'; 25import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl'; 26 27const TAG: string = 'QRCodeScanComponent'; 28let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); 29const PERMISSIONS: Permissions[] = ['ohos.permission.CAMERA', 'ohos.permission.READ_IMAGEVIDEO', 30 'ohos.permission.WRITE_IMAGEVIDEO', 'ohos.permission.MEDIA_LOCATION']; 31 32/** 33 * 二维码扫描组件 34 */ 35@Component 36export default struct QRCodeScanComponent { 37 private cameraService: CameraService = new CameraService(); 38 private qrCodeParser: QRCodeParser = new QRCodeParser(); 39 private xComponentController: XComponentController = new XComponentController(); 40 private context: common.Context = getContext(this); 41 @StorageProp('qrCodeParseResult') @Watch('showQrCodeResult') qrCodeParseResult: string = ''; 42 @State qrCodeImage: image.PixelMap | undefined = undefined; 43 @State animationOrdinate: number = QRCodeScanConst.SCAN_TO_TOP_HEIGHT; 44 @State hasCameraPermission: boolean | undefined = false; 45 @State isQRCodeScanStopped: boolean = false; 46 // 每当应用切换回来都重新打开相机 47 @StorageLink('cameraStatus') @Watch('reCreateCamera') isCameraOpened: boolean = false; 48 @State surFaceId: string = ''; 49 50 // 重新打开相机 51 async reCreateCamera() { 52 await this.cameraService.createCamera(this.surFaceId); 53 } 54 55 showQrCodeResult() { 56 prompt.showDialog({ 57 title: $r('app.string.qrcodeResult'), 58 message: this.qrCodeParseResult 59 }) 60 } 61 62 // 获取权限 63 async requestPermissions() { 64 try { 65 await atManager.requestPermissionsFromUser(this.context, PERMISSIONS) 66 .then((data) => { 67 Logger.info(TAG, `data: ${JSON.stringify(data)}`); 68 // 如果权限列表中有-1,说明用户拒绝了授权 69 if (data.authResults[0] === 0) { 70 // 控制相机是否打开 71 AppStorage.setOrCreate(QRCodeScanConst.HAS_CAMERA_PERMISSION, true); 72 Logger.info(TAG, 'permissionRequestResult success'); 73 } else { 74 Logger.info(TAG, 'permissionRequestResult failed'); 75 } 76 Logger.info(TAG, `context:${JSON.stringify(this.context)}`); 77 AppStorage.setOrCreate('context', this.context); 78 }) 79 .catch((err: BusinessError) => { 80 Logger.error(TAG, `requestPermissionsFromUser err code:${err.code},message:${err.message}`); 81 }) 82 } catch (err) { 83 Logger.info(TAG, `catch err->${JSON.stringify(err)}`); 84 } 85 } 86 87 aboutToAppear() { 88 this.requestPermissions(); 89 // 监听相机权限 90 this.watchCameraPermission(); 91 // 设置扫描动画 92 this.setQRCodeScanAnimation(); 93 // 解析二维码图片信息 94 this.qrCodeParser.parseQRCodeImageFromCamera(this.cameraService); 95 } 96 97 build() { 98 Column() { 99 Stack() { 100 if (this.hasCameraPermission) { 101 XComponent({ 102 id: 'componentId', 103 type: 'surface', 104 controller: this.xComponentController 105 }) 106 .onLoad(() => { 107 // 适配可能需要获取设备信息 108 this.xComponentController.setXComponentSurfaceSize({ 109 surfaceWidth: QRCodeScanConst.IMG_DEFAULT_SIZE_WIDTH, 110 surfaceHeight: QRCodeScanConst.IMG_DEFAULT_SIZE_HEIGHT 111 }) 112 this.surFaceId = this.xComponentController.getXComponentSurfaceId() 113 this.cameraService.createCamera(this.surFaceId) 114 }) 115 .onDestroy(() => { 116 this.cameraService.releaseCamera() 117 }) 118 .height('100%') 119 .width('100%') 120 } 121 Column() { 122 Column() { 123 Image($r('app.media.scan_border')) 124 .width('100%') 125 .height('100%') 126 .margin({ top: QRCodeScanConst.SCAN_TO_TOP_HEIGHT }) 127 .onAreaChange((oldValue: Area, newValue: Area) => { 128 this.animationOrdinate = (newValue.position.y as number) + 10 129 }) 130 131 Divider() 132 .strokeWidth(1) 133 .height(4) 134 .width('100%') 135 .color(Color.White) 136 .width('100%') 137 .position({ x: 0, y: 0 }) 138 .translate({ x: 0, y: this.animationOrdinate }) 139 } 140 .width(280) 141 .height(280) 142 143 Text($r('app.string.putTheQRCodeToScan')) 144 .fontSize(18) 145 .fontColor(Color.White) 146 .margin({ top: 24 }) 147 } 148 .width('100%') 149 .height('100%') 150 .margin({ right: 20, top: 20, left: 20 }) 151 .alignItems(HorizontalAlign.Center) 152 .justifyContent(FlexAlign.Start) 153 154 Row() { 155 Image($r('app.media.scan_back')) 156 .width(30) 157 .height(30) 158 .onClick(() => { 159 router.back() 160 }) 161 162 Row({ space: 16 }) { 163 Image($r('app.media.scan_photo')) 164 .width(30) 165 .height(30) 166 .id('scanPhoto') 167 .onClick(async () => { 168 // 打开相册获取图片 169 this.isQRCodeScanStopped = true 170 let context = AppStorage.Get('context') as common.UIAbilityContext 171 await context.startAbilityForResult({ 172 parameters: { uri: 'singleselect' }, 173 bundleName: 'com.ohos.photos', 174 abilityName: 'com.ohos.photos.MainAbility', 175 }).then(data => { 176 // 获取want数据 177 let want = data['want']; 178 if (want) { 179 // param代表want参数中的paramters 180 let param = want['parameters']; 181 if (param) { 182 // 被选中的图片路径media/image/8 183 let selectedUri = param['select-item-list']; 184 setTimeout(async () => { 185 if (!selectedUri) { 186 prompt.showToast({ 187 message: $r('app.string.queryImageFailed'), 188 duration: 1000 189 }) 190 setInterval(async () => { 191 // 监听相机权限 192 this.watchCameraPermission() 193 // 设置扫描动画 194 this.setQRCodeScanAnimation() 195 // 解析二维码图片信息 196 this.qrCodeParser.parseQRCodeImageFromCamera(this.cameraService); 197 }, 4000) 198 } 199 // 获取解析数据 200 let qrCodeParseRlt: DecodeResultAttribute = await this.qrCodeParser.parseImageQRCode((selectedUri as string[])[0]); 201 if (qrCodeParseRlt.isSucess) { 202 prompt.showDialog({ 203 title: $r('app.string.qrcodeResult'), 204 message: qrCodeParseRlt.decodeResult 205 }) 206 } else { 207 prompt.showToast({ 208 message: $r('app.string.qrCodeNotRecognized') 209 }) 210 } 211 }, 50) 212 } 213 } 214 }) 215 }) 216 217 Image($r('app.media.scan_more')) 218 .width(30) 219 .height(30) 220 .onClick(() => { 221 prompt.showToast({ 222 message: $r('app.string.notSupportCurrent'), 223 duration: 1000 224 }) 225 }) 226 } 227 } 228 .width('100%') 229 .height('100%') 230 .margin({ top: 24 }) 231 .padding({ left: 24, right: 24 }) 232 .alignItems(VerticalAlign.Top) 233 .justifyContent(FlexAlign.SpaceBetween) 234 235 Column() { 236 Image($r('app.media.scan_light')) 237 .width(48) 238 .height(48) 239 240 Text($r('app.string.lightByTouch')) 241 .fontSize(20) 242 .fontColor(Color.White) 243 .margin({ top: 4 }) 244 245 // 扫一扫 文本翻译 字体设置 246 NotSupportComponent() 247 }.margin({ top: 350 }) 248 } 249 .width('100%') 250 .height('100%') 251 .layoutWeight(1) 252 .backgroundColor(Color.Grey) 253 } 254 .height('100%') 255 .width('100%') 256 .backgroundColor(Color.White) 257 } 258 259 // 监听相机权限变化 260 watchCameraPermission() { 261 let interval = setInterval(() => { 262 this.hasCameraPermission = AppStorage.Get(QRCodeScanConst.HAS_CAMERA_PERMISSION) 263 if (this.hasCameraPermission) { 264 let qrCodeScanInterval = setInterval(() => { 265 if (this.qrCodeParseResult.length > 0 || this.isQRCodeScanStopped) { 266 clearInterval(qrCodeScanInterval) 267 } 268 // 拍照 269 this.cameraService.takePicture() 270 }, 4000) 271 clearInterval(interval) 272 } 273 }, 100) 274 } 275 276 // 扫描扫描动画 277 setQRCodeScanAnimation() { 278 setInterval(() => { 279 animateTo({ 280 duration: 1000, // 动画时间 281 tempo: 0.5, // 动画速率 282 curve: Curve.EaseInOut, 283 delay: 200, // 动画延迟时间 284 iterations: -1, // 动画是否重复播放 285 playMode: PlayMode.Normal, 286 }, () => { 287 this.animationOrdinate = 390 // 扫描动画结束Y坐标 288 }) 289 }, 2000) 290 } 291} 292 293@Component 294struct NotSupportComponent { 295 build() { 296 Row({ space: 32 }) { 297 Image($r('app.media.scan_trans')) 298 .width(30) 299 .height(30) 300 .onClick(() => { 301 prompt.showToast({ message: $r('app.string.notSupportCurrent'), duration: 1000 }) 302 }) 303 Image($r('app.media.scan_size')) 304 .width(30) 305 .height(30) 306 .onClick(() => { 307 prompt.showToast({ message: $r('app.string.notSupportCurrent'), duration: 1000 }) 308 }) 309 } 310 .margin({ top: 30, left: 20 }) 311 } 312}