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;