• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}