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 common from '@ohos.app.ability.common';
16import display from '@ohos.display';
17import settings from '@ohos.settings';
18import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession';
19import deviceInfo from '@ohos.deviceInfo';
20import { BusinessError } from '@ohos.base';
21import { EditableLeftIconType, EditableTitleBar } from '@ohos.arkui.advanced.EditableTitleBar';
22import mediaQuery from '@ohos.mediaquery';
23import ConfigurationConstant from '@ohos.app.ability.ConfigurationConstant';
24import CommonConstants, { FontSizeScale } from '../common/constants/CommonConstants';
25import { logger } from '../utils/Logger';
26import { TipsJumpUtils } from '../utils/TipsJumpUtils';
27import osAccount from '@ohos.account.osAccount';
28import systemParameterEnhance from '@ohos.systemParameterEnhance';
29
30const TAG = '[ContinueSwitch_Page] : ';
31let context = getContext(this) as common.UIAbilityContext;
32let localStorage = LocalStorage.getShared();
33
34interface switchStatus {
35  open: string;
36  close: string;
37}
38
39let switchState: switchStatus = {
40  open: CommonConstants.SWITCH_STATUS_OPEN,
41  close: CommonConstants.SWITCH_STATUS_CLOSE
42}
43
44@Entry
45@Component
46struct ContinueSwitch {
47  @StorageLink('isSwitchOn') isSwitchOn: boolean | undefined = true;
48  @StorageLink('continueSession') continueSession: UIExtensionContentSession | undefined = undefined;
49  @State title: string = '';
50  @State screenHeight: number = 0;
51  @State screenWidth: number = 0;
52  @State shortSideSize: number = 0;
53  @State imageAnimatorHeight: number = this.getImageAnimatorHeight();
54  @State imageAnimatorWidth: number = this.getImageAnimatorWidth();
55  @State textWidth: number = 0;
56  @State gapLength: number = 0;
57  @State isShow: boolean = false;
58  @State is2in1: boolean = false;
59  @State portraitFunc: mediaQuery.MediaQueryResult | void | null = null;
60  @State isVideoVisible: Visibility = Visibility.Hidden;
61  @State contentHeight: number = 0;
62  @State imageArray: Array<ImageFrameInfo> = [];
63  @State animationState: AnimationStatus = AnimationStatus.Running;
64  @State reverse: boolean = false;
65  @State iterations: number = -1;
66  @State isEnabled: boolean = true;
67  @State isShowBack: boolean = true;
68  @State isAnimatorDone: boolean = false;
69  @StorageProp('currentFontSizeScale') @Watch('onFontSizeScaleChange') fontSizeScale: number = 1;
70  @State phoneSwitchTextTopMargin: string = '17vp';
71  @State phoneSwitchTextBottomMargin: string = '18vp';
72  private listener: mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync('(dark-mode:true)');
73  private extContext?: common.UIExtensionContext;
74  private scroller: Scroller = new Scroller();
75  private learnMore: ResourceStr = $r('app.string.learn_more');
76  private continueDesc: ResourceStr = $r('app.string.continue_desc_text', '');
77  private accountManager: osAccount.AccountManager = osAccount.getAccountManager();
78  private startReason?: string = '';
79
80  onPortrait(mediaQueryResult: mediaQuery.MediaQueryResult): void {
81    logger.info(`${TAG} 'onPortrait in`);
82    if (mediaQueryResult.matches as boolean) {
83      this.extContext?.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_DARK);
84    } else {
85      this.extContext?.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT);
86    }
87  }
88
89  onFontSizeScaleChange(): void {
90    logger.info(`${TAG} onFontSizeScaleChange`);
91    this.phoneSwitchTextTopMargin = this.UpdateMarginBasedOnFontSize(17, this.fontSizeScale);
92    this.phoneSwitchTextBottomMargin = this.UpdateMarginBasedOnFontSize(18, this.fontSizeScale);
93  }
94
95  /**
96   * Update the margins of the switch list according to the font size.
97   */
98  public UpdateMarginBasedOnFontSize(fontFp: number, fontSizeScale: number): string {
99    logger.info(`${TAG} getlistSpace, fontSizeScale: ${fontSizeScale} ; fontFp: ${fontFp}`);
100    switch (fontSizeScale) {
101      case FontSizeScale.XXL1:
102        return '16vp';
103      case FontSizeScale.XXL2:
104        return '20vp';
105      case FontSizeScale.XXL3:
106        return '24vp';
107      default:
108        return `${fontFp}vp`;
109    }
110  }
111
112  /**
113   *  Initialize the switch list spacing size
114   */
115  public phoneSwitchTextMarginInit(): void {
116    let fontSizeScale = parseFloat(systemParameterEnhance.getSync(CommonConstants.FONT_SIZE_SCALE_PARAM, '1'));
117    this.phoneSwitchTextTopMargin = this.UpdateMarginBasedOnFontSize(17, fontSizeScale);
118    this.phoneSwitchTextBottomMargin = this.UpdateMarginBasedOnFontSize(18, fontSizeScale);
119  }
120
121  getStringSync(): void {
122    logger.info(`${TAG} getStringSync in`);
123    try {
124      context.resourceManager.getStringValue($r('app.string.continue_title')
125        .id, (error: BusinessError, value: string) => {
126        if (error != null) {
127          logger.error(TAG + 'error is ' + error);
128        } else {
129          this.title = value;
130          logger.info(`${TAG} <aboutToAppear> this.title : ${this.title}`);
131        }
132      })
133    } catch (error) {
134      let code: number = (error as BusinessError).code;
135      let message: string = (error as BusinessError).message;
136      logger.error(`${TAG} callback getStringValue failed,error code: ${code},message: ${message}.`);
137    }
138  }
139
140  getImageArray(): void {
141    logger.info(`${TAG} getImageArray in`);
142    for (let i = 0; i <= CommonConstants.IMAGE_COUNT; ++i) {
143      this.imageArray.push({
144        src: $r(`app.media.continue_${i}`),
145        duration: (i == CommonConstants.IMAGE_COUNT) ? CommonConstants.IMG_ANIMATOR_OVER_DURATION
146          : CommonConstants.IMG_ANIMATOR_NORMAL_DURATION
147      })
148    }
149  }
150
151  getGapLength(): void {
152    logger.info(`${TAG} getGapLength in, deviceInfo.deviceType : ${deviceInfo.deviceType}`);
153    if (deviceInfo.deviceType == 'phone') {
154      this.gapLength = CommonConstants.GENERAL_PHONE_GAP_LENGTH;
155    } else if (deviceInfo.deviceType == '2in1' || deviceInfo.deviceType == 'tablet') {
156      this.gapLength = CommonConstants.PC_PAD_GAP_LENGTH;
157    }
158    logger.info(`${TAG} this.gapLength : ${this.gapLength}`);
159  }
160
161  getForegroundOsAccountLocalId(): void {
162    logger.info(`${TAG} getForegroundOsAccountLocalId in`);
163    try {
164      this.accountManager.getForegroundOsAccountLocalId().then((localId: number) => {
165        logger.info(`${TAG} getForegroundOsAccountLocalId, localId: ${localId}`);
166        this.getAccountInfo(localId);
167      }).catch((err: BusinessError) => {
168        logger.error(`${TAG} getForegroundOsAccountLocalId errCode: ${err?.code}`);
169      });
170    } catch (err) {
171      logger.error(`${TAG} getForegroundOsAccountLocalId exception: ${err?.message}`);
172    }
173  }
174
175  getAccountInfo(localId: number): void {
176    logger.info(`${TAG} getAccountInfo in`);
177    try {
178      this.accountManager.queryOsAccountById(localId).then((accountInfo: osAccount.OsAccountInfo) => {
179        logger.info(`${TAG} queryOsAccountById, accountInfo.type: ${accountInfo.type}`);
180        if (accountInfo.type === osAccount.OsAccountType.PRIVATE) {
181          let status: boolean = settings.setValueSync(context, CommonConstants.CONTINUE_SWITCH_KEY, switchState.close,
182            settings.domainName.USER_SECURITY);
183          this.isSwitchOn = false;
184          this.isEnabled = false;
185          logger.info(`${TAG} set value isSuccess : status = ${status}; set:Continue_Switch_Status is 0`);
186        }
187      }).catch((err: BusinessError) => {
188        logger.error(`${TAG} queryOsAccountById errCode: ${err?.code}`);
189      });
190    } catch (err) {
191      logger.error(`${TAG} queryOsAccountById exception: ${err?.message}`);
192    }
193  }
194
195  onPageShow() {
196    logger.info(`${TAG} onPageShow in`);
197    this.getGapLength();
198    display.getAllDisplays((err, data) => {
199      this.screenWidth = px2vp(data[0].width);
200      this.screenHeight = px2vp(data[0].height);
201      this.contentHeight = this.screenHeight;
202      logger.info(`${TAG} screenWidth = ${this.screenWidth}; screenHeight = ${this.screenHeight}`);
203    })
204    this.is2in1 = deviceInfo.deviceType === '2in1' ? true : false;
205  }
206
207  aboutToAppear() {
208    logger.info(`${TAG} aboutToAppear in`);
209    // Switch State Initialization
210    let value = settings.getValueSync(context, CommonConstants.CONTINUE_SWITCH_KEY, switchState.open,
211      settings.domainName.USER_SECURITY);
212    this.isSwitchOn = value != switchState.close ? true : false;
213    logger.info(`${TAG} <aboutToAppear> this.isSwitchOn : ${this.isSwitchOn}; value: ${value}`);
214
215    AppStorage.setOrCreate('isSwitchOn', this.isSwitchOn);
216    logger.info(`${TAG} AppStorage.get<boolean>(isSwitchOn) : ${AppStorage.get<boolean>('isSwitchOn')}`);
217
218    if (this.isSwitchOn) {
219      let status: boolean = settings.setValueSync(context, CommonConstants.CONTINUE_SWITCH_KEY, switchState.open,
220        settings.domainName.USER_SECURITY);
221      logger.info(`${TAG} set value success :${status}; set:Continue_Switch_Status is 1`);
222    }
223
224    this.getStringSync();
225    this.getImageArray();
226    this.getForegroundOsAccountLocalId();
227    this.listener.on('change', (mediaQueryResult: mediaQuery.MediaQueryResult) => {
228      this.onPortrait(mediaQueryResult);
229    })
230    this.extContext = localStorage.get<common.UIExtensionContext>('context');
231    this.startReason = AppStorage.get<string>('startReason');
232    this.isShowBack = AppStorage.get<boolean>('isShowBack') ?? true;
233    logger.info(`${TAG} aboutToAppear: startReason is ${this.startReason}, isShowBack: ${this.isShowBack}`);
234    if (this.isPhone()) {
235      this.checkFoldBackButton();
236    }
237    this.checkPcPadBackButton();
238    this.phoneSwitchTextMarginInit();
239    setTimeout(() => {
240      this.isAnimatorDone = true;
241    }, 20)
242  }
243
244  aboutToDisappear() {
245    logger.info(`${TAG} aboutToDisappear in`);
246  }
247
248  onBackPress() {
249    logger.info(`${TAG} onBackPress in`);
250  }
251
252  @Builder
253  NormalRootContent() {
254    this.titleBar();
255    this.ContentBuilder();
256  }
257
258  @Builder
259  SearchRootContent() {
260    NavDestination() {
261      this.ContentBuilder();
262    }
263    .hideTitleBar(false)
264    .title(this.title)
265    .backgroundColor($r('sys.color.ohos_id_color_titlebar_sub_bg'))
266  }
267
268  @Builder
269  titleBar() {
270    Column() {
271      EditableTitleBar({
272        leftIconStyle: EditableLeftIconType.Back,
273        title: $r('app.string.continue_title'),
274        isSaveIconRequired: false,
275        onCancel: () => {
276          if (this.continueSession) {
277            this.continueSession.sendData({ 'action': 'pop' })
278          } else {
279            logger.error(`${TAG} continueSession is undefined`);
280          }
281        }
282      })
283    }
284  }
285
286  @Builder
287  ContentBuilder() {
288    Scroll(this.scroller) {
289      Column() {
290        ImageAnimator()
291          .images(this.imageArray)
292          .state(this.animationState)
293          .reverse(this.reverse)
294          .fillMode(this.iterations)
295          .iterations(this.iterations)
296          .width(this.imageAnimatorWidth)
297          .height(this.imageAnimatorHeight)
298          .onStart(() => {
299            logger.info(`${TAG} ImageAnimator Start`);
300          })
301          .onFinish(() => {
302            logger.info(`${TAG} ImageAnimator Finish`);
303          })
304
305        Text() {
306          Span(this.continueDesc)
307            .fontFamily('HarmonyHeiTi')
308            .fontSize($r('sys.float.ohos_id_text_size_body2'))
309            .fontColor($r('sys.color.ohos_id_color_text_secondary'))
310            .fontWeight(FontWeight.Regular)
311          Span(this.learnMore)
312            .fontFamily('HarmonyHeiTi')
313            .fontSize($r('sys.float.ohos_id_text_size_body2'))
314            .fontColor($r('sys.color.ohos_id_color_text_primary_activated'))
315            .fontWeight(FontWeight.Medium)
316            .onClick(() => {
317              TipsJumpUtils.jumpTips(getContext(this) as common.UIAbilityContext, CommonConstants.FUN_NUM,
318                CommonConstants.TIPS_TYPE);
319            })
320        }
321        .margin({
322          bottom: CommonConstants.CONTINUE_DESC_TEXT_MARGIN_BOTTOM,
323          top: CommonConstants.CONTINUE_DESC_TEXT_MARGIN_TOP
324        })
325        .textAlign(TextAlign.Center)
326        .width('100%')
327
328        Column() {
329          Flex({
330            direction: FlexDirection.Row,
331            justifyContent: FlexAlign.SpaceBetween,
332            alignItems: ItemAlign.Center
333          }) {
334            Text($r('app.string.continue_title'))
335              .fontSize($r('sys.float.ohos_id_text_size_sub_title2'))
336              .fontWeight(FontWeight.Medium)
337              .fontColor($r('sys.color.ohos_id_color_text_primary'))
338              .accessibilityLevel('no')
339              .padding({
340                top: this.is2in1 ? CommonConstants.ITEM_LIST_PADDING_TOP_PC : this.phoneSwitchTextTopMargin,
341                bottom: this.is2in1 ? CommonConstants.ITEM_LIST_PADDING_BOTTOM_PC : this.phoneSwitchTextBottomMargin
342              })
343
344            Toggle({ type: ToggleType.Switch, isOn: this.isSwitchOn })
345              .width(CommonConstants.CONTINUE_SWITCH_WIDTH)
346              .height(CommonConstants.CONTINUE_SWITCH_HEIGHT)
347              .hoverEffect(HoverEffect.None)
348              .enabled(this.isEnabled)
349              .onChange((isOn: boolean) => {
350                logger.info(`${TAG} isOn: ${isOn}`);
351                this.isSwitchOn = isOn;
352                AppStorage.setAndLink('isSwitchOn', isOn);
353                if (isOn) {
354                  let status: boolean = settings.setValueSync(context, CommonConstants.CONTINUE_SWITCH_KEY,
355                    switchState.open, settings.domainName.USER_SECURITY);
356                  logger.info(`${TAG} is set success :${status}; set:Continue_Switch_Status is open`);
357                } else {
358                  let status: boolean = settings.setValueSync(context, CommonConstants.CONTINUE_SWITCH_KEY,
359                    switchState.close, settings.domainName.USER_SECURITY);
360                  logger.info(`${TAG} is set success :${status}; set:Continue_Switch_Status is close`);
361                }
362              })
363          }
364          .width('100%')
365          .padding({
366            left: CommonConstants.TEXT_LIST_ALIGN_DISTANCE,
367            right: CommonConstants.TEXT_LIST_ALIGN_DISTANCE
368          })
369          .backgroundColor($r('sys.color.ohos_id_color_list_card_bg'))
370          .borderRadius(this.is2in1 ? CommonConstants.PC_BORDER_RADIUS : CommonConstants.NON_PC_BORDER_RADIUS)
371          .accessibilityText(this.title)
372        }
373        .width('100%')
374        .constraintSize({
375          minHeight: CommonConstants.PC_LIST_HEIGHT
376        })
377
378        Column() {
379          Flex({
380            direction: FlexDirection.Row,
381            justifyContent: FlexAlign.Start,
382            alignItems: ItemAlign.Center
383          }) {
384            SymbolGlyph($r('sys.symbol.info_circle_fill'))
385              .fontWeight(FontWeight.Medium)
386              .fontSize(CommonConstants.SYMBOL_INFO_CIRCLE)
387              .fontColor([$r('sys.color.ohos_id_color_activated')])
388              .margin({ right: CommonConstants.SYMBOL_MARGIN_RIGHT })
389              .width(CommonConstants.SYMBOL_INFO_CIRCLE)
390              .height(CommonConstants.SYMBOL_INFO_CIRCLE)
391              .accessibilityLevel('no')
392
393            Text($r('app.string.update_version_prompt'))
394              .fontSize($r('sys.float.ohos_id_text_size_body3'))
395              .fontWeight(FontWeight.Medium)
396              .fontColor($r('sys.color.ohos_id_color_text_primary'))
397              .textAlign(TextAlign.Start)
398              .lineHeight(CommonConstants.UPDATE_PROMPT_LINE_HEIGHT)
399          }
400          .margin({ top: CommonConstants.UPDATE_PROMPT_MARGIN_TOP })
401        }
402        .padding({
403          left: CommonConstants.TEXT_LIST_ALIGN_DISTANCE,
404          right: CommonConstants.TEXT_LIST_ALIGN_DISTANCE
405        })
406
407        Column() {
408          Row() {
409            Text($r('app.string.continue_privacy_text'))
410              .fontSize($r('sys.float.ohos_id_text_size_body3'))
411              .fontWeight(FontWeight.Regular)
412              .margin({ top: CommonConstants.CONTINUE_PRIVACY_TEXT_MARGIN_TOP })
413              .fontColor($r('sys.color.ohos_id_color_text_secondary'))
414              .textAlign(TextAlign.Start)
415              .width('100%')
416          }
417          .padding({
418            left: CommonConstants.TEXT_LIST_ALIGN_DISTANCE,
419            right: CommonConstants.TEXT_LIST_ALIGN_DISTANCE
420          })
421        }.width('100%')
422
423      }
424      .width('100%')
425      .padding({ left: this.gapLength, right: this.gapLength })
426      .margin({ bottom: this.contentHeight * 0.09 })
427      .backgroundColor($r('sys.color.ohos_id_color_sub_background'))
428      .visibility(this.getContentVisibility())
429    }
430    .width('100%')
431    .height(this.screenHeight)
432    .scrollable(ScrollDirection.Vertical)
433    .scrollBar(BarState.Off)
434    .align(Alignment.TopStart)
435    .friction(0.6)
436    .edgeEffect(EdgeEffect.Spring)
437    .onScrollEdge(() => {
438      logger.info('To the edge');
439    })
440    .onScrollStop(() => {
441      logger.info('Scroll Stop');
442    })
443    .onAreaChange((oldArea: Area, newArea: Area) => {
444      logger.info(`${TAG} Scroll, oldArea.height = ${oldArea.height}, newArea.height = ${newArea.height}`);
445    })
446  }
447
448  build() {
449    Column() {
450      if (this.isShowBack) {
451        this.NormalRootContent();
452      } else {
453        this.SearchRootContent();
454      }
455    }
456    .width('100%')
457    .height('100%')
458    .backgroundColor($r('sys.color.ohos_id_color_sub_background'))
459    .onAreaChange((oldArea: Area, newArea: Area) => {
460      logger.info(`${TAG} build column , oldArea.height = ${oldArea.height}, newArea.height = ${newArea.height}`);
461      logger.info(`${TAG} this.shortSideSize = ${this.shortSideSize}, this.imageAnimatorWidth =
462        ${this.imageAnimatorWidth}, this.imageAnimatorHeight = ${this.imageAnimatorHeight}`);
463    })
464  }
465
466  private getContentVisibility(): Visibility {
467    return (this.imageAnimatorWidth !== 0 && this.isAnimatorDone) ? Visibility.Visible : Visibility.Hidden;
468  }
469
470  private getImageAnimatorHeight(): number {
471    if (deviceInfo.deviceType === 'phone') {
472      return CommonConstants.ANIMATOR_HEIGHT_PHONE;
473    } else if (deviceInfo.deviceType === '2in1') {
474      return CommonConstants.ANIMATOR_HEIGHT_PC;
475    } else if (deviceInfo.deviceType === 'tablet') {
476      return CommonConstants.ANIMATOR_HEIGHT_PAD;
477    }
478    return CommonConstants.ANIMATOR_HEIGHT_PHONE;
479  }
480
481  private getImageAnimatorWidth(): number {
482    if (deviceInfo.deviceType === 'phone') {
483      return CommonConstants.ANIMATOR_WIDTH_PHONE;
484    } else if (deviceInfo.deviceType === '2in1') {
485      return CommonConstants.ANIMATOR_WIDTH_PC;
486    } else if (deviceInfo.deviceType === 'tablet') {
487      return CommonConstants.ANIMATOR_WIDTH_PAD;
488    }
489    return CommonConstants.ANIMATOR_WIDTH_PHONE;
490  }
491
492  private checkPcPadBackButton(): void {
493    logger.info(`${TAG} checkPcPadBackButton in`);
494    if (this.startReason === 'from_search') {
495      if (deviceInfo.deviceType === '2in1' || deviceInfo.deviceType === 'tablet') {
496        this.isShowBack = false;
497      }
498    }
499  }
500
501  private isPhone(): boolean {
502    logger.info(`${TAG} isPhone in`);
503    return (deviceInfo.deviceType === 'phone' || deviceInfo.deviceType === 'default');
504  }
505
506  private refreshFoldStatus(foldStatus: display.FoldStatus): void {
507    logger.info(`${TAG} refreshFoldStatus in. foldStatus: ${foldStatus}. startReason: ${this.startReason}`);
508    if (this.startReason === 'from_search') {
509      if (foldStatus === display.FoldStatus.FOLD_STATUS_FOLDED) {
510        this.isShowBack = true;
511        logger.info(`${TAG} foldStatus: ${foldStatus}. this.isShowBack: ${this.isShowBack}`);
512      } else {
513        this.isShowBack = false;
514        logger.info(`${TAG} foldStatus: ${foldStatus}. this.isShowBack: ${this.isShowBack}`);
515      }
516    } else {
517      this.isShowBack = true;
518      logger.info(`${TAG} startReason: ${this.startReason}. this.isShowBack: ${this.isShowBack}`);
519    }
520  }
521
522  private checkFoldBackButton(): void {
523    logger.info(`${TAG} checkFoldBackButton in`);
524    if (display.isFoldable()) {
525      try {
526        display.on('foldStatusChange', (foldStatus: display.FoldStatus) => {
527          let foldStatusValue = foldStatus.valueOf();
528          logger.info(`${TAG} checkFoldBackButton: foldStatusValue is ${foldStatusValue}`);
529          this.refreshFoldStatus(foldStatusValue);
530        })
531        let data: display.FoldStatus = display.getFoldStatus();
532        this.refreshFoldStatus(data);
533      } catch (err) {
534        logger.error(`${TAG} Register failed. exception: ${err.message}`);
535      }
536    }
537  }
538}