• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 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 */
15
16import { inputMethod } from '@kit.IMEKit';
17import { VerifyCodeConstants } from '../constants/VerifyCodeConstants';
18import { logger } from '../utils/Logger';
19import { emitter } from '@kit.BasicServicesKit';
20
21@Extend(Text)
22function verifyCodeUnitStyle() {
23  .fontSize($r('sys.float.ohos_id_text_size_body1'))
24  .fontWeight(VerifyCodeConstants.VERIFY_CODE_FONTWEIGHT)
25  .textAlign(TextAlign.Center)
26  .width($r('app.integer.verify_code_code_unit_with'))
27  .height('100%')
28  .margin({
29    left: $r('app.integer.verify_code_code_unit_margin'),
30    right: $r('app.integer.verify_code_code_unit_margin')
31  })
32  .border({
33    width: { bottom: $r('app.integer.verify_code_code_border_width') },
34    color: { bottom: Color.Grey },
35    style: { bottom: BorderStyle.Solid }
36  })
37}
38
39@Component
40struct VerifyCodeComponentWithoutCursor {
41  @State codeText: string = '';
42  private readonly verifyID: string = 'verifyCodeComponent';
43  private inputController: inputMethod.InputMethodController = inputMethod.getController();
44  // 监听键盘弹出收起状态
45  @State isKeyboardShow: boolean = false;
46  private verifyCodeLength: number = 6;
47  private isListen: boolean = false;
48  private textConfig: inputMethod.TextConfig = {
49    inputAttribute: {
50      textInputType: inputMethod.TextInputType.NUMBER,
51      enterKeyType: inputMethod.EnterKeyType.GO
52    },
53  };
54  private codeIndexArray: Array<number> = Array.from([0, 1, 2, 3, 4, 5]);
55  // 注册路由返回函数,案例插件不触发
56  popRouter: () => void = () => {
57  };
58
59  aboutToAppear(): void {
60    // 注册返回监听,包括点击手机返回键返回与侧滑返回
61    this.listenBackPress();
62  }
63
64  async attachAndListen(): Promise<void> {
65    focusControl.requestFocus(this.verifyID);
66    await this.inputController.attach(true, this.textConfig);
67    logger.info('attached');
68    this.listen();
69    this.isKeyboardShow = true;
70  }
71
72  listenBackPress() {
73    let innerEvent: emitter.InnerEvent = {
74      eventId: 5
75    };
76    // 收到eventId为5的事件后执行回调函数
77    emitter.on(innerEvent, () => {
78      if (this.isKeyboardShow) {
79        // 退出文本编辑状态
80        this.inputController.hideTextInput();
81        this.isKeyboardShow = false;
82      } else {
83        this.popRouter();
84      }
85    });
86  }
87
88  aboutToDisappear(): void {
89    this.off();
90    // 关闭事件监听
91    emitter.off(5);
92  }
93
94  /**
95   * TODO 知识点:绑定输入法
96   */
97  async attach() {
98    await this.inputController.attach(true, this.textConfig);
99    logger.info('attached');
100  }
101
102  /**
103   * TODO:知识点:解绑
104   */
105  off(): void {
106    this.inputController.off('insertText');
107    this.inputController.off('deleteLeft');
108    this.isListen = false;
109    logger.info('detached');
110    // 退出文本编辑状态
111    this.inputController.hideTextInput();
112    this.isKeyboardShow = false;
113  }
114
115  /**
116   * TODO 知识点:订阅输入法代插入、向左删除事件,从而获得键盘输入内容
117   */
118  listen() {
119    if (this.isListen) {
120      return;
121    }
122    this.inputController.on('insertText', (text: string) => {
123      if (this.codeText.length >= this.verifyCodeLength || isNaN(Number(text)) || text === ' ') {
124        return;
125      }
126      this.codeText += text;
127      if (this.codeText.length === this.verifyCodeLength) {
128        logger.info('VerifyCode: %{public}s', this.codeText);
129      }
130      logger.info('VerifyCode [insert]: %{public}s', this.codeText);
131    })
132
133    this.inputController.on('deleteLeft', (length: number) => {
134      this.codeText = this.codeText.substring(0, this.codeText.length - 1);
135      logger.info('VerifyCode [delete left]: %{public}s', this.codeText);
136    })
137    this.isListen = true;
138    logger.info('listener added');
139  }
140
141  /**
142   * TODO 知识点:部分验证码场景要完全禁止对输入验证码的选中、复制等功能,因此可以使用Text组件完成
143   */
144  @Builder
145  buildVerifyCodeComponent() {
146    Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
147      ForEach(this.codeIndexArray, (item: number, index: number) => {
148        Text(this.codeText[item])
149          .verifyCodeUnitStyle()
150      }, (item: number, index: number) => item.toString())
151    }
152    .id(this.verifyID)
153    /**
154     * TODO:知识点:当可视面积变化时进行绑定注册与解绑
155     */
156    .onBlur(() => {
157      this.off();
158    })
159    .backgroundColor(Color.Transparent)
160    .height($r('app.integer.verify_code_verify_code_height'))
161    .margin({ left: $r('sys.float.ohos_id_card_margin_start'), right: $r('sys.float.ohos_id_card_margin_start') })
162    .defaultFocus(true)
163    .onClick(() => {
164      // TODO 知识点:点击本组件时弹出输入法,因为这里使用的是Text组件,因此需要重新attach,而不能直接使用showSoftKeyboard
165      this.attachAndListen();
166    })
167  }
168
169  build() {
170    Row() {
171      this.buildVerifyCodeComponent()
172    }
173  }
174}
175
176/**
177 * 验证码组件:禁用选中、复制、光标
178 */
179@Component
180export struct VerifyCodeViewComponent {
181  popRouter: () => void = () => {
182  };
183
184  build() {
185    Column() {
186      VerifyCodeComponentWithoutCursor({ popRouter: this.popRouter })
187    }
188    .height('100%')
189    .width('100%')
190    .justifyContent(FlexAlign.Center)
191  }
192}
193