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