diff --git a/e2e/modal-navigation/app/app.ts b/e2e/modal-navigation/app/app.ts
index 88113cfe5..ae7b39686 100644
--- a/e2e/modal-navigation/app/app.ts
+++ b/e2e/modal-navigation/app/app.ts
@@ -3,3 +3,4 @@ import * as application from "tns-core-modules/application";
application.run({ moduleName: "app-root" });
// application.run({ moduleName: "tab-root" });
+// application.run({ moduleName: "layout-root" });
diff --git a/e2e/modal-navigation/app/home/home-page.ts b/e2e/modal-navigation/app/home/home-page.ts
index 684ec48bd..3a0b9a9af 100644
--- a/e2e/modal-navigation/app/home/home-page.ts
+++ b/e2e/modal-navigation/app/home/home-page.ts
@@ -41,6 +41,14 @@ export function onModalPage(args: EventData) {
false);
}
+export function onModalLayout(args: EventData) {
+ const view = args.object as View;
+ view.showModal("modal-layout/modal-layout",
+ "context",
+ () => console.log("home-page modal layout closed"),
+ false);
+}
+
export function onModalTabView(args: EventData) {
const fullscreen = false;
const animated = false;
@@ -61,7 +69,14 @@ export function onNavigate(args: EventData) {
page.frame.navigate("second/second-page");
}
-export function onRootViewChange() {
- let rootView = application.getRootView();
- rootView instanceof Frame ? application._resetRootView({ moduleName: "tab-root" }) : application._resetRootView({ moduleName: "app-root" });
+export function onFrameRootViewReset() {
+ application._resetRootView({ moduleName: "app-root" });
}
+
+export function onTabRootViewReset() {
+ application._resetRootView({ moduleName: "tab-root" });
+}
+
+export function onLayoutRootViewReset() {
+ application._resetRootView({ moduleName: "layout-root" });
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation/app/home/home-page.xml b/e2e/modal-navigation/app/home/home-page.xml
index a34618a34..805df0ab5 100644
--- a/e2e/modal-navigation/app/home/home-page.xml
+++ b/e2e/modal-navigation/app/home/home-page.xml
@@ -12,8 +12,11 @@
+
-
+
+
+
diff --git a/e2e/modal-navigation/app/layout-root.xml b/e2e/modal-navigation/app/layout-root.xml
new file mode 100644
index 000000000..ee0b59854
--- /dev/null
+++ b/e2e/modal-navigation/app/layout-root.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/e2e/modal-navigation/app/modal-layout/modal-layout.ts b/e2e/modal-navigation/app/modal-layout/modal-layout.ts
new file mode 100644
index 000000000..65c9136f2
--- /dev/null
+++ b/e2e/modal-navigation/app/modal-layout/modal-layout.ts
@@ -0,0 +1,23 @@
+export function onShowingModally() {
+ console.log("modal-layout showingModally");
+}
+
+export function onLoaded() {
+ console.log("modal-layout loaded");
+}
+
+export function onNavigatingTo() {
+ console.log("modal-layout onNavigatingTo");
+}
+
+export function onNavigatingFrom() {
+ console.log("modal-layout onNavigatingFrom");
+}
+
+export function onNavigatedTo() {
+ console.log("modal-layout onNavigatedTo");
+}
+
+export function onNavigatedFrom() {
+ console.log("modal-layout onNavigatedFrom");
+}
diff --git a/e2e/modal-navigation/app/modal-layout/modal-layout.xml b/e2e/modal-navigation/app/modal-layout/modal-layout.xml
new file mode 100644
index 000000000..b77cb41e3
--- /dev/null
+++ b/e2e/modal-navigation/app/modal-layout/modal-layout.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/e2e/modal-navigation/e2e/screen.ts b/e2e/modal-navigation/e2e/screen.ts
index 3a2d56e5d..bd9f36fe3 100644
--- a/e2e/modal-navigation/e2e/screen.ts
+++ b/e2e/modal-navigation/e2e/screen.ts
@@ -12,7 +12,9 @@ const modalFrame = "Show Modal Page With Frame";
const modalPage = "Show Modal Page";
const modalTabView = "Show Modal TabView";
const navToSecondPage = "Navigate To Second Page";
-const rootView = "Change Root View";
+const resetFrameRootView = "Reset Frame Root View";
+const resetTabRootView = "Reset Tab Root View";
+const resetLayoutRootView = "Reset Layout Root View";
const showNestedModalFrame = "Show Nested Modal Page With Frame";
const showNestedModalPage = "Show Nested Modal Page";
@@ -35,9 +37,19 @@ export class Screen {
console.log(home + " loaded!");
}
- changeRootView = async () => {
- const btnChangeRootView = await this._driver.findElementByText(rootView);
- await btnChangeRootView.tap();
+ resetFrameRootView = async () => {
+ const btnResetFrameRootView = await this._driver.findElementByText(resetFrameRootView);
+ await btnResetFrameRootView.tap();
+ }
+
+ resetTabRootView = async () => {
+ const btnResetTabRootView = await this._driver.findElementByText(resetTabRootView);
+ await btnResetTabRootView.tap();
+ }
+
+ resetLayoutRootView = async () => {
+ const btnResetLayoutRootView = await this._driver.findElementByText(resetLayoutRootView);
+ await btnResetLayoutRootView.tap();
}
loadedTabRootView = async () => {
@@ -52,7 +64,7 @@ export class Screen {
try {
await this.loadedTabRootView();
} catch (err) {
- await this.changeRootView();
+ await this.resetTabRootView();
await this.loadedTabRootView();
}
}
diff --git a/tns-core-modules/ui/core/view/view-common.ts b/tns-core-modules/ui/core/view/view-common.ts
index c68e4b5d8..36d531714 100644
--- a/tns-core-modules/ui/core/view/view-common.ts
+++ b/tns-core-modules/ui/core/view/view-common.ts
@@ -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(parent)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
export const automationTextProperty = new Property({ name: "automationText" });
diff --git a/tns-core-modules/ui/core/view/view.android.ts b/tns-core-modules/ui/core/view/view.android.ts
index 9a49221aa..b73bf6317 100644
--- a/tns-core-modules/ui/core/view/view.android.ts
+++ b/tns-core-modules/ui/core/view/view.android.ts
@@ -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
}
diff --git a/tns-core-modules/ui/core/view/view.d.ts b/tns-core-modules/ui/core/view/view.d.ts
index 63acf4a5a..8071247c9 100644
--- a/tns-core-modules/ui/core/view/view.d.ts
+++ b/tns-core-modules/ui/core/view/view.d.ts
@@ -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;
export const isUserInteractionEnabledProperty: Property;
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;
diff --git a/tns-core-modules/ui/frame/frame-common.ts b/tns-core-modules/ui/frame/frame-common.ts
index 810fa8782..c45a2217a 100644
--- a/tns-core-modules/ui/frame/frame-common.ts
+++ b/tns-core-modules/ui/frame/frame-common.ts
@@ -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 = [];
-
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 {
diff --git a/tns-core-modules/ui/frame/frame-stack.ts b/tns-core-modules/ui/frame/frame-stack.ts
new file mode 100644
index 000000000..619da7fd6
--- /dev/null
+++ b/tns-core-modules/ui/frame/frame-stack.ts
@@ -0,0 +1,50 @@
+// Types.
+import { FrameBase } from "./frame-common";
+
+export let frameStack: Array = [];
+
+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;
+}