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