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