1/* 2 * Copyright (c) 2022-2024 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 */ 15import deviceManager from '@ohos.distributedHardware.deviceManager'; 16import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession' 17import mediaquery from '@ohos.mediaquery'; 18import deviceInfo from '@ohos.deviceInfo'; 19import inputMethod from '@ohos.inputMethod'; 20import Constant from '../common/constant'; 21import accessibility from '@ohos.accessibility'; 22import common from '@ohos.app.ability.common'; 23import i18n from '@ohos.i18n'; 24 25let dmClass: deviceManager.DeviceManager | null; 26let TAG = '[DeviceManagerUI:InputPinDialog]==>' 27const ACTION_CANCEL_PINCODE_INPUT: number = 4 28const ACTION_DONE_PINCODE_INPUT: number = 5 29const MSG_PIN_CODE_ERROR: number = 0 30const MSG_CANCEL_PIN_CODE_INPUT: number = 3 31const MSG_DOING_AUTH: number = 4 32const MODEL_PIN: string = 'pin'; 33const MODEL_PASSWORD: string = 'password'; 34 35@CustomDialog 36struct InputCustomDialog { 37 @State password: string = ''; 38 @State passwordCircle: string[] = ['', '', '', '', '', '']; 39 @State isTimes: number = 3; 40 @State errorTips: Resource = $r('app.plural.dm_incorrect_code', this.isTimes, this.isTimes); 41 @State errorTipsVisible: Visibility = Visibility.None; 42 @State heightNum: number = 600; 43 @State targetDeviceName: string = ''; 44 @State model: string = MODEL_PIN; 45 @State isPC: boolean = false; 46 @State btnColor: ResourceColor = Color.Transparent; 47 listener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(orientation: landscape)'); 48 controller?: CustomDialogController; 49 private scroller: Scroller = new Scroller(); 50 51 onPortrait(mediaQueryResult: mediaquery.MediaQueryResult) { 52 if (mediaQueryResult.matches as boolean) { 53 this.heightNum = 100; 54 } else { 55 this.heightNum = 800; 56 } 57 } 58 59 aboutToDisappear() { 60 console.info(TAG + 'InputCustomDialog aboutToDisappear'); 61 let ims = inputMethod.getSetting(); 62 ims.off('imeShow'); 63 } 64 65 aboutToAppear() { 66 console.info(TAG + 'InputCustomDialog aboutToAppear'); 67 this.isPC = Constant.isPC(); 68 let ims = inputMethod.getSetting(); 69 ims.on('imeShow', (info: Array<inputMethod.InputWindowInfo>) => { 70 this.scroller.scrollTo({yOffset: 72, xOffset: 0}); 71 }); 72 if (AppStorage.get('mediaQueryResult') != null && AppStorage.get('mediaQueryResult') as boolean) { 73 this.heightNum = 100; 74 } 75 if (AppStorage.get('targetDeviceName') != null) { 76 this.targetDeviceName = AppStorage.get('targetDeviceName') as string; 77 console.log('targetDeviceName is ' + this.targetDeviceName); 78 } 79 if (AppStorage.get('model') != null) { 80 this.model = AppStorage.get('model') as string; 81 console.log('model is ' + this.model); 82 } 83 deviceManager.createDeviceManager('com.ohos.devicemanagerui.input', 84 (err: Error, dm: deviceManager.DeviceManager) => { 85 if (err) { 86 console.log('createDeviceManager err:' + JSON.stringify(err) + ' --fail:' + '${dm}'); 87 return; 88 } 89 dmClass = dm; 90 dmClass.on('uiStateChange', (data: Record<string, string>) => { 91 console.log('uiStateChange executed, dialog closed' + JSON.stringify(data)); 92 let tmpStr: Record<string, number> = JSON.parse(data.param); 93 let msg: number = tmpStr.uiStateMsg as number; 94 if (msg === MSG_DOING_AUTH) { 95 this.errorTips = $r('app.string.dm_authenticating'); 96 this.errorTipsVisible = Visibility.Visible; 97 return; 98 } 99 if (msg === MSG_CANCEL_PIN_CODE_INPUT) { 100 this.destruction(); 101 return; 102 } 103 if (msg === MSG_PIN_CODE_ERROR) { 104 this.inputCodeError(); 105 } 106 }) 107 }); 108 this.listener.on('change', (mediaQueryResult: mediaquery.MediaQueryResult) => { 109 this.onPortrait(mediaQueryResult); 110 }); 111 } 112 113 sendAccessibilityEvent(times: number) { 114 console.log(TAG + 'sendAccessibilityEvent in'); 115 let context = getContext(this) as common.UIAbilityContext; 116 let str = context.resourceManager.getPluralStringValueSync($r('app.plural.dm_incorrect_code').id, times) 117 let eventInfo: accessibility.EventInfo = ({ 118 type: 'announceForAccessibility', 119 bundleName: 'com.ohos.devicemanagerui', 120 triggerAction: 'common', 121 textAnnouncedForAccessibility: str 122 }) 123 124 try { 125 accessibility.sendAccessibilityEvent(eventInfo).then(()=>{ 126 console.info(`${TAG} Succeeded in send event, eventInfo is ${JSON.stringify(eventInfo)}`); 127 }); 128 } catch (error) { 129 console.info(`${TAG} Failed in send event, error.message is ${error.message}`); 130 } 131 } 132 133 inputCodeError() { 134 console.log(TAG + 'inputCodeError in'); 135 if (this.model == MODEL_PASSWORD) { 136 this.errorTips = $r('app.string.dm_password_error'); 137 } else { 138 this.isTimes--; 139 this.errorTips = $r('app.plural.dm_incorrect_code', this.isTimes, this.isTimes); 140 this.sendAccessibilityEvent(this.isTimes); 141 } 142 this.password = ''; 143 this.errorTipsVisible = Visibility.Visible; 144 this.passwordCircle = ['', '', '', '', '', '']; 145 } 146 147 cancel() { 148 console.log('cancle'); 149 if (dmClass) { 150 console.log('deviceManager exist'); 151 } else { 152 console.log('createDeviceManager is null'); 153 return; 154 } 155 console.log('cancle' + ACTION_CANCEL_PINCODE_INPUT); 156 this.setUserOperation(ACTION_CANCEL_PINCODE_INPUT, 'extra'); 157 this.destruction(); 158 } 159 160 confirm() { 161 console.log('confirm'); 162 if (this.password == null || this.password == '') { 163 return; 164 } 165 if (dmClass) { 166 console.log('deviceManager exist'); 167 } else { 168 console.log('createDeviceManager is null'); 169 return; 170 } 171 console.log('confirm' + JSON.stringify(ACTION_DONE_PINCODE_INPUT)); 172 this.setUserOperation(ACTION_DONE_PINCODE_INPUT, this.password); 173 } 174 175 setUserOperation(operation: number, extra: string) { 176 console.log('setUserOperation: ' + operation + 'password' + extra); 177 if (dmClass == null) { 178 console.log('setUserOperation: ' + 'dmClass null'); 179 return; 180 } 181 try { 182 dmClass.setUserOperation(operation, extra); 183 } catch (error) { 184 console.log('dmClass setUserOperation failed'); 185 } 186 } 187 188 destruction() { 189 console.info(TAG + 'destruction'); 190 let inputMethodController = inputMethod.getController(); 191 inputMethodController.hideTextInput(); 192 let session = AppStorage.get<UIExtensionContentSession>('inputSession'); 193 if (session) { 194 console.info(TAG + 'terminateSelf'); 195 session.terminateSelf(); 196 } 197 } 198 199 isNumberSix(str: string): boolean { 200 console.info(TAG + 'isNumber6 in'); 201 const reg: RegExp = new RegExp('^[0-9]{6}$'); 202 return reg.test(str); 203 } 204 205 passwordOnChange(value: string) { 206 console.info(TAG + 'passwordOnChange in'); 207 if (this.isNumberSix(value)) { 208 this.confirm(); 209 } 210 } 211 212 private isTibetanLanguages(): boolean { 213 console.info(`${TAG} isTibetanLanguages in`); 214 let locale = new Intl.Locale(i18n.System.getSystemLanguage()).toString(); 215 console.info(`${TAG} isTibetanLanguages: ${locale}`); 216 return Constant.TIBETAN_LANGUAGES.includes(locale); 217 } 218 219 build() { 220 GridRow({ 221 columns: { xs: 4, sm: 8, md: this.isPC ? 24 : 12 }, 222 gutter: { x: 4 }, 223 breakpoints: { value: ['600vp', '840vp'] } 224 }) { 225 GridCol({ span: { xs: 4, sm: 4, md: this.isPC ? 6 : 4 }, offset: { sm: 2, md: this.isPC ? 9 : 4 } }) { 226 Scroll(this.scroller) { 227 Column() { 228 Column() { 229 Text($r('app.string.dm_connect', this.targetDeviceName)) 230 .fontSize($r('sys.float.ohos_id_text_size_dialog_tittle')) 231 .fontWeight(FontWeight.Bold) 232 .fontColor($r('sys.color.ohos_id_color_text_primary')) 233 .margin({ top: 12, bottom: 3 }) 234 .width('auto') 235 .textAlign(TextAlign.Center) 236 .maxLines(2) 237 .textOverflow({ overflow: TextOverflow.Ellipsis }) 238 .minFontSize(12) 239 .maxFontSize($r('sys.float.ohos_id_text_size_dialog_tittle')) 240 .heightAdaptivePolicy(TextHeightAdaptivePolicy.LAYOUT_CONSTRAINT_FIRST) 241 .lineHeight(this.isTibetanLanguages() ? 31 : 0) 242 243 Text($r('app.string.dm_enter_connect_code')) 244 .fontSize($r('sys.float.ohos_id_text_size_body2')) 245 .fontWeight(FontWeight.Regular) 246 .fontColor($r('sys.color.ohos_id_color_text_secondary')) 247 .margin({ bottom: 8 }) 248 .width('auto') 249 .maxLines(2) 250 .textAlign(TextAlign.Center) 251 .textOverflow({ overflow: TextOverflow.Ellipsis }) 252 .minFontSize(12) 253 .maxFontSize($r('sys.float.ohos_id_text_size_body2')) 254 .heightAdaptivePolicy(TextHeightAdaptivePolicy.LAYOUT_CONSTRAINT_FIRST) 255 .lineHeight(this.isTibetanLanguages() ? 22 : 0) 256 } 257 .margin({ left: 24, right: 24 }) 258 .constraintSize({ minHeight: 72 }) 259 .justifyContent(FlexAlign.Center) 260 261 Stack() { 262 List() { 263 ListItem() { 264 Flex({ justifyContent: FlexAlign.Center }) { 265 ForEach(this.passwordCircle, (item:string) => { 266 Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { 267 Text(item) 268 .fontSize($r('sys.float.ohos_id_text_size_headline7')) 269 .fontColor($r('sys.color.ohos_id_color_text_primary')) 270 .fontWeight(FontWeight.Medium) 271 }.width('10%') 272 .height('100%') 273 .visibility(item === '' ? Visibility.None : Visibility.Visible) 274 }) 275 ForEach(this.passwordCircle, (item: string) => { 276 Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { 277 Column() 278 .width(12) 279 .height(12) 280 .border({ width: 2, color: $r('sys.color.ohos_id_color_primary'), radius: 12}) 281 }.width('10%') 282 .height('100%') 283 .visibility(item === '' ? Visibility.Visible : Visibility.None) 284 }) 285 } 286 } 287 } 288 TextInput({ placeholder: '', text: this.password}) 289 .defaultFocus(true) 290 .onAppear(() => { 291 focusControl.requestFocus('inputpin') 292 }) 293 .id('inputpin') 294 .type(8) 295 .height(60) 296 .opacity(0) 297 .fontColor(('rgba(0,0,0,0)')) 298 .backgroundColor(('rgba(0,0,0,0)')) 299 .caretColor(('rgba(0,0,0,0)')) 300 .maxLength(6) 301 .margin({ bottom: 8 }) 302 .width('100%') 303 .onChange((value: string) => { 304 this.password = value; 305 if (value.length > 6) { 306 return; 307 } 308 let length = value.length; 309 for (let i = 0; i < 6; i++) { 310 if (i < length) { 311 this.passwordCircle[i] = value[i]; 312 } else { 313 this.passwordCircle[i] = ''; 314 } 315 } 316 let gThis = this; 317 setTimeout(()=> { 318 gThis.passwordOnChange(value); 319 }, 50) 320 }) 321 }.height(48) 322 .margin({ top: 12, bottom: 16}) 323 324 Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { 325 Text(this.errorTips) 326 .fontSize($r('sys.float.ohos_id_text_size_body2')) 327 .fontWeight(FontWeight.Medium) 328 .fontColor($r('sys.color.ohos_id_color_warning')) 329 .lineHeight(this.isTibetanLanguages() ? 22 : 0) 330 }.visibility(this.errorTipsVisible) 331 .margin({ bottom: 16, 332 left: $r('sys.float.ohos_id_corner_radius_dialog'), 333 right: $r('sys.float.ohos_id_corner_radius_dialog') }) 334 335 Flex({ justifyContent: FlexAlign.Center }) { 336 Button($r('app.string.dm_cancel')) 337 .constraintSize({ minHeight: 40 }) 338 .fontSize($r('sys.float.ohos_id_text_size_button1')) 339 .onClick(() => { 340 if (this.controller) { 341 this.controller.close(); 342 } 343 this.cancel(); 344 }) 345 .width('100%') 346 .backgroundColor(this.btnColor) 347 .fontColor($r('sys.color.ohos_id_color_text_primary_activated')) 348 .onHover((isHover?: boolean, event?: HoverEvent): void => { 349 if (isHover) { 350 this.btnColor = $r('sys.color.ohos_id_color_hover'); 351 } else { 352 this.btnColor = this.isPC ? $r('sys.color.ohos_id_color_button_normal') : Color.Transparent; 353 } 354 }) 355 .stateStyles({ 356 pressed: { 357 .backgroundColor($r('sys.color.ohos_id_color_click_effect')) 358 }, 359 normal: { 360 .backgroundColor(this.isPC ? $r('sys.color.ohos_id_color_button_normal') : Color.Transparent) 361 } 362 }) 363 }.margin({ 364 left: 16, 365 right: 16, 366 bottom: this.isPC ? 24 : 16 }) 367 } 368 } 369 .scrollable(ScrollDirection.Vertical) 370 .scrollBar(BarState.On) 371 .constraintSize({ maxHeight: `${this.heightNum}`}) 372 .borderRadius($r('sys.float.ohos_id_corner_radius_dialog')) 373 .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK) 374 .margin({ 375 left: $r('sys.float.ohos_id_dialog_margin_bottom'), 376 right: $r('sys.float.ohos_id_dialog_margin_bottom') 377 }) 378 } 379 }.margin({top: 8, bottom: 20}) 380 } 381} 382 383@Entry 384@Component 385struct dialogPlusPage { 386 mediaQueryListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(orientation: landscape)'); 387 dialogController: CustomDialogController = new CustomDialogController({ 388 builder: InputCustomDialog(), 389 autoCancel: false, 390 alignment: DialogAlignment.Center, 391 offset: { dx: 0, dy: 0 }, 392 customStyle: true, 393 maskColor: $r('sys.color.ohos_id_color_mask_thin') 394 }); 395 396 aboutToAppear() { 397 console.log(TAG + 'aboutToAppear aboutToAppear'); 398 this.mediaQueryListener.on('change', this.onPortrait.bind(this)); 399 } 400 401 onPortrait(mediaQueryResult: mediaquery.MediaQueryResult) { 402 AppStorage.setOrCreate('mediaQueryResult', mediaQueryResult.matches as boolean); 403 } 404 405 aboutToDisappear() { 406 console.log(TAG + 'aboutToDisappear aboutToDisappear') 407 if (dmClass != null) { 408 try { 409 dmClass.off('uiStateChange'); 410 dmClass.release(); 411 } catch (error) { 412 console.log('dmClass release failed'); 413 } 414 dmClass = null 415 } 416 } 417 418 build() { 419 Column(this.dialogController.open()) 420 } 421}