• 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 from '../common/constants/CommonConstants';
25
26const TAG = '[PasteboardSwitch_Page] : ';
27let context = getContext(this) as common.UIAbilityContext;
28let localStorage = LocalStorage.getShared();
29
30interface switchStatus {
31  open: string;
32  close: string;
33}
34
35let switchState: switchStatus = {
36  open: CommonConstants.SWITCH_STATUS_OPEN,
37  close: CommonConstants.SWITCH_STATUS_CLOSE
38}
39
40@Entry
41@Component
42struct PasteboardSwitch {
43  @StorageLink('isSwitchOn') isSwitchOn: boolean | undefined = true;
44  @StorageLink('pasteboardSession') pasteboardSession: UIExtensionContentSession | undefined = undefined;
45  @State title: string = '';
46  @State screenHeight: number = 0;
47  @State screenWidth: number = 0;
48  @State shortSideSize: number = 0;
49  @State imageAnimatorHeight: number = 0;
50  @State imageAnimatorWidth: number = 0;
51  @State textWidth: number = 0;
52  @State gapLength: number = 0;
53  @State isShow: boolean = false;
54  @State is2in1: boolean = false;
55  @State portraitFunc: mediaQuery.MediaQueryResult | void | null = null;
56  @State contentHeight: number = 0;
57  @State imageArray: Array<ImageFrameInfo> = [];
58  @State animationState: AnimationStatus = AnimationStatus.Running;
59  @State reverse: boolean = false;
60  @State iterations: number = -1;
61  @State isShowBack: boolean = true;
62  private listener: mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync('(dark-mode:true)');
63  private extContext?: common.UIExtensionContext;
64  private scroller: Scroller = new Scroller();
65  private startReason?: string = '';
66
67  onPortrait(mediaQueryResult: mediaQuery.MediaQueryResult) {
68    console.log(TAG + 'onPortrait in');
69    if (mediaQueryResult.matches as boolean) {
70      this.extContext?.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_DARK);
71    } else {
72      this.extContext?.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT);
73    }
74  }
75
76  /**
77   * @description Calculate the ImageAnimator size
78   *   Calculation rule:
79   *    1.general phone & pad : screen height/2 compare with width,shorter is ImageAnimator width
80   *    2.pc: screen height/2 compare with width,shorter is ImageAnimator height
81   *    3.ratio: 3:2
82   * @param height
83   * @param width
84   */
85  getImageAnimatorSize(height: Length, width: Length) {
86    console.log(TAG + 'getImageAnimatorSize in, deviceInfo.deviceType : ' + deviceInfo.deviceType);
87    let totalHeight = height as number;
88    this.shortSideSize = width < totalHeight / 2 ? width as number : totalHeight / 2 as number;
89    if (deviceInfo.deviceType === 'phone' || deviceInfo.deviceType === 'tablet') {
90      this.imageAnimatorWidth = this.shortSideSize * 0.8;
91      this.imageAnimatorHeight = this.shortSideSize * 0.8 * 2 / 3;
92    } else if (deviceInfo.deviceType === '2in1') {
93      this.imageAnimatorWidth = this.shortSideSize * 0.8 * 3 / 2;
94      this.imageAnimatorHeight = this.shortSideSize * 0.8;
95    }
96    console.log(TAG + 'this.shortSideSize = ' + this.shortSideSize + ', this.imageAnimatorWidth = ' +
97    this.imageAnimatorWidth + ', this.imageAnimatorHeight = ' + this.imageAnimatorHeight);
98  }
99
100  getStringSync() {
101    console.log(TAG + 'getStringSync in');
102    try {
103      context.resourceManager.getStringValue($r('app.string.pasteboard_title')
104        .id, (error: BusinessError, value: string) => {
105        if (error != null) {
106          console.error(TAG + 'error is ' + error);
107        } else {
108          this.title = value;
109          console.info(TAG + '<aboutToAppear> this.title : ' + this.title);
110        }
111      })
112    } catch (error) {
113      let code: number = (error as BusinessError).code;
114      let message: string = (error as BusinessError).message;
115      console.error(TAG + `callback getStringValue failed,error code: ${code},message: ${message}.`);
116    }
117  }
118
119  getImageArray() {
120    console.info(TAG + 'getImageArray in');
121    for (let i = 1; i <= CommonConstants.IMAGE_COUNT; i++) {
122      this.imageArray.push({
123        src: $r(`app.media.pasteboard_${i}`),
124        duration: (i == CommonConstants.IMAGE_COUNT) ? CommonConstants.IMG_ANIMATOR_OVER_DURATION
125          : CommonConstants.IMG_ANIMATOR_NORMAL_DURATION
126      })
127    }
128  }
129
130  onPageShow() {
131    console.log(TAG + 'onPageShow in');
132    this.getGapLength();
133    display.getAllDisplays((err, data) => {
134      this.screenWidth = px2vp(data[0].width);
135      this.screenHeight = px2vp(data[0].height);
136      this.contentHeight = this.screenHeight;
137      console.log(TAG + 'screenWidth = ' + this.screenWidth + '; screenHeight = ' + this.screenHeight);
138    })
139    this.is2in1 = deviceInfo.deviceType === '2in1' ? true : false;
140  }
141
142  aboutToAppear() {
143    console.log(TAG + 'aboutToAppear in');
144    // Switch State Initialization
145    let value = settings.getValueSync(context, 'distributed_pasteboard_switch', switchState.open);
146    this.isSwitchOn = value != switchState.close ? true : false;
147    console.log(TAG + '<aboutToAppear> this.isSwitchOn : ' + this.isSwitchOn + '; value: ' + value);
148
149    AppStorage.setOrCreate('isSwitchOn', this.isSwitchOn);
150    console.log(TAG + 'AppStorage.get<boolean>(isSwitchOn) : ' + AppStorage.get<boolean>('isSwitchOn'));
151
152    if (this.isSwitchOn) {
153      let status: boolean = settings.setValueSync(context, 'distributed_pasteboard_switch', switchState.open);
154      console.log(TAG + 'set value success :' + status + '; set:distributed_pasteboard_switch is 1');
155    }
156
157    this.getStringSync();
158    this.getImageArray();
159
160    this.listener.on('change', (mediaQueryResult: mediaQuery.MediaQueryResult) => {
161      this.onPortrait(mediaQueryResult);
162    })
163    this.extContext = localStorage.get<common.UIExtensionContext>('context');
164    this.startReason = AppStorage.get<string>('startReason');
165    this.isShowBack = AppStorage.get<boolean>('isShowBack') ?? true;
166    console.info(`${TAG} aboutToAppear: startReason is ${this.startReason}, isShowBack: ${this.isShowBack}`);
167    if (this.isPhone()) {
168      this.checkFoldBackButton();
169    }
170    this.checkPcPadBackButton();
171  }
172
173  aboutToDisappear() {
174    console.info(`${TAG} aboutToDisappear in`);
175  }
176
177  getGapLength() {
178    console.log(TAG + 'getGapLength in, deviceInfo.deviceType : ' + deviceInfo.deviceType);
179    if (deviceInfo.deviceType == 'phone') {
180      this.gapLength = CommonConstants.GENERAL_PHONE_GAP_LENGTH;
181    } else if (deviceInfo.deviceType == '2in1' || deviceInfo.deviceType == 'tablet') {
182      this.gapLength = CommonConstants.PC_PAD_GAP_LENGTH;
183    }
184    console.log(TAG + 'this.gapLength : ' + this.gapLength);
185  }
186
187  onBackPress() {
188    console.log(TAG + 'onBackPress in');
189  }
190
191  @Builder
192  NormalRootContent() {
193    this.titleBar();
194    this.ContentBuilder();
195  }
196
197  @Builder
198  SearchRootContent() {
199    NavDestination() {
200      this.ContentBuilder();
201    }
202    .hideTitleBar(false)
203    .title(this.title)
204    .backgroundColor($r('sys.color.ohos_id_color_titlebar_sub_bg'))
205  }
206
207  @Builder
208  titleBar() {
209    Column() {
210      EditableTitleBar({
211        leftIconStyle: EditableLeftIconType.Back,
212        title: $r('app.string.pasteboard_title'),
213        isSaveIconRequired: false,
214        onCancel: () => {
215          if (this.pasteboardSession) {
216            this.pasteboardSession.sendData({ 'action': 'pop' })
217          } else {
218            console.error(TAG + 'pasteboardSession is undefined');
219          }
220        }
221      })
222    }
223  }
224
225  @Builder
226  ContentBuilder() {
227    Scroll(this.scroller) {
228      Column() {
229        ImageAnimator()
230          .images(this.imageArray)
231          .state(this.animationState)
232          .reverse(this.reverse)
233          .fillMode(this.iterations)
234          .iterations(this.iterations)
235          .width(this.imageAnimatorWidth)
236          .height(this.imageAnimatorHeight)
237          .onStart(() => {
238            console.info(TAG + 'ImageAnimator Start');
239          })
240          .onFinish(() => {
241            console.info(TAG + 'ImageAnimator Finish');
242          })
243
244        Text($r('app.string.pasteboard_desc'))
245          .fontSize($r('sys.float.ohos_id_text_size_body2'))
246          .fontWeight(FontWeight.Regular)
247          .margin({
248            bottom: CommonConstants.PASTEBOARD_DESC_TEXT_MARGIN_BOTTOM,
249            top: CommonConstants.PASTEBOARD_DESC_TEXT_MARGIN_TOP
250          })
251          .fontColor($r('sys.color.ohos_id_color_text_secondary'))
252          .textAlign(TextAlign.Center)
253          .width('100%')
254          .maxLines(10)
255          .onAreaChange((oldArea: Area, newArea: Area) => {
256            console.info(TAG + 'Text:continue desc, oldArea.height = ' + oldArea.height +
257              ', newArea.height = ' + newArea.height);
258          })
259
260
261        Column() {
262          Flex({
263            direction: FlexDirection.Row,
264            justifyContent: FlexAlign.SpaceBetween,
265            alignItems: ItemAlign.Center
266          }) {
267            Text($r('app.string.pasteboard_title'))
268              .fontSize($r('sys.float.ohos_id_text_size_sub_title2'))
269              .fontWeight(FontWeight.Medium)
270              .fontColor($r('sys.color.ohos_id_color_text_primary'))
271              .accessibilityLevel('no')
272              .maxFontScale(2.8)
273              .padding({
274                top: this.is2in1 ? CommonConstants.ITEM_LIST_PADDING_TOP_PC : CommonConstants.ITEM_LIST_PADDING_TOP,
275                bottom: this.is2in1 ? CommonConstants.ITEM_LIST_PADDING_BOTTOM_PC :
276                CommonConstants.ITEM_LIST_PADDING_BOTTOM
277              })
278
279            Toggle({ type: ToggleType.Switch, isOn: this.isSwitchOn })
280              .width(CommonConstants.PASTEBOARD_SWITCH_WIDTH)
281              .height(CommonConstants.PASTEBOARD_SWITCH_HEIGHT)
282              .hoverEffect(HoverEffect.None)
283              .onChange((isOn: boolean) => {
284                console.log(TAG + 'isOn:' + isOn);
285                this.isSwitchOn = isOn;
286                AppStorage.setAndLink('isSwitchOn', isOn);
287                if (isOn) {
288                  let status: boolean =
289                    settings.setValueSync(context, 'distributed_pasteboard_switch', switchState.open);
290                  console.log(TAG + 'is set success :' + status + '; set:distributed_pasteboard_switch is on');
291                } else {
292                  let status: boolean =
293                    settings.setValueSync(context, 'distributed_pasteboard_switch', switchState.close);
294                  console.log(TAG + 'is set success :' + status + '; set:distributed_pasteboard_switch is close');
295                }
296              })
297          }
298          .width('100%')
299          .padding({
300            left: CommonConstants.TEXT_LIST_ALIGN_DISTANCE,
301            right: CommonConstants.TEXT_LIST_ALIGN_DISTANCE
302          })
303          .backgroundColor($r('sys.color.ohos_id_color_list_card_bg'))
304          .borderRadius(this.is2in1 ? CommonConstants.PC_BORDER_RADIUS : CommonConstants.NON_PC_BORDER_RADIUS)
305          .accessibilityText(this.title)
306        }
307        .width('100%')
308        .constraintSize({
309          minHeight: CommonConstants.PC_LIST_HEIGHT
310        })
311
312        Column() {
313          Flex({
314            direction: FlexDirection.Row,
315            justifyContent: FlexAlign.Start,
316            alignItems: ItemAlign.Center
317          }) {
318            SymbolGlyph($r('sys.symbol.info_circle_fill'))
319              .fontWeight(FontWeight.Medium)
320              .fontSize(CommonConstants.SYMBOL_INFO_CIRCLE)
321              .fontColor([$r('sys.color.ohos_id_color_activated')])
322              .margin({ right: CommonConstants.SYMBOL_MARGIN_RIGHT })
323
324            Text($r('app.string.update_version_prompt'))
325              .fontSize($r('sys.float.ohos_id_text_size_body3'))
326              .fontWeight(FontWeight.Medium)
327              .fontColor($r('sys.color.ohos_id_color_text_primary'))
328              .textAlign(TextAlign.Start)
329              .lineHeight(CommonConstants.UPDATE_PROMPT_LINE_HEIGHT)
330          }
331          .margin({ top: CommonConstants.UPDATE_PROMPT_MARGIN_TOP })
332        }
333        .padding({
334          left: CommonConstants.TEXT_LIST_ALIGN_DISTANCE,
335          right: CommonConstants.TEXT_LIST_ALIGN_DISTANCE
336        })
337      }
338      .width('100%')
339      .padding({ left: this.gapLength, right: this.gapLength })
340      .margin({ bottom: this.contentHeight * 0.09 })
341      .backgroundColor($r('sys.color.ohos_id_color_sub_background'))
342    }
343    .width('100%')
344    .height(this.screenHeight)
345    .scrollable(ScrollDirection.Vertical)
346    .scrollBar(BarState.Off)
347    .align(Alignment.TopStart)
348    .friction(0.6)
349    .edgeEffect(EdgeEffect.Spring)
350    .onWillScroll((xOffset: number, yOffset: number) => {
351      console.info(TAG + 'onScroll : xOffset:' + xOffset + ' ; yOffset' + yOffset);
352    })
353    .onScrollEdge(() => {
354      console.info('To the edge');
355    })
356    .onScrollStop(() => {
357      console.info('Scroll Stop');
358    })
359    .onAreaChange((oldArea: Area, newArea: Area) => {
360      console.log(TAG + 'Scroll, oldArea.height = ' + oldArea.height + ', newArea.height = ' + newArea.height);
361    })
362  }
363
364  build() {
365    Column() {
366      if (this.isShowBack) {
367        this.NormalRootContent();
368      } else {
369        this.SearchRootContent();
370      }
371    }
372    .width('100%')
373    .height('100%')
374    .backgroundColor($r('sys.color.ohos_id_color_sub_background'))
375    .onAreaChange((oldArea: Area, newArea: Area) => {
376      console.log(TAG + 'build column , oldArea.height = ' + oldArea.height + ', newArea.height = ' + newArea.height);
377      this.getImageAnimatorSize(newArea.height, newArea.width);
378      console.info(TAG + 'this.shortSideSize = ' + this.shortSideSize + ', this.imageAnimatorWidth = ' +
379      this.imageAnimatorWidth + ', this.imageAnimatorHeight = ' + this.imageAnimatorHeight);
380    })
381  }
382
383  private checkPcPadBackButton(): void {
384    console.info(`${TAG} checkPcPadBackButton in`);
385    if (this.startReason === 'from_search') {
386      if (deviceInfo.deviceType === '2in1' || deviceInfo.deviceType === 'tablet') {
387        this.isShowBack = false;
388      }
389    }
390  }
391
392  private isPhone(): boolean {
393    console.info(`${TAG} isPhone in`);
394    return (deviceInfo.deviceType === 'phone' || deviceInfo.deviceType === 'default');
395  }
396
397  private refreshFoldStatus(foldStatus: display.FoldStatus): void {
398    console.info(`${TAG} refreshFoldStatus in. foldStatus: ${foldStatus}. startReason: ${this.startReason}`);
399    if (this.startReason === 'from_search') {
400      if (foldStatus === display.FoldStatus.FOLD_STATUS_FOLDED) {
401        this.isShowBack = true;
402        console.info(`${TAG} foldStatus: ${foldStatus}. this.isShowBack: ${this.isShowBack}`);
403      } else {
404        this.isShowBack = false;
405        console.info(`${TAG} foldStatus: ${foldStatus}. this.isShowBack: ${this.isShowBack}`);
406      }
407    } else {
408      this.isShowBack = true;
409      console.info(`${TAG} startReason: ${this.startReason}. this.isShowBack: ${this.isShowBack}`);
410    }
411  }
412
413  private checkFoldBackButton(): void {
414    console.info(`${TAG} checkFoldBackButton in`);
415    if (display.isFoldable()) {
416      try {
417        display.on('foldStatusChange', (foldStatus: display.FoldStatus) => {
418          let foldStatusValue = foldStatus.valueOf();
419          console.info(`${TAG} checkFoldBackButton: foldStatusValue is ${foldStatusValue}`);
420          this.refreshFoldStatus(foldStatusValue);
421        })
422        let data: display.FoldStatus = display.getFoldStatus();
423        this.refreshFoldStatus(data);
424      } catch (err) {
425        console.error(`${TAG} Register failed. exception: ${err.message}`);
426      }
427    }
428  }
429}