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}