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