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