1/*
2 * Copyright (c) 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'
17
18let dmClass: deviceManager.DeviceManager | null;
19let TAG = '[DeviceManagerUI:InputPinDialog]==>';
20const ACTION_CANCEL_PINCODE_INPUT: number = 4;
21const ACTION_DONE_PINCODE_INPUT: number = 5;
22const MSG_PIN_CODE_ERROR: number = 0;
23const MSG_CANCEL_PIN_CODE_INPUT: number = 3;
24const MSG_DOING_AUTH: number = 4;
25const BUTTON_BG_COLOR = 'rgba(255, 255, 255, 0.15)';
26const TOAST_DURATION_TIME_MS = 1500;
27const MAX_PINCODE_LENGTH = 6;
28
29@Entry
30@Component
31struct Index {
32  @State isTimes: number = 3;
33  @State passwordCircle: string[] = ['', '', '', '', '', ''];
34  @State errorTips: Resource = $r('app.plural.dm_incorrect_code', this.isTimes, this.isTimes);
35  @State @Watch('onChangeInput') input: string = '';
36  private keyboards: string[][] = [
37    ['1', '2', '3'],
38    ['4', '5', '6'],
39    ['7', '8', '9'],
40    ['', '0', 'x'],
41  ];
42  @State isUserInput: boolean = false;
43  @State isUserOperate: boolean = false;
44
45  dialogController: CustomDialogController = new CustomDialogController({
46    builder: errTips({attemptTimes: this.isTimes, errTips: this.errorTips}),
47    autoCancel: false,
48    alignment: DialogAlignment.Center,
49    customStyle: true,
50    maskColor: $r('sys.color.ohos_id_color_mask_thin'),
51  });
52
53  aboutToAppear() {
54    if (dmClass) {
55      console.log(TAG + 'deviceManager exist');
56      return;
57    }
58    deviceManager.createDeviceManager('com.ohos.devicemanagerui.input',
59      (err: Error, dm: deviceManager.DeviceManager) => {
60      if (err) {
61        console.log('createDeviceManager err:' + JSON.stringify(err) + '  --fail:' + '${dm}');
62        return;
63      }
64      dmClass = dm;
65      dmClass.on('uiStateChange', (data: Record<string, string>) => {
66        console.log('uiStateChange executed, dialog closed' + JSON.stringify(data));
67        let tmpStr: Record<string, number> = JSON.parse(data.param);
68        let msg: number = tmpStr.uiStateMsg as number;
69        if (msg === MSG_DOING_AUTH) {
70          this.errorTips = $r('app.string.dm_authenticating');
71          return;
72        }
73        if (msg === MSG_CANCEL_PIN_CODE_INPUT) {
74          this.destruction();
75          return;
76        }
77        if (msg === MSG_PIN_CODE_ERROR) {
78          this.isTimes--;
79          this.errorTips = $r('app.plural.dm_incorrect_code', this.isTimes, this.isTimes);
80          this.input = '';
81          this.passwordCircle = ['', '', '', '', '', ''];
82          this.showToast();
83        }
84      });
85    });
86  }
87
88  showToast() {
89    if (this.dialogController) {
90      this.dialogController.open();
91      setTimeout(() => {
92        this.dialogController.close();
93      }, TOAST_DURATION_TIME_MS);
94    }
95  }
96
97  onPageHide() {
98    console.log('onPageHide');
99    if (this.isUserOperate) {
100      console.log('user operate');
101      return;
102    }
103    this.cancel();
104  }
105
106  destruction() {
107    console.log(TAG + 'destruction begin');
108    if (dmClass != null) {
109      try {
110        dmClass.off('uiStateChange');
111        dmClass.release();
112      } catch (error) {
113        console.log('dmClass release failed');
114      }
115      dmClass = null;
116    }
117    let session = AppStorage.get<UIExtensionContentSession>('inputSession');
118    if (session) {
119      session.terminateSelf();
120    }
121  }
122
123  setUserOperation(operation: number, extra: string) {
124    console.log('setUserOperation: ' + operation + 'input' + extra);
125    if (dmClass === null) {
126      console.log('setUserOperation: ' + 'dmClass null');
127      return;
128    }
129    try {
130      this.isUserOperate = true;
131      dmClass.setUserOperation(operation, extra);
132    } catch (error) {
133      console.log('dmClass setUserOperation failed');
134    }
135  }
136
137  cancel() {
138    console.log('cancle');
139    if (dmClass) {
140      console.log('deviceManager exist');
141    } else {
142      console.log('createDeviceManager is null');
143      return;
144    }
145    console.log('cancle' + ACTION_CANCEL_PINCODE_INPUT);
146    this.setUserOperation(ACTION_CANCEL_PINCODE_INPUT, 'extra');
147    this.destruction();
148  }
149
150  confirm() {
151    console.log('confirm');
152    if (this.input === null || this.input === '') {
153      return;
154    }
155    if (dmClass) {
156      console.log('deviceManager exist');
157    } else {
158      console.log('createDeviceManager is null');
159      return;
160    }
161    console.log('confirm' + JSON.stringify(ACTION_DONE_PINCODE_INPUT));
162    this.setUserOperation(ACTION_DONE_PINCODE_INPUT, this.input);
163  }
164
165  build() {
166    Column() {
167      this.titleBuilder();
168      this.keyboardMocker();
169    }
170    .backgroundColor(Color.Black)
171    .height('100%')
172    .width('100%')
173  }
174
175  @Builder
176  keyBuilder(key: string) {
177    GridItem() {
178      Button(key)
179        .backgroundColor(BUTTON_BG_COLOR)
180        .fontColor('#FFFFFF')
181        .fontSize($r('sys.float.ohos_id_text_size_headline6'))
182        .onClick(() => {
183          if (this.input.length >= MAX_PINCODE_LENGTH) {
184            return;
185          }
186          this.input += key.toString();
187        })
188        .size({ width: 60, height: 48 })
189        .margin({ left: 3, top: 2 })
190        .visibility(key === '' ? Visibility.None : Visibility.Visible)
191    }
192  }
193
194  @Builder
195  processKey(key: string) {
196    if (key === 'x') {
197      this.SymbolDelete();
198    } else {
199      this.keyBuilder(key);
200    }
201  }
202
203  @Builder
204  keyboardMocker() {
205    Grid() {
206      ForEach(this.keyboards, (keysArr: string[]) => {
207        ForEach(keysArr, (key: string) => {
208          this.processKey(key);
209        })
210      })
211    }
212    .width(189)
213    .height(200)
214    .margin({ top: 2 })
215  }
216
217  @Builder
218  SymbolDelete() {
219    SymbolGlyph($r('sys.symbol.delete_left_fill'))
220      .fontSize('28vp')
221      .renderingStrategy(SymbolRenderingStrategy.MULTIPLE_OPACITY)
222      .fontColor(['#FFFFFF'])
223      .margin({ left: 10, top: 5 })
224      .onClick(() => {
225        this.input = this.input.slice(0, -1);
226      })
227      .visibility(this.isUserInput ? Visibility.Visible : Visibility.None)
228  }
229
230  @Builder
231  PinCode() {
232    Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
233      ForEach(this.passwordCircle, (item:string) => {
234        Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
235          Text(item)
236            .fontSize($r('sys.float.ohos_id_text_size_sub_title2'))
237            .fontColor('#FFFFFF')
238            .fontWeight(FontWeight.Medium)
239        }.width('7%')
240        .visibility(item === '' ? Visibility.None : Visibility.Visible)
241      })
242      ForEach(this.passwordCircle, (item: string) => {
243        Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
244          Column()
245            .width(9)
246            .height(9)
247            .border({ width: 1, color: '#FFFFFF', radius: 9})
248            .alignItems(HorizontalAlign.Center)
249        }.width('7%')
250        .visibility(item === '' ? Visibility.Visible : Visibility.None)
251      })
252    }
253  }
254
255  @Builder
256  titleBuilder() {
257    Text($r('app.string.dm_enter_peer_connect_code'))
258      .fontColor('#FFFFFF')
259      .fontSize($r('sys.float.ohos_id_text_size_body2'))
260      .margin({ top: 10 })
261      .textOverflow({ overflow: TextOverflow.MARQUEE })
262      .visibility(this.isUserInput ? Visibility.None : Visibility.Visible)
263      .height(19)
264    Stack() {
265      List() {
266        ListItem() {
267          this.PinCode();
268        }
269      }
270    }
271    .visibility(this.isUserInput ? Visibility.Visible : Visibility.None)
272    .margin({ top: 9 })
273    .height(20)
274  }
275
276  onChangeInput(changedPropertyName: string) {
277    let length = this.input.length;
278    for (let i = 0; i < MAX_PINCODE_LENGTH; ++i) {
279      if (i < length) {
280        this.passwordCircle[i] = this.input[i];
281      } else {
282        this.passwordCircle[i] = '';
283      }
284    }
285    if (length === MAX_PINCODE_LENGTH) {
286      setTimeout(() => {
287        this.setUserOperation(ACTION_DONE_PINCODE_INPUT, this.input);
288      }, 50);
289    }
290    this.isUserInput = true;
291  }
292}
293
294@CustomDialog
295struct errTips {
296  controller?: CustomDialogController;
297  @Link attemptTimes: number;
298  @Link errTips: Resource;
299  build() {
300    Column() {
301      Text(this.errTips)
302        .fontSize(15)
303        .fontColor('#FFFFFF')
304        .textAlign(TextAlign.Center)
305        .backgroundColor('#F2404040')
306    }
307    .justifyContent(FlexAlign.Center)
308    .width(205)
309    .padding({left: 12, right: 12, top: 10, bottom: 10 })
310    .backgroundColor('#F2404040')
311    .borderRadius(19)
312  }
313}