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}