1/* 2 * Copyright (c) 2025 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 { uiObserver, UIObserver } from '@kit.ArkUI'; 17import CustomScanViewModel, { ScanResults } from '../viewmodel/CustomScanViewModel'; 18import CustomScanCameraComp from '../components/CustomScanCameraComp'; 19import CustomScanCtrlComp from '../components/CustomScanCtrlComp'; 20import CommonConstants from '../common/constants/CommonConstants'; 21import { CommonTipsDialog } from '../components/CommonTipsDialog'; 22import { DetectBarcodePage } from './DetectBarcodePage'; 23 24/** 25 * 二维码扫描页面 26 * 实现步骤: 27 * 1.用户授权相机后初始化页面内子组件 28 * 2.进二维码扫描路由时监控折叠屏状态变化,实时重新初始化扫码服务和相机流尺寸 29 */ 30@Component 31export struct CustomScanPage { 32 // 应用前后台标记 33 @StorageProp('isBackground') @Watch('onBackgroundUpdate') isBackground: boolean = false; 34 // 自页面栈 35 @Provide('subPageStack') subPageStack: NavPathStack = new NavPathStack(); 36 // 自定义扫码vm实例 37 @Provide('customScanVM') customScanVM: CustomScanViewModel = CustomScanViewModel.getInstance(); 38 // 授权标志 39 @State scanResult: ScanResults = this.customScanVM.scanResult 40 @State isGranted: boolean = false; 41 // 提示授权弹框 42 @State isDialogShow: boolean = false; 43 44 dialogController: CustomDialogController = new CustomDialogController({ 45 builder: CommonTipsDialog({ 46 isDialogShow: this.isDialogShow 47 }), 48 autoCancel: false, 49 customStyle: false, 50 alignment: DialogAlignment.Center 51 }); 52 listener: (info: uiObserver.RouterPageInfo) => void = (info: uiObserver.RouterPageInfo) => { 53 let routerInfo: uiObserver.RouterPageInfo | undefined = this.queryRouterPageInfo(); 54 if (info.pageId == routerInfo?.pageId) { 55 if (info.state == uiObserver.RouterPageState.ON_PAGE_SHOW) { 56 this.onComponentShow(); 57 } else if (info.state == uiObserver.RouterPageState.ON_PAGE_HIDE) { 58 this.onComponentHide(); 59 } 60 } 61 } 62 63 @Builder 64 pageMap(name: string) { 65 if (name === CommonConstants.SUB_PAGE_DETECT_BARCODE) { 66 NavDestination() { 67 DetectBarcodePage() 68 } 69 .hideTitleBar(true) 70 } 71 } 72 73 async aboutToAppear() { 74 // 注册routerPage更新监听 75 let uiObserver: UIObserver = this.getUIContext().getUIObserver(); 76 uiObserver.on('routerPageUpdate', this.listener); 77 78 this.onComponentShow(); 79 } 80 81 /** 82 * 组件出现时执行,包括前后台切换 83 */ 84 async onComponentShow() { 85 // TODO:知识点:检测应用是否已被用户允许使用相机权限,未授权向申请授权 86 const isGranted = await this.customScanVM.reqCameraPermission(); 87 if (!isGranted && !this.isDialogShow) { 88 // 用户未授权,给出提示 89 this.dialogController.open(); 90 this.isDialogShow = true; 91 } 92 this.isGranted = isGranted; 93 } 94 95 aboutToDisappear() { 96 // 取消监听 97 let uiObserver: UIObserver = this.getUIContext().getUIObserver(); 98 uiObserver.off('routerPageUpdate', this.listener); 99 } 100 101 /** 102 * 应用前后台切换回调,进后台时需要释放扫码资源,进前台时需要重启扫码功能 103 * @returns {void} 104 */ 105 onBackgroundUpdate(): void { 106 if (this.subPageStack.size() > 0) { 107 return; 108 } 109 110 if (this.isBackground) { 111 this.customScanVM.stopCustomScan(); 112 } else { 113 this.customScanVM.restartCustomScan(); 114 } 115 } 116 117 /** 118 * 组件隐藏时执行 119 */ 120 onComponentHide() { 121 this.isDialogShow = false; 122 this.dialogController.close(); 123 } 124 125 initScan(): void { 126 this.customScanVM.initCustomScan(); 127 } 128 129 build() { 130 Navigation(this.subPageStack) { 131 Stack() { 132 if (this.isGranted) { 133 CustomScanCameraComp() 134 } 135 CustomScanCtrlComp() 136 } 137 .alignContent(Alignment.Center) 138 .backgroundColor(Color.Black) 139 .expandSafeArea([SafeAreaType.SYSTEM],[SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) 140 } 141 .navDestination(this.pageMap) 142 .mode(NavigationMode.Stack) 143 .hideTitleBar(true) 144 } 145}