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 */ 15 16import { hilog } from '@kit.PerformanceAnalysisKit'; 17import deviceInfo from '@ohos.deviceInfo'; 18import { display, mediaquery } from '@kit.ArkUI'; 19import base from '@ohos.base'; 20 21const TAG = 'DeviceHelper'; 22 23/** 24 * device info util 25 * 26 */ 27export class DeviceHelper { 28 static readonly TYPE_DEFAULT = 'default'; 29 static readonly TYPE_PHONE = 'phone'; 30 static readonly TYPE_TABLET = 'tablet'; 31 static readonly DEVICE_TYPE = deviceInfo.deviceType; 32 33 /** 34 * whether the device type is phone 35 * 36 * @returns true if is phone 37 */ 38 static isPhone(): boolean { 39 return (DeviceHelper.DEVICE_TYPE === DeviceHelper.TYPE_PHONE || 40 DeviceHelper.DEVICE_TYPE === DeviceHelper.TYPE_DEFAULT); 41 } 42 43 /** 44 * whether the device type is tablet 45 * 46 * @returns true if is tablet 47 */ 48 public static isTablet(): boolean { 49 return DeviceHelper.DEVICE_TYPE === DeviceHelper.TYPE_TABLET; 50 } 51 52 /** 53 * Check if is foldable 54 * 55 * @returns true if is foldable 56 */ 57 static isFold(): boolean { 58 let isFold: boolean = false; 59 try { 60 isFold = display.isFoldable(); 61 } catch (e) { 62 hilog.error(0x0000, TAG, 'isFold -> isFoldable try error:', e); 63 } 64 return isFold; 65 } 66 67 /** 68 * Check if is expanded 69 * 70 * @returns true if is expanded 71 */ 72 static isExpanded(): boolean { 73 let isExpanded: boolean = false; 74 try { 75 isExpanded = display.getFoldStatus() === display.FoldStatus.FOLD_STATUS_EXPANDED; 76 } catch (e) { 77 hilog.error(0x0000, TAG, 'isExpanded -> try error:', e); 78 } 79 return isExpanded; 80 } 81 82 /** 83 * Check if is column 84 * 85 * @returns true if is column 86 */ 87 static isColumn(): boolean { 88 let isColumn: boolean = false; 89 try { 90 isColumn = display.isFoldable() && (display.getFoldStatus() === display.FoldStatus.FOLD_STATUS_EXPANDED || 91 display.getFoldStatus() === display.FoldStatus.FOLD_STATUS_HALF_FOLDED); 92 } catch (e) { 93 hilog.error(0x0000, TAG, 'isColumn -> try error:', e); 94 } 95 return isColumn; 96 } 97 98 /** 99 * Check if is straight product 100 * 101 * @returns true if is straight product 102 */ 103 public static isStraightProduct(): boolean { 104 return DeviceHelper.isPhone() && !DeviceHelper.isFold(); 105 } 106} 107 108export class DeviceListenerManager { 109 private static instance: DeviceListenerManager | undefined; 110 private portraitListener = mediaquery.matchMediaSync('(orientation: portrait)'); 111 private drawableWidthLargeListener = mediaquery.matchMediaSync('(width >= 600vp)'); 112 private isPortrait: boolean | undefined = undefined; 113 private onOrientationChange: Function | undefined = undefined; 114 private isLarge: boolean | undefined = undefined; 115 private onDrawableWidthChange: Function | undefined = undefined; 116 117 public static getInstance(): DeviceListenerManager { 118 if (DeviceListenerManager.instance === undefined) { 119 DeviceListenerManager.instance = new DeviceListenerManager(); 120 } 121 return DeviceListenerManager.instance; 122 } 123 124 private onPortraitChange(result: mediaquery.MediaQueryResult) { 125 let isChanged: boolean = false; 126 if (DeviceListenerManager.getInstance().isPortrait === undefined) { 127 DeviceListenerManager.getInstance().isPortrait = result.matches; 128 isChanged = true; 129 } else { 130 if (result.matches) { 131 if (!DeviceListenerManager.getInstance().isPortrait) { 132 DeviceListenerManager.getInstance().isPortrait = true; 133 isChanged = true; 134 hilog.debug(0x0000, 'MultiNavigation', 'display portrait'); 135 } 136 } else { 137 if (DeviceListenerManager.getInstance().isPortrait) { 138 DeviceListenerManager.getInstance().isPortrait = false; 139 isChanged = true; 140 hilog.debug(0x0000, 'MultiNavigation', 'display landscape'); 141 } 142 } 143 } 144 if (isChanged) { 145 DeviceListenerManager.getInstance().notifyOrientationChange(); 146 } 147 } 148 149 private notifyOrientationChange() { 150 this.onOrientationChange && this.onOrientationChange(this.isPortrait); 151 } 152 153 private onDrawableWidthLargeChange(result: mediaquery.MediaQueryResult) { 154 let isChanged: boolean = false; 155 if (DeviceListenerManager.getInstance().isLarge === undefined) { 156 DeviceListenerManager.getInstance().isLarge = result.matches; 157 isChanged = true; 158 } else { 159 if (result.matches) { 160 if (!DeviceListenerManager.getInstance().isLarge) { 161 DeviceListenerManager.getInstance().isLarge = true; 162 isChanged = true; 163 hilog.debug(0x0000, 'MultiNavigation', 'display isLarge'); 164 } 165 } else { 166 if (DeviceListenerManager.getInstance().isLarge) { 167 DeviceListenerManager.getInstance().isLarge = false; 168 isChanged = true; 169 hilog.debug(0x0000, 'MultiNavigation', 'display not large'); 170 } 171 } 172 } 173 174 if (isChanged) { 175 DeviceListenerManager.getInstance().notifyWidthChange(); 176 } 177 } 178 179 private notifyWidthChange() { 180 this.onDrawableWidthChange && this.onDrawableWidthChange(this.isLarge); 181 } 182 183 public registerOrientationLister(func: Function): void { 184 this.onOrientationChange = func; 185 this.onOrientationChange && this.isPortrait && this.onOrientationChange(this.isPortrait); 186 } 187 188 public unregisterOrientationLister(): void { 189 this.onOrientationChange = undefined; 190 } 191 192 public registerDrawableWidthLister(func: Function): void { 193 this.onDrawableWidthChange = func; 194 this.onDrawableWidthChange && this.isLarge && this.onDrawableWidthChange(this.isLarge); 195 } 196 197 public unregisterDrawableWidthLister(): void { 198 this.onDrawableWidthChange = undefined; 199 } 200 201 public initListener(): void { 202 this.portraitListener.on('change', this.onPortraitChange); 203 this.drawableWidthLargeListener.on('change', this.onDrawableWidthLargeChange); 204 } 205 206 public finalizeListener() { 207 this.portraitListener.off('change', this.onPortraitChange); 208 this.drawableWidthLargeListener.off('change', this.onDrawableWidthLargeChange); 209 } 210} 211 212@Observed 213export class NavWidthRangeAttrModifier implements AttributeModifier<NavigationAttribute> { 214 isApplicationSet: boolean = false; 215 minHomeWidth: Percentage = '50%'; 216 maxHomeWidth: Percentage = '50%'; 217 218 applyNormalAttribute(instance: NavigationAttribute): void { 219 if (this.isApplicationSet) { 220 instance.navBarWidthRange([this.minHomeWidth, this.maxHomeWidth]); 221 } 222 } 223} 224 225@Component 226export struct SubNavigation { 227 @Link isPortrait: boolean; 228 @State displayMode: number = 0; 229 @ObjectLink multiStack: MultiNavPathStack 230 @BuilderParam navDestination: NavDestinationBuildFunction; 231 primaryStack: MyNavPathStack = new MyNavPathStack(); 232 @State secondaryStack: MyNavPathStack = new MyNavPathStack(); 233 @State primaryWidth: number | string = '50%'; 234 @ObjectLink needRenderIsFullScreen: NeedRenderIsFullScreen; 235 @ObjectLink needRenderLeftClickCount: NeedRenderLeftClickCount; 236 @ObjectLink navWidthRangeModifier: NavWidthRangeAttrModifier; 237 @ObjectLink needRenderDisplayMode: NeedRenderDisplayMode; 238 onNavigationModeChange?: OnNavigationModeChangeCallback = (mode: NavigationMode) => {}; 239 240 @Builder 241 SubNavDestination(name: string, param?: object) { 242 this.navDestination(name, param); 243 } 244 245 getMode(): NavigationMode { 246 this.displayMode = this.needRenderDisplayMode.displayMode; 247 if (DeviceHelper.isPhone() && DeviceHelper.isStraightProduct()) { 248 return NavigationMode.Stack; 249 } 250 if (this.displayMode === display.FoldStatus.FOLD_STATUS_UNKNOWN) { 251 try { 252 this.displayMode = display.getFoldStatus(); 253 } catch (err) { 254 hilog.warn(0x0000, 'MultiNavigation', 'Failed to get fold status. error:' + JSON.stringify(err)); 255 } 256 } 257 if (DeviceHelper.isTablet() && this.isPortrait) { 258 hilog.info(0x0000, 'MultiNavigation', 'SubNavigation getMode tablet portrait'); 259 return NavigationMode.Stack; 260 } 261 if (this.needRenderIsFullScreen.isFullScreen == undefined) { 262 if (DeviceHelper.isPhone()) { 263 return this.secondaryStack.size() > 0 && DeviceHelper.isColumn() ? NavigationMode.Auto : NavigationMode.Stack; 264 } 265 return this.secondaryStack.size() > 0 ? NavigationMode.Auto : NavigationMode.Stack; 266 } 267 return this.needRenderIsFullScreen.isFullScreen ? NavigationMode.Stack : NavigationMode.Auto; 268 } 269 270 aboutToAppear(): void { 271 hilog.debug(0x0000, 'MultiNavigation', 'SubNavigation aboutToAppear param = ' + JSON.stringify(this.primaryStack)); 272 } 273 274 build() { 275 NavDestination() { 276 Navigation(this.secondaryStack) { 277 Navigation(this.primaryStack) { 278 } 279 .hideNavBar(true) 280 .mode(NavigationMode.Stack) 281 .navDestination(this.SubNavDestination) 282 .hideTitleBar(true) 283 .hideToolBar(true) 284 .hideBackButton(true) 285 .onTouch((event) => { 286 if (event.type === TouchType.Down) { 287 this.needRenderLeftClickCount.leftClickCount = 2; 288 } 289 }) 290 } 291 .mode(this.getMode()) 292 .onNavigationModeChange(this?.onNavigationModeChange) 293 .hideBackButton(true) 294 .hideTitleBar(true) 295 .navDestination(this.SubNavDestination) 296 .navBarWidth(this.primaryWidth) 297 .attributeModifier(this.navWidthRangeModifier) 298 .onTouch((event) => { 299 if (event.type === TouchType.Down) { 300 hilog.info(0x0000, 'MultiNavigation', 'outer navigation this.outerStack.leftClickCount ' + 301 this.needRenderLeftClickCount.leftClickCount); 302 this.needRenderLeftClickCount.leftClickCount--; 303 } 304 }) 305 } 306 .onBackPressed(() => { 307 hilog.debug(0x0000, 'MultiNavigation', 'subNavigation NavDestination onBackPressed'); 308 if (this.multiStack && this.secondaryStack.size() === 1) { 309 hilog.info(0x0000, 'MultiNavigation', 'subNavigation NavDestination onBackPressed multiStack.pop'); 310 this.multiStack.pop(); 311 return true; 312 } 313 return false; 314 }) 315 .hideTitleBar(true) 316 } 317} 318 319export enum SplitPolicy { 320 HOME_PAGE = 0, 321 DETAIL_PAGE = 1, 322 FULL_PAGE = 2, 323 // PlACE_HOLDER_PAGE is not declared in SDK 324 PlACE_HOLDER_PAGE = 3, 325} 326 327let that: MultiNavigation; 328 329@Component 330export struct MultiNavigation { 331 private foldStatusCallback: Callback<display.FoldStatus> = (data: display.FoldStatus) => { 332 hilog.info(0x0000, 'MultiNavigation', 'foldStatusCallback data.valueOf()=' + data.valueOf()); 333 this.multiStack.needRenderDisplayMode.displayMode = data.valueOf(); 334 this.multiStack.handleRefreshPlaceHolderIfNeeded(); 335 }; 336 @State multiStack: MultiNavPathStack = new MultiNavPathStack(); 337 @BuilderParam navDestination: (name: string, param?: object) => void; 338 mode: NavigationMode | undefined = undefined; 339 onNavigationModeChangeCallback?: (mode: NavigationMode) => void = (mode: NavigationMode) => { 340 }; 341 onHomeShowOnTop?: OnHomeShowOnTopCallback = (name: string) => {}; 342 @State isPortrait: boolean = false; 343 344 @Builder 345 MultiNavDestination(name: string, param?: object) { 346 if (name === 'SubNavigation') { 347 SubNavigation({ 348 isPortrait: this.isPortrait, 349 multiStack: this.multiStack, 350 navDestination: this.navDestination, 351 primaryStack: (param as SubNavigationStack).primaryStack, 352 secondaryStack: (param as SubNavigationStack).secondaryStack, 353 needRenderIsFullScreen: (param as SubNavigationStack).needRenderIsFullScreen, 354 needRenderLeftClickCount: this.multiStack.needRenderLeftClickCount, 355 navWidthRangeModifier: this.multiStack.navWidthRangeModifier, 356 onNavigationModeChange: this?.callback, 357 needRenderDisplayMode: this.multiStack.needRenderDisplayMode, 358 }); 359 } else { 360 this.navDestination(name, param); 361 } 362 } 363 364 callback(mode: NavigationMode): void { 365 if (that.onNavigationModeChangeCallback !== undefined) { 366 if (mode !== that.mode || that.mode === undefined) { 367 that?.onNavigationModeChangeCallback(mode); 368 } 369 that.mode = mode; 370 } 371 } 372 373 aboutToAppear(): void { 374 that = this; 375 hilog.info(0x0000, 'MultiNavigation', 'MultiNavigation aboutToAppear'); 376 try { 377 display.on('foldStatusChange', this.foldStatusCallback); 378 } catch (exception) { 379 console.error('Failed to register callback. Code: ' + JSON.stringify(exception)); 380 } 381 DeviceListenerManager.getInstance().registerOrientationLister((isPortrait: boolean) => { 382 hilog.info(0x0000, 'MultiNavigation', 'MultiNavigation orientation change ' + isPortrait); 383 this.isPortrait = isPortrait; 384 this.multiStack.isPortrait = isPortrait; 385 this.multiStack.handleRefreshPlaceHolderIfNeeded(); 386 }); 387 DeviceListenerManager.getInstance().registerDrawableWidthLister((isLarge: boolean) => { 388 hilog.debug(0x0000, 'MultiNavigation', 'MultiNavigation Drawable width change ' + isLarge); 389 this.multiStack.isLarge = isLarge; 390 this.multiStack.handleRefreshPlaceHolderIfNeeded(); 391 }); 392 try { 393 this.multiStack.needRenderDisplayMode.displayMode = display.getFoldStatus(); 394 } catch (err) { 395 hilog.warn(0x0000, 'MultiNavigation', 'Failed to get fold status. error:' + JSON.stringify(err)); 396 } 397 DeviceListenerManager.getInstance().initListener(); 398 this.multiStack.registerHomeChangeListener({ 399 onHomeShowOnTop: (name) => { 400 this.onHomeShowOnTop?.(name); 401 }, 402 }) 403 } 404 405 aboutToDisappear(): void { 406 try { 407 display.off('foldStatusChange'); 408 } catch (exception) { 409 console.error('Failed to unregister callback. Code: ' + JSON.stringify(exception)); 410 } 411 DeviceListenerManager.getInstance().unregisterOrientationLister(); 412 DeviceListenerManager.getInstance().unregisterDrawableWidthLister(); 413 DeviceListenerManager.getInstance().finalizeListener(); 414 this.multiStack.unregisterHomeChangeListener(); 415 } 416 417 build() { 418 Navigation(this.multiStack.outerStack) { 419 } 420 .mode(NavigationMode.Stack) 421 .navDestination(this.MultiNavDestination) 422 .hideBackButton(true) 423 .hideTitleBar(true) 424 .hideToolBar(true) 425 .hideNavBar(true) 426 } 427} 428 429@Observed 430export class MultiNavPathStack extends NavPathStack { 431 outerStack: MyNavPathStack = new MyNavPathStack(); 432 totalStack: MultiNavPolicyInfo[] = []; 433 subStackList: Array<SubNavigationStack> = new Array<SubNavigationStack>(); 434 needRenderLeftClickCount: NeedRenderLeftClickCount = new NeedRenderLeftClickCount(); 435 needRenderDisplayMode: NeedRenderDisplayMode = new NeedRenderDisplayMode(); 436 disableAllAnimation: boolean = false; 437 private mPolicyMap = new Map<string, SplitPolicy>(); 438 navWidthRangeModifier: NavWidthRangeAttrModifier = new NavWidthRangeAttrModifier(); 439 homeWidthPercents: number[] = [50, 50]; 440 keepBottomPageFlag = false; 441 homeChangeListener: HomeChangeListener | undefined = undefined; 442 placeHolderPolicyInfo: MultiNavPolicyInfo | undefined = undefined; 443 isPortrait: boolean = false; 444 isLarge: boolean = false; 445 446 navPathStackOperate:MultiNavPathStackOperate = { 447 onPrimaryPop:() => { 448 hilog.info(0x0000, 'MultiNavigation', 'MyNavPathStack onPrimaryPop'); 449 this.totalStack.pop(); 450 this.subStackList.pop(); 451 this.outerStack.popInner(false); 452 }, 453 onSecondaryPop:() => { 454 hilog.info(0x0000, 'MultiNavigation', 'MyNavPathStack onSecondaryPop'); 455 this.totalStack.pop(); 456 this.checkAndNotifyHomeChange(); 457 } 458 }; 459 460 outerStackOperate: NavPathStackOperate = { 461 onSystemPop:() => { 462 hilog.info(0x0000, 'MultiNavigation', 'MyNavPathStack onOuterPop'); 463 this.totalStack.pop(); 464 this.subStackList.pop(); 465 this.checkAndNotifyHomeChange(); 466 } 467 }; 468 469 constructor() { 470 super(); 471 this.outerStack.registerStackOperateCallback(this.outerStackOperate); 472 } 473 474 pushPath(info: NavPathInfo, animated?: boolean, policy?: SplitPolicy): void; 475 pushPath(info: NavPathInfo, options?: NavigationOptions, policy?: SplitPolicy): void; 476 pushPath(info: NavPathInfo, optionParam?: boolean | NavigationOptions, policy?: SplitPolicy): void { 477 hilog.info(0x0000, 'MultiNavigation', 'pushPath policy = ' + policy + ', info.name = ' + info.name); 478 let animated: boolean = true; 479 if (optionParam !== undefined) { 480 if (typeof optionParam === 'boolean') { 481 animated = optionParam; 482 } else if (optionParam.animated !== undefined) { 483 animated = optionParam.animated; 484 } else { 485 486 } 487 } 488 policy = (policy === undefined) ? SplitPolicy.DETAIL_PAGE : policy; 489 const subStackLength = this.subStackList.length; 490 const multiPolicyStack = new MultiNavPolicyInfo(policy, info); 491 hilog.info(0x0000, 'MultiNavigation', 'pushPath subStackLength = ' + subStackLength); 492 if (subStackLength > 0) { 493 hilog.info(0x0000, 'MultiNavigation', 'pushPath currentTopPrimaryPolicy = ' + 494 this.subStackList[subStackLength - 1].getPrimaryPolicy()); 495 } 496 if (policy === SplitPolicy.DETAIL_PAGE && subStackLength > 0 && 497 this.subStackList[subStackLength - 1].getPrimaryPolicy() === SplitPolicy.HOME_PAGE) { 498 let detailSize = this.subStackList[subStackLength - 1].getSecondaryInfoList().length; 499 hilog.info(0x0000, 'MultiNavigation', 'pushPath detailSize = ' + detailSize ); 500 if (detailSize === 0) { 501 this.subStackList[subStackLength - 1].pushSecondaryPath(multiPolicyStack, animated); 502 } else { 503 if (this.needRenderLeftClickCount.leftClickCount > 0) { 504 // click on home, so we need to clear detail 505 if (this.placeHolderPolicyInfo === undefined) { 506 this.subStackList[subStackLength - 1].clearSecondary(false); 507 this.totalStack.splice(this.totalStack.length - detailSize); 508 this.subStackList[subStackLength - 1].pushSecondaryPath(multiPolicyStack, false); 509 } else { 510 const firstSecondaryPolicy = this.subStackList[subStackLength - 1].getSecondaryInfoList()[0].policy; 511 if (firstSecondaryPolicy === SplitPolicy.PlACE_HOLDER_PAGE) { 512 if (detailSize === 1 ) { 513 // detail has only place holder, so just push 514 this.subStackList[subStackLength - 1].pushSecondaryPath(multiPolicyStack, animated); 515 } else { 516 this.subStackList[subStackLength - 1].clearSecondaryKeepPlaceHolder(false); 517 this.totalStack.splice(this.totalStack.length - detailSize + 1); 518 this.subStackList[subStackLength - 1].pushSecondaryPath(multiPolicyStack, false); 519 } 520 } else { 521 this.subStackList[subStackLength - 1].clearSecondary(false); 522 this.totalStack.splice(this.totalStack.length - detailSize); 523 this.subStackList[subStackLength - 1].pushSecondaryPath(multiPolicyStack, false); 524 } 525 } 526 } else { 527 // click on detail, so just push 528 this.subStackList[subStackLength - 1].pushSecondaryPath(multiPolicyStack, animated); 529 } 530 } 531 } else { 532 let subStack = new SubNavigationStack(); 533 subStack.registerMultiStackOperateCallback(this.navPathStackOperate); 534 subStack.disableAnimation(this.disableAllAnimation); 535 subStack.pushPrimaryPath(multiPolicyStack, false); 536 this.subStackList.push(subStack); 537 this.outerStack.pushPath({ name: 'SubNavigation', param: subStack }, animated); 538 } 539 540 this.totalStack.push(multiPolicyStack); 541 if (policy === SplitPolicy.HOME_PAGE && this.placeHolderPolicyInfo !== undefined && 542 this.needShowPlaceHolder()) { 543 this.pushPlaceHolder(subStackLength); 544 } 545 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack pushPath policy = ' + policy + 546 ' stackSize = ' + this.totalStack.length + 547 ' this.leftClickCount = ' + this.needRenderLeftClickCount.leftClickCount); 548 this.needRenderLeftClickCount.leftClickCount = 0; 549 } 550 551 pushPathByName(name: string, param: Object, animated?: boolean, policy?: SplitPolicy): void; 552 pushPathByName(name: string, param: Object, onPop?: base.Callback<PopInfo>, animated?: boolean, 553 policy?: SplitPolicy): void; 554 pushPathByName(name: string, param: Object, onPop?: base.Callback<PopInfo> | boolean, 555 animated?: boolean | SplitPolicy, policy?: SplitPolicy): void { 556 if (onPop !== undefined && typeof onPop !== 'boolean') { 557 this.pushPath({ name: name, param: param, onPop: onPop as base.Callback<PopInfo> }, animated as boolean, policy); 558 return; 559 } 560 if (typeof onPop === 'boolean') { 561 this.pushPath({ name: name, param: param }, onPop as boolean, animated as SplitPolicy); 562 return; 563 } 564 // here onpop is undefined 565 if (animated !== undefined && typeof animated !== 'boolean') { 566 this.pushPath({ name: name, param: param}, undefined, animated as SplitPolicy); 567 return; 568 } 569 if (typeof animated === 'boolean') { 570 this.pushPath({ name: name, param: param}, animated as boolean, policy); 571 return; 572 } 573 this.pushPath({ name: name, param: param}, undefined, policy); 574 } 575 576 pushDestination(info: NavPathInfo, animated?: boolean, policy?: SplitPolicy): Promise<void>; 577 pushDestination(info: NavPathInfo, options?: NavigationOptions, policy?: SplitPolicy): Promise<void>; 578 pushDestination(info: NavPathInfo, animated?: boolean | NavigationOptions, policy?: SplitPolicy): Promise<void> { 579 hilog.error(0x0000, 'MultiNavigation', 'pushDestination is not support'); 580 let promise: Promise<void> = Promise.reject({message: 'not support'}); 581 return promise; 582 } 583 584 pushDestinationByName(name: string, param: Object, animated?: boolean, policy?: SplitPolicy): Promise<void>; 585 pushDestinationByName(name: string, param: Object, onPop: base.Callback<PopInfo>, animated?: boolean, 586 policy?: SplitPolicy): Promise<void>; 587 pushDestinationByName(name: string, param: Object, onPop?: boolean | base.Callback<PopInfo>, 588 animated?: boolean | SplitPolicy, policy?: SplitPolicy): Promise<void> { 589 hilog.error(0x0000, 'MultiNavigation', 'pushDestinationByName is not support'); 590 let promise: Promise<void> = Promise.reject({message: 'not support'}); 591 return promise; 592 } 593 594 replacePath(info: NavPathInfo, animated?: boolean): void; 595 replacePath(info: NavPathInfo, options?: NavigationOptions): void; 596 replacePath(info: NavPathInfo, optionParam?: boolean | NavigationOptions): void { 597 let animated: boolean = true; 598 if (optionParam !== undefined) { 599 if (typeof optionParam === 'boolean') { 600 animated = optionParam; 601 } else if (optionParam.animated !== undefined) { 602 animated = optionParam.animated; 603 } else { 604 605 } 606 } 607 608 let totalSize = this.totalStack.length; 609 let subStackSize = this.subStackList.length; 610 if (totalSize < 1 || subStackSize < 1) { 611 hilog.error(0x0000, 'MultiNavigation', 'replacePath fail stack is empty'); 612 return; 613 } 614 let currentTopPolicy = this.totalStack[totalSize - 1].policy; 615 if (currentTopPolicy === SplitPolicy.PlACE_HOLDER_PAGE) { 616 hilog.warn(0x0000, 'MultiNavigation', 'replacePath fail, not support replace placeHolder'); 617 return; 618 } 619 const newPolicyInfo = new MultiNavPolicyInfo(currentTopPolicy, info); 620 this.subStackList[subStackSize - 1].replacePath(newPolicyInfo, animated); 621 this.totalStack.pop(); 622 this.totalStack.push(newPolicyInfo); 623 } 624 625 replacePathByName(name: string, param: Object, animated?: boolean): void { 626 this.replacePath({ name: name, param: param }, animated); 627 } 628 629 removeByIndexes(indexes: number[]): number { 630 let indexesLength = indexes.length; 631 hilog.info(0x0000, 'MultiNavigation', 'removeByIndexes indexesLength=' + indexesLength); 632 if (indexesLength <= 0) { 633 return 0; 634 } 635 let oriStackSize = this.totalStack.length; 636 hilog.info(0x0000, 'MultiNavigation', 'removeByIndexes oriStackSize=' + oriStackSize); 637 indexes.sort((a, b) => a - b); 638 let i: number = 0; 639 let currentStackInfoLength: number = 0; 640 let outerIndexes: number[] = []; 641 hilog.info(0x0000, 'MultiNavigation', 'removeByIndexes this.subStackList.length=' + this.subStackList.length + 642 ', oriStackSize=' + oriStackSize); 643 this.subStackList.forEach((subStack, subStackIndex) => { 644 let stepStartIndex = currentStackInfoLength; 645 currentStackInfoLength += subStack.getAllInfoLength(); 646 const subIndexes: number[] = []; 647 for (; i < indexes.length; ) { 648 if (indexes[i] < currentStackInfoLength) { 649 subIndexes.push(indexes[i] - stepStartIndex); 650 i++; 651 } else { 652 break; 653 } 654 } 655 subStack.removeByIndexes(subIndexes); 656 if (!subStack.hasPrimaryInfo()) { 657 outerIndexes.push(subStackIndex); 658 } 659 }); 660 hilog.info(0x0000, 'MultiNavigation', 'removeByIndexes outerIndexes.length=' + outerIndexes.length); 661 this.outerStack.removeByIndexes(outerIndexes); 662 this.subStackList = this.subStackList.filter((subStack) => { 663 return subStack.hasPrimaryInfo() 664 }); 665 666 this.totalStack = []; 667 this.subStackList.forEach((subStack) => { 668 this.totalStack.push(...subStack.getPrimaryInfoList()); 669 this.totalStack.push(...subStack.getSecondaryInfoList()); 670 }) 671 this.handleRefreshPlaceHolderIfNeeded(); 672 this.checkAndNotifyHomeChange(); 673 let size = oriStackSize - this.totalStack.length; 674 hilog.info(0x0000, 'MultiNavigation', 'removeByIndexes size=' + size); 675 return size; 676 } 677 678 removeByName(name: string): number { 679 let oriStackSize = this.totalStack.length; 680 hilog.info(0x0000, 'MultiNavigation', 'removeByName name=' + name + ', oriStackSize=' + oriStackSize); 681 let outerIndexes: number[] = []; 682 this.subStackList.forEach((subStack, index) => { 683 subStack.removeByName(name); 684 if (!subStack.hasPrimaryInfo()) { 685 outerIndexes.push(index); 686 } 687 }); 688 this.outerStack.removeByIndexes(outerIndexes); 689 hilog.info(0x0000, 'MultiNavigation', 'removeByName outerIndexes.length=' + outerIndexes.length); 690 this.subStackList = this.subStackList.filter((subStack) => { 691 return subStack.hasPrimaryInfo() 692 }); 693 694 this.totalStack = []; 695 this.subStackList.forEach((subStack) => { 696 this.totalStack.push(...subStack.getPrimaryInfoList()); 697 this.totalStack.push(...subStack.getSecondaryInfoList()); 698 }) 699 this.handleRefreshPlaceHolderIfNeeded(); 700 this.checkAndNotifyHomeChange(); 701 let size = oriStackSize - this.totalStack.length; 702 hilog.info(0x0000, 'MultiNavigation', 'removeByName size=' + size); 703 return size; 704 } 705 706 707 pop(animated?: boolean): NavPathInfo | undefined; 708 pop(result?: Object, animated?: boolean): NavPathInfo | undefined; 709 pop(result?: Object | boolean, animated?: boolean): NavPathInfo | undefined { 710 let totalSize = this.totalStack.length; 711 let subStackLength = this.subStackList.length; 712 if (totalSize < 1 || subStackLength < 1) { 713 hilog.error(0x0000, 'MultiNavigation', 'MultiNavPathStack pop fail stack is empty!'); 714 return undefined; 715 } 716 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack pop totalSize=' + totalSize + 717 ', subStackLength' + subStackLength); 718 719 if (this.keepBottomPageFlag && (totalSize === 1 || 720 (this.placeHolderPolicyInfo !== undefined && totalSize === 2 && 721 this.totalStack[1].policy === SplitPolicy.PlACE_HOLDER_PAGE))) { 722 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack pop fail for keep bottom'); 723 return undefined; 724 } 725 let currentPath = this.totalStack[totalSize - 1].navInfo; 726 let allInfoLength = this.subStackList[subStackLength - 1].getAllInfoLength(); 727 if (allInfoLength < 1) { 728 hilog.error(0x0000, 'MultiNavigation', 'MultiNavPathStack pop fail sub stack is empty'); 729 return undefined; 730 } 731 let secondaryStackFirstPolice: SplitPolicy | undefined = undefined; 732 if (allInfoLength > 1) { 733 secondaryStackFirstPolice = this.subStackList[subStackLength - 1].getSecondaryInfoList()[0].policy; 734 } 735 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack pop allInfoLength=' + allInfoLength + 736 ', secondaryStackFirstPolice' + secondaryStackFirstPolice); 737 this.totalStack.pop(); 738 if (allInfoLength === 1) { 739 // pop home 740 this.outerStack.popInner(animated); 741 let subStack = this.subStackList.pop(); 742 setTimeout(() => { 743 subStack?.pop(false); 744 subStack = undefined; 745 }, 300); 746 } else { 747 if (allInfoLength === 2) { 748 if (this.placeHolderPolicyInfo !== undefined) { 749 if (secondaryStackFirstPolice === SplitPolicy.PlACE_HOLDER_PAGE) { 750 this.outerStack.popInner(animated); 751 let subStack = this.subStackList.pop(); 752 setTimeout(() => { 753 subStack?.clear(false); 754 subStack = undefined; 755 }, 300); 756 currentPath = this.totalStack.pop()?.navInfo; 757 } else { 758 if (this.needShowPlaceHolder()) { 759 this.subStackList[subStackLength - 1].pop(animated); 760 this.pushPlaceHolder(subStackLength - 1) 761 } else { 762 this.subStackList[subStackLength - 1].pop(animated); 763 } 764 } 765 } else { 766 this.subStackList[subStackLength - 1].pop(animated); 767 } 768 } else { 769 this.subStackList[subStackLength - 1].pop(animated); 770 } 771 } 772 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack pop currentPath.name = ' + currentPath?.name); 773 774 if (result !== undefined && typeof result !== 'boolean' && 775 currentPath !== undefined && currentPath.onPop !== undefined) { 776 let popInfo: PopInfo = { 777 info: currentPath, 778 result: result, 779 }; 780 currentPath.onPop(popInfo); 781 } 782 this.handleRefreshPlaceHolderIfNeeded(); 783 this.checkAndNotifyHomeChange(); 784 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack pop stackSize = ' + this.totalStack.length); 785 return currentPath; 786 } 787 788 popToName(name: string, animated?: boolean): number; 789 popToName(name: string, result: Object, animated?: boolean): number; 790 popToName(name: string, result?: Object | boolean, animated?: boolean): number { 791 let index = this.totalStack.findIndex((value: MultiNavPolicyInfo) => { 792 return value.navInfo?.name === name; 793 }) 794 let totalSize = this.totalStack.length; 795 let subStackLength = this.subStackList.length; 796 if (totalSize < 1 || subStackLength < 1) { 797 hilog.error(0x0000, 'MultiNavigation', 'popToName fail stack is empty!'); 798 return -1; 799 } 800 if (index !== -1) { 801 let currentPath = this.totalStack[totalSize - 1].navInfo; 802 let secondaryStackSize: number[] = []; 803 this.subStackList.forEach((subStack, index) => { 804 secondaryStackSize.push(this.subStackList[index].secondaryStack.size()); 805 }); 806 let removeIndex = 0; 807 for (let i = 0; i < subStackLength; i++) { 808 removeIndex++; 809 if (index === removeIndex - 1) { 810 this.subStackList[i]?.secondaryStack.clear(); 811 this.subStackList[i].secondaryStack.policyInfoList.splice(0); 812 this.totalStack.splice(index + 1); 813 this.clearTrashStack(i + 1, result, animated); 814 break; 815 } else if (index > removeIndex - 1 && index < removeIndex + secondaryStackSize[i]) { 816 this.subStackList[i].secondaryStack.popToIndex(index - removeIndex); 817 this.subStackList[i].secondaryStack.policyInfoList.splice(index - removeIndex + 1); 818 this.totalStack.splice(index + 1); 819 this.clearTrashStack(i + 1, result, animated); 820 } 821 removeIndex += secondaryStackSize[i]; 822 } 823 if (result !== undefined && typeof result !== 'boolean' && 824 currentPath !== undefined && currentPath.onPop !== undefined) { 825 let popInfo: PopInfo = { 826 info: currentPath, 827 result: result, 828 }; 829 currentPath.onPop(popInfo); 830 } 831 } 832 this.handleRefreshPlaceHolderIfNeeded(); 833 this.checkAndNotifyHomeChange(); 834 return index; 835 } 836 837 popToIndex(index: number, animated?: boolean): void; 838 popToIndex(index: number, result: Object, animated?: boolean): void; 839 popToIndex(index: number, result?: Object | boolean, animated?: boolean): void { 840 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack popToIndex index = ' + index); 841 if (index > this.totalStack.length || index < 0) { 842 hilog.error(0x0000, 'MultiNavigation', 'popToIndex fail wrong index'); 843 return; 844 } 845 let totalSize = this.totalStack.length; 846 let subStackLength = this.subStackList.length; 847 if (totalSize < 1 || subStackLength < 1) { 848 hilog.error(0x0000, 'MultiNavigation', 'popToIndex fail stack is empty!'); 849 return; 850 } 851 let currentPath = this.totalStack[totalSize - 1].navInfo; 852 let secondaryStackSize: number[] = []; 853 this.subStackList.forEach((subStack, index) => { 854 secondaryStackSize.push(this.subStackList[index].secondaryStack.size()); 855 }); 856 let removeIndex = 0; 857 for (let i = 0; i < subStackLength; i++) { 858 removeIndex++; 859 if (index === removeIndex - 1) { 860 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack popToIndex home' + i); 861 this.subStackList[i]?.secondaryStack.clear(); 862 this.subStackList[i].secondaryStack.policyInfoList.splice(0); 863 this.totalStack.splice(index + 1); 864 this.clearTrashStack(i + 1, result, animated); 865 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack popToIndex totalStack=' + this.totalStack.length); 866 break; 867 } else if (index > removeIndex - 1 && index < removeIndex + secondaryStackSize[i]) { 868 this.subStackList[i].secondaryStack.popToIndex(index - removeIndex); 869 this.subStackList[i].secondaryStack.policyInfoList.splice(index - removeIndex + 1); 870 this.totalStack.splice(index + 1); 871 this.clearTrashStack(i + 1, result, animated); 872 } 873 removeIndex += secondaryStackSize[i]; 874 } 875 876 if (result !== undefined && typeof result !== 'boolean' && 877 currentPath !== undefined && currentPath.onPop !== undefined) { 878 let popInfo: PopInfo = { 879 info: currentPath, 880 result: result, 881 }; 882 currentPath.onPop(popInfo); 883 } 884 this.handleRefreshPlaceHolderIfNeeded(); 885 this.checkAndNotifyHomeChange(); 886 } 887 888 private clearTrashStack(index: number, result?: Object | boolean, animated?: boolean): void { 889 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack popToIndex clearTrashStack' + index); 890 for (let i = index; i < this.subStackList.length; i++) { 891 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack popToIndex subStackList' + index); 892 this.subStackList[i].primaryStack.clear(); 893 this.subStackList[i].secondaryStack.clear(); 894 this.subStackList[i].primaryStack.policyInfoList.splice(0); 895 this.subStackList[i].secondaryStack.policyInfoList.splice(0); 896 } 897 this.subStackList.splice(index); 898 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack popToIndex subStackList.length=' + this.subStackList.length); 899 this.outerStack.popToIndex(index - 1, result, animated); 900 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack popToIndex outerStack.size=' + this.outerStack.size()); 901 } 902 903 moveToTop(name: string, animated?: boolean): number { 904 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack moveToTop name=' + name); 905 let index = this.totalStack.findIndex((value) => { 906 return value.navInfo?.name === name; 907 }); 908 if (index !== -1) { 909 this.moveIndexToTop(index, animated); 910 } 911 912 return index; 913 } 914 915 moveIndexToTop(index: number, animated?: boolean): void { 916 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack moveIndexToTop index=' + index); 917 if (index < 0 || index > this.totalStack.length) { 918 hilog.error(0x0000, 'MultiNavigation', 'MultiNavPathStack moveIndexToTop wrong index'); 919 return; 920 } 921 let subStackLength = this.subStackList.length; 922 let currentStackInfoLength: number = 0; 923 let outerIndex: number = -1; 924 for (let subIndex = 0; subIndex < subStackLength; subIndex++) { 925 let stepStartIndex = currentStackInfoLength; 926 currentStackInfoLength += this.subStackList[subIndex].getAllInfoLength(); 927 if (index < currentStackInfoLength) { 928 outerIndex = subIndex; 929 if (this.subStackList[subIndex].getPrimaryPolicy() === SplitPolicy.HOME_PAGE) { 930 let innerIndex = index - stepStartIndex; 931 if (innerIndex !== 0) { 932 this.subStackList[subIndex].secondaryStack.moveIndexToTop(innerIndex - 1, animated); 933 const subInfo = this.subStackList[subIndex].secondaryStack.policyInfoList.splice(innerIndex - 1, 1); 934 this.subStackList[subIndex].secondaryStack.policyInfoList.push(...subInfo); 935 } 936 } 937 break; 938 } 939 } 940 if (outerIndex !== -1) { 941 let subStack = this.subStackList.splice(outerIndex, 1); 942 this.subStackList.push(...subStack); 943 this.outerStack.moveIndexToTop(outerIndex, animated); 944 } 945 946 this.totalStack = []; 947 this.subStackList.forEach((subStack) => { 948 this.totalStack.push(...subStack.getPrimaryInfoList()); 949 this.totalStack.push(...subStack.getSecondaryInfoList()); 950 }); 951 this.handleRefreshPlaceHolderIfNeeded(); 952 this.checkAndNotifyHomeChange(); 953 } 954 955 clear(animated?: boolean): void { 956 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack clear animated = ' + animated + ', keepBottomPage=' + 957 this.keepBottomPageFlag); 958 959 if (this.subStackList.length === 0 || this.totalStack.length === 0) { 960 hilog.info(0x0000, 'MultiNavigation', 'MultiNavPathStack clear return size is 0'); 961 return; 962 } 963 if (this.keepBottomPageFlag) { 964 let subStackLength = this.subStackList.length; 965 for (let i = 1; i < subStackLength; i++) { 966 this.subStackList[i].clear(animated); 967 } 968 this.outerStack.popToIndex(0, animated); 969 this.subStackList.splice(1); 970 if (this.placeHolderPolicyInfo !== undefined) { 971 if (this.subStackList[0].getSecondaryInfoList().length > 1 && 972 this.subStackList[0].secondaryStack.policyInfoList[0].policy === SplitPolicy.PlACE_HOLDER_PAGE) { 973 this.subStackList[0].clearSecondaryKeepPlaceHolder(animated); 974 this.totalStack.splice(2); 975 } else { 976 this.subStackList[0].clearSecondary(animated); 977 this.totalStack.splice(1); 978 if (this.needShowPlaceHolder()) { 979 this.subStackList[0].pushSecondaryPath(this.placeHolderPolicyInfo, animated); 980 this.totalStack.push(this.placeHolderPolicyInfo); 981 } 982 } 983 } else { 984 this.subStackList[0].clearSecondary(animated); 985 this.totalStack.splice(1); 986 } 987 988 this.checkAndNotifyHomeChange(); 989 return; 990 } 991 this.subStackList.forEach((subStack) => { 992 subStack.clear(animated); 993 }) 994 this.outerStack.clear(animated); 995 this.subStackList.splice(0); 996 this.totalStack.splice(0) 997 } 998 999 getAllPathName(): string[] { 1000 let result: string[] = []; 1001 this.totalStack.forEach((value) => { 1002 if (value.navInfo !== undefined) { 1003 result.push(value.navInfo.name); 1004 } 1005 }) 1006 return result; 1007 } 1008 1009 getParamByIndex(index: number): Object | undefined { 1010 let result: Object | undefined = undefined; 1011 if (index >= 0 && index < this.totalStack.length) { 1012 result = this.totalStack[index].navInfo?.param as Object; 1013 } 1014 return result; 1015 } 1016 1017 getParamByName(name: string): Object[] { 1018 let result: Object[] = []; 1019 this.totalStack.forEach((value) => { 1020 if (value.navInfo !== undefined && value.navInfo.name == name) { 1021 result.push(value.navInfo.param as Object); 1022 } 1023 }) 1024 return result; 1025 } 1026 1027 getIndexByName(name: string): number[] { 1028 let result: number[] = []; 1029 for (let i = 0; i < this.totalStack.length; i++) { 1030 if (this.totalStack[i].navInfo?.name === name) { 1031 result.push(i); 1032 } 1033 } 1034 return result; 1035 } 1036 1037 getParent(): NavPathStack { 1038 hilog.error(0x0000, 'MultiNavigation', 'getParent is not support!'); 1039 throw new Error('getParent is not support in multi navigation'); 1040 } 1041 1042 size(): number { 1043 return this.totalStack.length; 1044 } 1045 1046 disableAnimation(value: boolean): void { 1047 for (const subStack of this.subStackList) { 1048 subStack.disableAnimation(value); 1049 } 1050 this.outerStack.disableAnimation(value); 1051 this.disableAllAnimation = value; 1052 } 1053 1054 setInterception(interception: NavigationInterception): void { 1055 hilog.error(0x0000, 'MultiNavigation', 'setInterception is not support!'); 1056 throw new Error('setInterception is not support in multi navigation'); 1057 } 1058 1059 setPagePolicy(policyMap: Map<string, SplitPolicy>): void { 1060 this.mPolicyMap = policyMap; 1061 } 1062 1063 switchFullScreenState(isFullScreen?: boolean): boolean { 1064 let totalStackSize = this.totalStack.length; 1065 let subStackListLength = this.subStackList.length; 1066 if (subStackListLength < 1 || totalStackSize < 1) { 1067 return false; 1068 } 1069 if (this.subStackList[subStackListLength - 1].getPrimaryPolicy() !== SplitPolicy.HOME_PAGE) { 1070 return false; 1071 } 1072 if (this.totalStack[totalStackSize - 1].policy === SplitPolicy.PlACE_HOLDER_PAGE) { 1073 return false; 1074 } 1075 if (this.totalStack[totalStackSize - 1].isFullScreen === isFullScreen) { 1076 hilog.info(0x0000, 'MultiNavigation', 'switchFullScreen is same:' + isFullScreen); 1077 return true; 1078 } 1079 hilog.info(0x0000, 'MultiNavigation', 'switchFullScreen name=' + 1080 this.totalStack[totalStackSize - 1].navInfo?.name + 1081 ', from ' + this.totalStack[totalStackSize - 1].isFullScreen + ' to ' + isFullScreen); 1082 this.totalStack[totalStackSize - 1].isFullScreen = isFullScreen; 1083 this.subStackList[subStackListLength - 1].refreshFullScreen(); 1084 return true; 1085 } 1086 1087 setHomeWidthRange(minPercent: number, maxPercent: number): void { 1088 if (!this.checkInputPercent(minPercent) || !this.checkInputPercent(maxPercent)) { 1089 hilog.error(0x0000, 'MultiNavigation', 'setHomeWidthRange failed, wrong param:' + 1090 ', ' + minPercent + ', ' + maxPercent) 1091 return; 1092 } 1093 this.homeWidthPercents = [minPercent, maxPercent]; 1094 this.refreshHomeWidth(); 1095 } 1096 1097 keepBottomPage(keepBottom: boolean): void { 1098 this.keepBottomPageFlag = keepBottom; 1099 } 1100 1101 registerHomeChangeListener(lister: HomeChangeListener): void { 1102 if (this.homeChangeListener === undefined) { 1103 this.homeChangeListener = lister; 1104 } 1105 } 1106 1107 unregisterHomeChangeListener(): void { 1108 this.homeChangeListener = undefined; 1109 } 1110 1111 setPlaceholderPage(info: NavPathInfo): void { 1112 this.placeHolderPolicyInfo = new MultiNavPolicyInfo(SplitPolicy.PlACE_HOLDER_PAGE, info); 1113 } 1114 1115 handleRefreshPlaceHolderIfNeeded() { 1116 if (this.placeHolderPolicyInfo === undefined) { 1117 return; 1118 } 1119 const subStackListLength = this.subStackList.length; 1120 if (subStackListLength < 1) { 1121 return; 1122 } 1123 const topStackPrimaryPolicy = this.subStackList[subStackListLength - 1].getPrimaryPolicy(); 1124 if (topStackPrimaryPolicy !== SplitPolicy.HOME_PAGE) { 1125 return; 1126 } 1127 const subStackAllInfoLength = this.subStackList[subStackListLength - 1].getAllInfoLength(); 1128 let secondaryStackFirstPolice: SplitPolicy | undefined = undefined; 1129 if (subStackAllInfoLength > 1) { 1130 secondaryStackFirstPolice = this.subStackList[subStackListLength - 1].getSecondaryInfoList()[0].policy; 1131 } 1132 if (this.needShowPlaceHolder()) { 1133 if (subStackAllInfoLength === 1) { 1134 this.pushPlaceHolder(subStackListLength - 1); 1135 } 1136 } else { 1137 if (secondaryStackFirstPolice === SplitPolicy.PlACE_HOLDER_PAGE) { 1138 if (subStackAllInfoLength === 2) { 1139 this.popPlaceHolder(subStackListLength - 1); 1140 } else { 1141 this.removeFirstPlaceHolder(subStackListLength - 1); 1142 } 1143 } 1144 } 1145 } 1146 1147 private removeFirstPlaceHolder(subIndex: number): void { 1148 this.subStackList[subIndex].removeByIndexes([1]); 1149 this.totalStack = []; 1150 this.subStackList.forEach((subStack) => { 1151 this.totalStack.push(...subStack.getPrimaryInfoList()); 1152 this.totalStack.push(...subStack.getSecondaryInfoList()); 1153 }) 1154 } 1155 1156 private pushPlaceHolder(subIndex: number): void { 1157 this.subStackList[subIndex].pushSecondaryPath(this.placeHolderPolicyInfo!, false); 1158 this.totalStack.push(this.placeHolderPolicyInfo!); 1159 } 1160 1161 private popPlaceHolder(subIndex: number): void { 1162 this.subStackList[subIndex].pop(false); 1163 this.totalStack.pop(); 1164 this.checkAndNotifyHomeChange(); 1165 } 1166 1167 private needShowPlaceHolder(): boolean { 1168 if (!this.isLarge) { 1169 hilog.info(0x0000, 'MultiNavigation', 'do not show placeHolder for drawable width is less then breakpoint'); 1170 return false; 1171 } 1172 if (DeviceHelper.isStraightProduct()) { 1173 hilog.info(0x0000, 'MultiNavigation', 'do not show placeHolder for straight product'); 1174 return false; 1175 } 1176 if (DeviceHelper.isPhone() && DeviceHelper.isFold() && 1177 this.needRenderDisplayMode.displayMode === display.FoldStatus.FOLD_STATUS_FOLDED) { 1178 hilog.info(0x0000, 'MultiNavigation', 'do not show placeHolder for fold status'); 1179 return false; 1180 } 1181 if (DeviceHelper.isTablet() && this.isPortrait) { 1182 hilog.info(0x0000, 'MultiNavigation', 'do not show placeHolder for portrait tablet'); 1183 return false; 1184 } 1185 return true; 1186 } 1187 1188 private checkAndNotifyHomeChange(): void { 1189 if (this.totalStack.length === 0) { 1190 return; 1191 } 1192 let topPolicyInfo = this.totalStack[this.totalStack.length - 1]; 1193 if (topPolicyInfo === undefined) { 1194 return; 1195 } 1196 if (topPolicyInfo.policy === SplitPolicy.HOME_PAGE && topPolicyInfo.navInfo !== undefined) { 1197 this.homeChangeListener && this.homeChangeListener.onHomeShowOnTop(topPolicyInfo.navInfo.name); 1198 } 1199 if (this.totalStack.length <= 1) { 1200 return; 1201 } 1202 let secondPolicyInfo = this.totalStack[this.totalStack.length - 2]; 1203 if (secondPolicyInfo === undefined) { 1204 return; 1205 } 1206 if (topPolicyInfo.policy === SplitPolicy.PlACE_HOLDER_PAGE && 1207 secondPolicyInfo.policy === SplitPolicy.HOME_PAGE && secondPolicyInfo.navInfo !== undefined) { 1208 this.homeChangeListener && this.homeChangeListener.onHomeShowOnTop(secondPolicyInfo.navInfo.name); 1209 } 1210 } 1211 1212 private refreshHomeWidth(): void { 1213 this.navWidthRangeModifier.minHomeWidth = `${this.homeWidthPercents[0]}%`; 1214 this.navWidthRangeModifier.maxHomeWidth = `${this.homeWidthPercents[1]}%`; 1215 this.navWidthRangeModifier.isApplicationSet = true; 1216 } 1217 1218 private checkInputPercent(inputPercent: number): boolean { 1219 return (0 <= inputPercent && inputPercent <= 100); 1220 } 1221} 1222 1223interface HomeChangeListener { 1224 onHomeShowOnTop: OnHomeShowOnTopCallback; 1225} 1226 1227@Observed 1228export class NeedRenderIsFullScreen { 1229 isFullScreen: boolean | undefined = undefined; 1230} 1231 1232@Observed 1233export class NeedRenderLeftClickCount { 1234 leftClickCount: number = 0; 1235} 1236 1237@Observed 1238export class NeedRenderDisplayMode { 1239 displayMode: number = 0; 1240} 1241 1242class MultiNavPolicyInfo { 1243 policy: SplitPolicy = SplitPolicy.DETAIL_PAGE; 1244 navInfo: NavPathInfo | undefined = undefined; 1245 isFullScreen: boolean | undefined = undefined; 1246 1247 constructor(policy: SplitPolicy, navInfo: NavPathInfo) { 1248 this.policy = policy; 1249 this.navInfo = navInfo; 1250 } 1251} 1252 1253 1254export class MyNavPathStack extends NavPathStack { 1255 operates:NavPathStackOperate[] = []; 1256 type = 'NavPathStack'; 1257 policyInfoList: MultiNavPolicyInfo[] = []; 1258 1259 registerStackOperateCallback(operate: NavPathStackOperate) { 1260 let index = this.operates.findIndex((item) => { return item === operate}); 1261 if (index === -1) { 1262 this.operates.push(operate); 1263 } 1264 } 1265 1266 unregisterStackOperateCallback(operate: NavPathStackOperate) { 1267 let index = this.operates.findIndex((item) => { return item === operate}); 1268 if (index !== -1) { 1269 this.operates.splice(index, 1); 1270 } 1271 } 1272 1273 popInner(result?: Object | boolean, animated?: boolean): NavPathInfo | undefined { 1274 hilog.info(0x0000, 'MultiNavigation', 'MyNavPathStack pop from inner:'); 1275 return super.pop(result, animated); 1276 } 1277 1278 1279 pop(animated?: boolean): NavPathInfo | undefined 1280 pop(result?: Object, animated?: boolean): NavPathInfo | undefined 1281 pop(result?: Object | boolean, animated?: boolean): NavPathInfo | undefined { 1282 hilog.info(0x0000, 'MultiNavigation', 'MyNavPathStack pop from system:'); 1283 let ret: NavPathInfo | undefined = undefined; 1284 if (typeof animated === 'boolean') { 1285 ret = super.pop(animated); 1286 } else { 1287 ret = super.pop(result, animated); 1288 } 1289 this.policyInfoList.pop(); 1290 this.operates.forEach((item) => { 1291 item.onSystemPop?.(); 1292 }) 1293 return ret; 1294 } 1295} 1296 1297interface NavPathStackOperate { 1298 onSystemPop: Function; 1299} 1300 1301interface MultiNavPathStackOperate { 1302 onPrimaryPop: Function; 1303 onSecondaryPop: Function; 1304} 1305 1306 1307class SubNavigationStack { 1308 primaryStack: MyNavPathStack = new MyNavPathStack(); 1309 secondaryStack: MyNavPathStack = new MyNavPathStack(); 1310 needRenderIsFullScreen: NeedRenderIsFullScreen = new NeedRenderIsFullScreen(); 1311 multiOperates:MultiNavPathStackOperate[] = []; 1312 1313 primaryNavPathStackOperate:NavPathStackOperate = { 1314 onSystemPop:() => { 1315 this.multiOperates.forEach((item) => { 1316 item.onPrimaryPop?.(); 1317 }) 1318 } 1319 } 1320 1321 secondaryNavPathStackOperate:NavPathStackOperate = { 1322 onSystemPop:() => { 1323 this.multiOperates.forEach((item) => { 1324 item.onSecondaryPop?.(); 1325 }) 1326 this.refreshFullScreen(); 1327 } 1328 } 1329 1330 constructor() { 1331 this.primaryStack.registerStackOperateCallback(this.primaryNavPathStackOperate); 1332 this.secondaryStack.registerStackOperateCallback(this.secondaryNavPathStackOperate); 1333 } 1334 1335 registerMultiStackOperateCallback(operate: MultiNavPathStackOperate) { 1336 let index = this.multiOperates.findIndex((item) => { return item === operate}); 1337 if (index === -1) { 1338 this.multiOperates.push(operate); 1339 } 1340 } 1341 1342 unregisterMultiStackOperateCallback(operate: MultiNavPathStackOperate) { 1343 let index = this.multiOperates.findIndex((item) => { return item === operate}); 1344 if (index !== -1) { 1345 this.multiOperates.splice(index, 1); 1346 } 1347 } 1348 1349 getPrimaryPolicy(): SplitPolicy | undefined { 1350 if (this.primaryStack.policyInfoList.length < 1) { 1351 return undefined; 1352 } 1353 return this.primaryStack.policyInfoList[0].policy; 1354 } 1355 1356 getPrimaryInfoList(): MultiNavPolicyInfo[] { 1357 return this.primaryStack.policyInfoList.slice(); 1358 } 1359 1360 getSecondaryInfoList(): MultiNavPolicyInfo[] { 1361 return this.secondaryStack.policyInfoList.slice(); 1362 } 1363 1364 getAllInfoLength(): number { 1365 return this.primaryStack.size() + this.secondaryStack.size(); 1366 } 1367 1368 hasPrimaryInfo(): boolean { 1369 return this.primaryStack.size() !== 0; 1370 } 1371 1372 hasSecondaryInfo(): boolean { 1373 return this.secondaryStack.size() !== 0; 1374 } 1375 1376 pushPrimaryPath(policyStack: MultiNavPolicyInfo, animated?: boolean) { 1377 this.primaryStack.policyInfoList.push(policyStack); 1378 this.primaryStack.pushPath(policyStack.navInfo, animated); 1379 this.refreshFullScreen(); 1380 } 1381 1382 pushSecondaryPath(policyStack: MultiNavPolicyInfo, animated?: boolean) { 1383 this.secondaryStack.policyInfoList.push(policyStack); 1384 this.secondaryStack.pushPath(policyStack.navInfo, animated); 1385 this.refreshFullScreen(); 1386 } 1387 1388 removeByIndexes(indexes: number[]): void { 1389 if (indexes.length < 1) { 1390 return; 1391 } 1392 if (indexes[0] === 0) { 1393 hilog.info(0x0000, 'MultiNavigation', 'SubNavigationStack removeByIndexes primaryStack'); 1394 this.primaryStack.removeByIndexes([0]); 1395 this.primaryStack.policyInfoList.pop(); 1396 this.clear(false); 1397 return; 1398 } 1399 if (indexes.length !== 0) { 1400 let slaveIndexes: number[] = []; 1401 indexes.forEach((value: number) => { 1402 slaveIndexes.push(value - 1); 1403 }); 1404 this.secondaryStack.removeByIndexes(slaveIndexes); 1405 this.secondaryStack.policyInfoList = this.secondaryStack.policyInfoList.filter((value, index) => { 1406 return value && !slaveIndexes.includes(index); 1407 }) 1408 } 1409 this.refreshFullScreen(); 1410 } 1411 1412 removeByName(name: string): void { 1413 this.primaryStack.removeByName(name); 1414 this.primaryStack.policyInfoList = this.primaryStack.policyInfoList.filter((value) => { 1415 return value.navInfo?.name !== name 1416 }); 1417 if (!this.hasPrimaryInfo()) { 1418 this.clear(false); 1419 return; 1420 } 1421 this.secondaryStack.removeByName(name); 1422 this.secondaryStack.policyInfoList = this.secondaryStack.policyInfoList.filter((value) => { 1423 return value.navInfo?.name !== name 1424 }); 1425 this.refreshFullScreen(); 1426 } 1427 1428 pop(result?: Object | boolean, animated?: boolean): NavPathInfo | undefined { 1429 let ret: NavPathInfo | undefined = undefined 1430 if (this.secondaryStack.policyInfoList.length > 0) { 1431 ret = this.popSecondary(result, animated); 1432 } else { 1433 ret = this.popPrimary(result, animated); 1434 } 1435 this.refreshFullScreen(); 1436 return ret; 1437 } 1438 1439 clearSecondary(animated?: boolean) { 1440 this.secondaryStack.clear(animated); 1441 this.secondaryStack.policyInfoList.splice(0); 1442 this.refreshFullScreen(); 1443 } 1444 1445 clearSecondaryKeepPlaceHolder(animated?: boolean) { 1446 this.secondaryStack.popToIndex(0, animated); 1447 this.secondaryStack.policyInfoList.splice(1); 1448 this.refreshFullScreen(); 1449 } 1450 1451 clear(animated?: boolean) { 1452 this.secondaryStack.clear(animated); 1453 this.primaryStack.clear(animated); 1454 this.secondaryStack.policyInfoList.splice(0); 1455 this.primaryStack.policyInfoList.splice(0); 1456 } 1457 1458 disableAnimation(value: boolean): void { 1459 this.primaryStack.disableAnimation(value); 1460 this.secondaryStack.disableAnimation(value); 1461 } 1462 1463 replacePath(info: MultiNavPolicyInfo, animated?: boolean): void { 1464 if (this.secondaryStack.policyInfoList.length > 0) { 1465 this.replaceSecond(info, animated); 1466 } else { 1467 this.replacePrimary(info, animated); 1468 } 1469 this.refreshFullScreen(); 1470 } 1471 1472 refreshFullScreen() { 1473 let secondInfoListLength = this.secondaryStack.policyInfoList.length 1474 if (secondInfoListLength > 0) { 1475 this.needRenderIsFullScreen.isFullScreen = 1476 this.secondaryStack.policyInfoList[secondInfoListLength - 1].isFullScreen; 1477 return; 1478 } 1479 let primaryInfoListLength = this.primaryStack.policyInfoList.length 1480 if (primaryInfoListLength > 0) { 1481 this.needRenderIsFullScreen.isFullScreen = 1482 this.primaryStack.policyInfoList[primaryInfoListLength - 1].isFullScreen; 1483 } 1484 } 1485 1486 private replacePrimary(info: MultiNavPolicyInfo, animated?: boolean): void { 1487 this.primaryStack.policyInfoList.pop(); 1488 this.primaryStack.policyInfoList.push(info) 1489 return this.primaryStack.replacePath(info.navInfo, animated); 1490 } 1491 1492 private replaceSecond(info: MultiNavPolicyInfo, animated?: boolean): void { 1493 this.secondaryStack.policyInfoList.pop(); 1494 this.secondaryStack.policyInfoList.push(info) 1495 return this.secondaryStack.replacePath(info.navInfo, animated); 1496 } 1497 1498 private popPrimary(result?: Object | boolean, animated?: boolean): NavPathInfo | undefined { 1499 this.primaryStack.policyInfoList.pop(); 1500 return this.primaryStack.popInner(result, animated); 1501 } 1502 1503 private popSecondary(result?: Object | boolean, animated?: boolean): NavPathInfo | undefined { 1504 this.secondaryStack.policyInfoList.pop(); 1505 return this.secondaryStack.popInner(result, animated); 1506 } 1507} 1508 1509declare type NavDestinationBuildFunction = (name: string, param?: object) => void; 1510 1511declare type OnNavigationModeChangeCallback = (mode: NavigationMode) => void; 1512 1513declare type OnHomeShowOnTopCallback = (name: string) => void;