1/*
2 * Copyright (c) 2023 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 { BusinessError } from '@ohos.base';
17import { staffItem } from './staff';
18import Constants from '../constant';
19import { AccountTipsConfig } from '../AccountTipsConfig';
20import { HiLog } from '../HiLog';
21import CommonUtil from '../CommonUtil';
22import AccountManager from '../../manager/AccountManager';
23import DomainAccountResponse from '../../bean/response/DomainAccountResponse';
24import AppStorageConstant from '../constant/AppStorageConstant';
25import { common } from '@kit.AbilityKit';
26import DomainAccountInfo from '../../bean/data/DomainAccountInfo';
27
28interface Staff {
29  authAccount: string;
30  textContent: string;
31}
32
33const TAG = 'AddStaff';
34
35@Extend(Text)
36function inputMessageText() {
37  .fontSize($r('sys.float.ohos_id_text_size_body3'))
38  .lineHeight(Constants.PP_TEXT_LINE_HEIGHT2)
39  .fontColor($r('sys.color.ohos_id_color_handup'))
40  .fontWeight(FontWeight.Medium)
41  .margin({ top: Constants.ENCRYPTION_ADD_STAFF_BORDER_MARGIN_TOP })
42  .textAlign(TextAlign.Start)
43}
44
45@Component
46struct AddStaff {
47  @State succ: number = 0;
48  @State fail: number = 0;
49  @Link isAccountCheckSuccess: boolean;
50  @State staffArrayLength: boolean = false;
51  @State @Watch('onErrorStyleChange') isInputInvalid: boolean = false;
52  @State isNetworkInvalid: boolean = false;
53  @State textContent: string = '';
54  @Link @Watch('onDataChange') staffArray: Staff[];
55  @State focusFlag: boolean = false;
56  @Prop isDisable: boolean = false;
57  @State isInitDataStatus: boolean = false;
58  @State errInput: string[] = [];
59  @State inputArray: string[] = [];
60  @State isInputInvalidFlag: boolean = false;
61  private controller: RichEditorController = new RichEditorController();
62  private options: RichEditorOptions = { controller: this.controller };
63
64  private shardingCount: number = 0;
65
66  @Builder
67  StaffItemBuilder(authAccount: string, textContent: string, index: number) {
68    Column() {
69      staffItem({
70        authAccount: authAccount,
71        textContent: textContent,
72        isActive: true,
73        changeIndex: Number(index)
74      });
75    }
76    .alignItems(HorizontalAlign.Start);
77  }
78
79  removeItem(i: number) {
80    this.staffArray.splice(i, 1)
81    this.staffArrayLength = false;
82  }
83
84  private async onSubmitMock(inputId: string, startOffset: number[], endOffset: number[]) {
85    if (!inputId) {
86      return;
87    }
88    if (this.staffArray.length >= Constants.ENCRYPTION_ADD_STAFF_LENGTH_MAX) {
89      this.staffArrayLength = true;
90      this.deleteBuildSpan(startOffset, endOffset);
91      return;
92    }
93    this.isAccountCheckSuccess = false;
94    let regex: RegExp = new RegExp('(\r|\n)*', 'g');
95    let inputString = inputId.replace(regex, '');
96    this.inputArray = inputString.split(';');
97    this.errInput = [];
98    if (this.inputArray.length > Constants.RICH_EDITOR_FIRST) {
99      this.deleteBuildSpan(startOffset, endOffset);
100    }
101
102    await this.dealAccount(startOffset, endOffset);
103
104    this.isAccountCheckSuccess = true;
105    if (this.staffArray.length < Constants.ENCRYPTION_ADD_STAFF_LENGTH_MAX) {
106      this.controller.addTextSpan(this.errInput.join(';'));
107      if (this.errInput.length && this.isInputInvalidFlag) {
108        this.isInputInvalid = true;
109      }
110    }
111  }
112
113  private async dealAccount(startOffset: number[], endOffset: number[]) {
114    HiLog.info(TAG, 'dealAccount start');
115    this.inputArray.filter(item =>{
116      return !CommonUtil.isEmptyStr(item);
117    });
118    this.shardingCount = this.inputArray.length / Constants.ENCRYPTION_ADD_STAFF_LENGTH_MAX;
119    let startLine: number = 0;
120    for (let i = 0; i < this.shardingCount; i++) {
121      if (this.staffArray.length >= Constants.ENCRYPTION_ADD_STAFF_LENGTH_MAX) {
122        this.errInput = [];
123        break;
124      }
125      let searchArray: string[] = this.inputArray.slice(startLine, (i + 1) * Constants.ENCRYPTION_ADD_STAFF_LENGTH_MAX);
126      let accountResponse: DomainAccountResponse | undefined =
127        await AccountManager.getDomainAccountByAccountNames(searchArray);
128      this.dealAccountResponse(searchArray, accountResponse, startOffset, endOffset);
129
130      startLine = (i + 1) * Constants.ENCRYPTION_ADD_STAFF_LENGTH_MAX;
131    }
132    HiLog.info(TAG, 'dealAccount end');
133  }
134
135  private dealAccountResponse(searchArray: string[], accountResponse: DomainAccountResponse | undefined,
136    startOffset: number[], endOffset: number[]) {
137    let businessCode = this.getBusinessCode(accountResponse);
138    if (businessCode !== Constants.INTERFACE_SUCCESS) {
139      this.batchDealError(searchArray, businessCode, startOffset, endOffset);
140      return;
141    }
142    let dataArray: Array<DomainAccountInfo> | undefined = accountResponse?.getData();
143    searchArray.forEach(accountName =>{
144      let matchAccount = dataArray?.find(data => data.accountName === accountName);
145      if (matchAccount) {
146        this.addStaff(matchAccount);
147        if (this.inputArray.length === Constants.RICH_EDITOR_FIRST) {
148          this.deleteBuildSpan(startOffset, endOffset);
149        }
150        this.succ = CommonUtil.increaseByAppStorageKey(AppStorageConstant.ACCOUNT_VERIFY_SUCCESS_COUNT, 1);
151        this.addBuildSpan(accountName, matchAccount[this.textContent]);
152      } else {
153        // case: not found
154        this.showErrInput(accountName, Constants.ERR_JS_ACCOUNT_NOT_FOUND, startOffset, endOffset);
155      }
156    })
157  }
158
159  private getBusinessCode(accountResponse: DomainAccountResponse | undefined): number {
160    if (accountResponse === undefined) {
161      // case: ipc error
162      return Constants.ERR_JS_NETWORK_INVALID;
163    }
164    if (accountResponse.getErrorCode() != Constants.INTERFACE_SUCCESS) {
165      // case: ipc success, but return error code
166      return accountResponse.getErrorCode();
167    }
168    if (CommonUtil.isEmptyArray(accountResponse.getData())) {
169      // case: ipc success, but no data
170      return Constants.ERR_JS_ACCOUNT_NOT_FOUND;
171    }
172    return Constants.INTERFACE_SUCCESS;
173  }
174
175  private addStaff(matchAccount: DomainAccountInfo): void {
176    let staff: Staff = {
177      authAccount: matchAccount.accountName,
178      textContent: matchAccount[this.textContent] as string
179    };
180    this.staffArray.push(staff);
181  }
182
183  private batchDealError(accountNameArray: string[], code: number, startOffset: number[], endOffset: number[]) {
184    accountNameArray.forEach(accountName =>{
185      this.showErrInput(accountName, code, startOffset, endOffset);
186    })
187  }
188
189  private deleteBuildSpan(startOffset: number[], endOffset: number[]) {
190    for (let i: number = startOffset.length - 1; i >= 0; i--) {
191      this.controller.deleteSpans({ start: startOffset[i], end: endOffset[i] });
192    }
193  }
194
195  private addBuildSpan(staffName: string, textContent: string) {
196    let index: number = this.controller.getCaretOffset();
197    let staffBuilder: CustomBuilder = () => {
198      this.StaffItemBuilder(staffName, textContent, index);
199    };
200    this.controller.addBuilderSpan(staffBuilder);
201  }
202
203  private showErrInput(staffName: string, errorCode: number, startOffset: number[], endOffset: number[]) {
204    if (this.inputArray.length === Constants.RICH_EDITOR_FIRST) {
205      this.deleteBuildSpan(startOffset, endOffset);
206    }
207    this.errInput.push(staffName);
208    this.fail = CommonUtil.increaseByAppStorageKey(AppStorageConstant.ACCOUNT_VERIFY_FAIL_COUNT, 1);
209    if ([
210      Constants.ERR_JS_INVALID_PARAMETER,
211      Constants.ERR_JS_ACCOUNT_NOT_FOUND
212    ].includes(errorCode)) {
213      this.isInputInvalidFlag = true;
214    } else {
215      this.isNetworkInvalid = true;
216    }
217  }
218
219  private onDataChange() {
220    !this.isInitDataStatus && this.staffArray && this.staffArray.forEach((item: Staff, index: number) => {
221      let staffItemBuilder: CustomBuilder = () => {
222        this.StaffItemBuilder(item.authAccount, item.textContent, index);
223      };
224      this.controller.addBuilderSpan(staffItemBuilder);
225    });
226  }
227
228  private onErrorStyleChange() {
229    this.controller.updateSpanStyle({
230      textStyle: {
231        fontSize: $r('sys.float.ohos_id_text_size_body1'),
232        fontColor: this.isInputInvalid ?
233        $r('sys.color.ohos_id_color_handup') : $r('sys.color.ohos_id_color_text_primary')
234      }
235    });
236  }
237
238  async aboutToAppear() {
239    AccountManager.connectAbility(getContext(this) as common.UIAbilityContext);
240    await AccountTipsConfig.getConfigTips();
241    this.textContent = AccountTipsConfig.showContentKey;
242    if (this.staffArray.length) {
243      setTimeout(() => {
244        this.onDataChange();
245      }, Constants.ENCRYPTION_SET_TIMEOUT_TIME);
246    }
247  }
248
249  build() {
250    Column() {
251      Flex({
252        direction: FlexDirection.Row,
253        wrap: FlexWrap.Wrap,
254      }) {
255        RichEditor(this.options)
256          .onReady(() => {
257            this.controller.setTypingStyle({
258              fontSize: $r('sys.float.ohos_id_text_size_body1')
259            })
260          })
261          .placeholder(!this.staffArray.length ? ($r('app.string.enter_a_complete_work_ID')) : '',
262            {
263              font: { size: $r('sys.float.ohos_id_text_size_body1') },
264              fontColor: $r('sys.color.ohos_id_color_text_hint')
265            })
266          .flexGrow(Constants.ENCRYPTION_ADD_STAFF_FLEX_GROW)
267          .backgroundColor($r('sys.color.ohos_id_color_dialog_bg'))
268          .borderRadius(Constants.PP_ROW_RADIUS)
269          .align(Alignment.Center)
270          .padding({
271            top: Constants.PP_BUTTON_PAD,
272            bottom: Constants.PP_BUTTON_PAD,
273            left: Constants.PP_BUTTON_PAD,
274            right: Constants.PP_BUTTON_PAD
275          })
276          .width(Constants.FOOTER_ROW_WIDTH)
277          .constraintSize({
278            minHeight: Constants.RICH_EDITOR_MIN_HEIGHT
279          })
280          .aboutToIMEInput((value: RichEditorInsertValue) => {
281            this.isInitDataStatus = true;
282            if (this.isInputInvalid || this.isNetworkInvalid) {
283              this.isInputInvalid = false;
284              this.isNetworkInvalid = false;
285            }
286            if (value.insertValue === Constants.ENTER_KEY_VALUE) {
287              let richEditorSpans: (RichEditorTextSpanResult | RichEditorImageSpanResult)[] =
288              this.controller.getSpans();
289              let inputId: string = '';
290              let startOffset: number[] = [];
291              let endOffset: number[] = [];
292              for (let index: number = 0; index < richEditorSpans.length; index++) {
293                let buildSpan: RichEditorTextSpanResult = richEditorSpans[index] as RichEditorTextSpanResult;
294                if (buildSpan.textStyle) {
295                  inputId += buildSpan.value;
296                  startOffset.push(buildSpan.spanPosition.spanRange[0]);
297                  endOffset.push(buildSpan.spanPosition.spanRange[1]);
298                }
299              }
300              if (this.isAccountCheckSuccess) {
301                this.onSubmitMock(inputId, startOffset, endOffset);
302              };
303              return false;
304            }
305            return true;
306          })
307          .aboutToDelete((value: RichEditorDeleteValue) => {
308            if (!value.richEditorDeleteSpans.length) {
309              return false;
310            };
311            this.isInitDataStatus = true;
312            if (this.isInputInvalid || this.isNetworkInvalid) {
313              this.isInputInvalid = false;
314              this.isNetworkInvalid = false;
315            }
316
317            let richEditorSpansAll: (RichEditorTextSpanResult | RichEditorImageSpanResult)[] =
318              this.controller.getSpans();
319
320            let richEditorDeleteSpans: (RichEditorTextSpanResult | RichEditorImageSpanResult)[] =
321            value.richEditorDeleteSpans;
322            let len = richEditorDeleteSpans[richEditorDeleteSpans.length - 1].spanPosition.spanIndex;
323            let textNum: number = 0;
324            for (let i = 0; i <= len; i++) {
325              let buildSpan: RichEditorTextSpanResult = richEditorSpansAll[i] as RichEditorTextSpanResult;
326              if (buildSpan?.textStyle) {
327                textNum++;
328              }
329            }
330            for (let index: number = richEditorDeleteSpans.length - 1; index >= 0; index--) {
331              let buildSpan: RichEditorImageSpanResult = richEditorDeleteSpans[index] as RichEditorImageSpanResult;
332              if (buildSpan.imageStyle) {
333                let spanIndex: number = buildSpan.spanPosition.spanIndex;
334                spanIndex -= textNum;
335                this.removeItem(spanIndex);
336              } else {
337                textNum--;
338              }
339            }
340            return true;
341          })
342      }
343      .onFocus(() => {
344        this.focusFlag = !this.focusFlag;
345      })
346      .onBlur(() => {
347        this.focusFlag = !this.focusFlag;
348      })
349
350      Divider()
351        .strokeWidth(this.focusFlag ?
352        px2vp(Constants.ENCRYPTION_ADD_STAFF_BORDER2) : px2vp(Constants.ENCRYPTION_ADD_STAFF_BORDER))
353        .color((this.isInputInvalid || this.staffArrayLength || this.isNetworkInvalid)
354          ? $r('sys.color.ohos_id_color_handup') :
355          this.focusFlag ? $r('sys.color.ohos_id_color_primary') : $r('sys.color.ohos_id_color_list_separator'))
356        .opacity(this.focusFlag ? Constants.FOOTER_OPACITY_SEPC : Constants.FOOTER_OPACITY_ONE);
357
358      Flex({ direction: FlexDirection.Row }) {
359        if (this.isInputInvalid && !this.isNetworkInvalid) {
360          Text($r('app.string.incorrect_work_ID'))
361            .inputMessageText()
362        }
363        if (this.isNetworkInvalid) {
364          Text($r('app.string.network_invalid'))
365            .inputMessageText()
366        }
367        Blank()
368        if (this.staffArray.length >=
369          Constants.ENCRYPTION_ADD_STAFF_LENGTH_MAX * Constants.ENCRYPTION_ADD_STAFF_LENGTH) {
370          Text(`${this.staffArray.length}/${Constants.ENCRYPTION_ADD_STAFF_LENGTH_MAX}`)
371            .fontSize($r('sys.float.ohos_id_text_size_body3'))
372            .lineHeight(Constants.PP_TEXT_LINE_HEIGHT2)
373            .fontColor(this.staffArrayLength
374              ? $r('sys.color.ohos_id_color_handup') : $r('sys.color.ohos_id_color_text_secondary'))
375            .fontWeight(FontWeight.Medium)
376            .margin({ top: Constants.ENCRYPTION_ADD_STAFF_BORDER_MARGIN_TOP })
377            .textAlign(TextAlign.End)
378        }
379      }
380    }
381    .opacity(this.isDisable ? Constants.DU_LINE_WIDTH : Constants.FOOTER_OPACITY_ONE)
382    .enabled(this.isDisable ? false : true)
383  }
384}
385
386export { AddStaff };
387