• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;