mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat(frame): handle back navigation when common layout is used as a root element (#5608)
* test(e2e): update modal navigation app Add layout as root. Add show modal layout. * chore(frame): move frame stack modifiers in a separate frame-stack module * feat(frame): handle back navigation when using common layout as root element
This commit is contained in:
committed by
Svetoslav
parent
8a1958e82e
commit
70f01123df
@@ -263,7 +263,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
that._closeModalCallback = null;
|
||||
that._dialogClosed();
|
||||
parent._modal = null;
|
||||
|
||||
|
||||
if (typeof closeCallback === "function") {
|
||||
closeCallback.apply(undefined, arguments);
|
||||
}
|
||||
@@ -976,6 +976,18 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
_onDetachedFromWindow(): void {
|
||||
//
|
||||
}
|
||||
|
||||
_hasAncestorView(ancestorView: ViewDefinition): boolean {
|
||||
let matcher = (view: ViewDefinition) => view === ancestorView;
|
||||
|
||||
for (let parent = this.parent; parent != null; parent = parent.parent) {
|
||||
if (matcher(<ViewDefinition>parent)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const automationTextProperty = new Property<ViewCommon, string>({ name: "automationText" });
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
|
||||
import { Background, ad as androidBackground } from "../../styling/background";
|
||||
import { profile } from "../../../profiling";
|
||||
import { topmost } from "../../frame/frame-stack";
|
||||
|
||||
export * from "./view-common";
|
||||
|
||||
@@ -287,6 +288,18 @@ export class View extends ViewCommon {
|
||||
super.onUnloaded();
|
||||
}
|
||||
|
||||
public onBackPressed(): boolean {
|
||||
let topmostFrame = topmost();
|
||||
|
||||
// Delegate back navigation handling to the topmost Frame
|
||||
// when it's a child of the current View.
|
||||
if (topmostFrame && topmostFrame._hasAncestorView(this)) {
|
||||
return topmostFrame.onBackPressed();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private hasGestureObservers() {
|
||||
return this._gestureObservers && Object.keys(this._gestureObservers).length > 0
|
||||
}
|
||||
|
||||
9
tns-core-modules/ui/core/view/view.d.ts
vendored
9
tns-core-modules/ui/core/view/view.d.ts
vendored
@@ -110,7 +110,7 @@ export abstract class View extends ViewBase {
|
||||
* String value used when hooking to shownModally event.
|
||||
*/
|
||||
public static shownModallyEvent: string;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the android-specific native instance that lies behind this proxy. Will be available if running on an Android platform.
|
||||
*/
|
||||
@@ -705,6 +705,11 @@ export abstract class View extends ViewBase {
|
||||
* Called in android when native view is dettached from window.
|
||||
*/
|
||||
_onDetachedFromWindow(): void;
|
||||
|
||||
/**
|
||||
* Checks whether the current view has specific view for an ancestor.
|
||||
*/
|
||||
_hasAncestorView(ancestorView: View): boolean;
|
||||
//@endprivate
|
||||
|
||||
/**
|
||||
@@ -797,7 +802,7 @@ export const isEnabledProperty: Property<View, boolean>;
|
||||
export const isUserInteractionEnabledProperty: Property<View, boolean>;
|
||||
|
||||
export namespace ios {
|
||||
export function isContentScrollable(controller: any /* UIViewController */, owner: View): boolean
|
||||
export function isContentScrollable(controller: any /* UIViewController */, owner: View): boolean
|
||||
export function updateAutoAdjustScrollInsets(controller: any /* UIViewController */, owner: View): void
|
||||
export function updateConstraints(controller: any /* UIViewController */, owner: View): void;
|
||||
export function layoutView(controller: any /* UIViewController */, owner: View): void;
|
||||
|
||||
@@ -9,10 +9,9 @@ import { knownFolders, path } from "../../file-system";
|
||||
import { parse, createViewFromEntry } from "../builder";
|
||||
import { profile } from "../../profiling";
|
||||
|
||||
import { frameStack, topmost as frameStackTopmost, _pushInFrameStack, _popFromFrameStack, _removeFromFrameStack } from "./frame-stack";
|
||||
export * from "../core/view";
|
||||
|
||||
let frameStack: Array<FrameBase> = [];
|
||||
|
||||
function buildEntryFromArgs(arg: any): NavigationEntry {
|
||||
let entry: NavigationEntry;
|
||||
if (typeof arg === "string") {
|
||||
@@ -403,41 +402,15 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
|
||||
}
|
||||
|
||||
public _pushInFrameStack() {
|
||||
if (this._isInFrameStack && frameStack[frameStack.length - 1] === this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isInFrameStack) {
|
||||
const indexOfFrame = frameStack.indexOf(this);
|
||||
frameStack.splice(indexOfFrame, 1);
|
||||
}
|
||||
|
||||
frameStack.push(this);
|
||||
this._isInFrameStack = true;
|
||||
_pushInFrameStack(this);
|
||||
}
|
||||
|
||||
public _popFromFrameStack() {
|
||||
if (!this._isInFrameStack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const top = topmost();
|
||||
if (top !== this) {
|
||||
throw new Error("Cannot pop a Frame which is not at the top of the navigation stack.");
|
||||
}
|
||||
|
||||
frameStack.pop();
|
||||
this._isInFrameStack = false;
|
||||
_popFromFrameStack(this);
|
||||
}
|
||||
|
||||
public _removeFromFrameStack() {
|
||||
if (!this._isInFrameStack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = frameStack.indexOf(this);
|
||||
frameStack.splice(index, 1);
|
||||
this._isInFrameStack = false;
|
||||
_removeFromFrameStack(this);
|
||||
}
|
||||
|
||||
public _dialogClosed(): void {
|
||||
@@ -585,11 +558,7 @@ export function getFrameById(id: string): FrameBase {
|
||||
}
|
||||
|
||||
export function topmost(): FrameBase {
|
||||
if (frameStack.length > 0) {
|
||||
return frameStack[frameStack.length - 1];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return frameStackTopmost();
|
||||
}
|
||||
|
||||
export function goBack(): boolean {
|
||||
|
||||
50
tns-core-modules/ui/frame/frame-stack.ts
Normal file
50
tns-core-modules/ui/frame/frame-stack.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// Types.
|
||||
import { FrameBase } from "./frame-common";
|
||||
|
||||
export let frameStack: Array<FrameBase> = [];
|
||||
|
||||
export function topmost(): FrameBase {
|
||||
if (frameStack.length > 0) {
|
||||
return frameStack[frameStack.length - 1];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function _pushInFrameStack(frame: FrameBase): void {
|
||||
if (frame._isInFrameStack && frameStack[frameStack.length - 1] === frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame._isInFrameStack) {
|
||||
const indexOfFrame = frameStack.indexOf(frame);
|
||||
frameStack.splice(indexOfFrame, 1);
|
||||
}
|
||||
|
||||
frameStack.push(frame);
|
||||
frame._isInFrameStack = true;
|
||||
}
|
||||
|
||||
export function _popFromFrameStack(frame: FrameBase): void {
|
||||
if (!frame._isInFrameStack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const top = topmost();
|
||||
if (top !== frame) {
|
||||
throw new Error("Cannot pop a Frame which is not at the top of the navigation stack.");
|
||||
}
|
||||
|
||||
frameStack.pop();
|
||||
frame._isInFrameStack = false;
|
||||
}
|
||||
|
||||
export function _removeFromFrameStack(frame: FrameBase): void {
|
||||
if (!frame._isInFrameStack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = frameStack.indexOf(frame);
|
||||
frameStack.splice(index, 1);
|
||||
frame._isInFrameStack = false;
|
||||
}
|
||||
Reference in New Issue
Block a user