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