feat(hmr): preserve navigation history on applying changes (#7146)

This commit is contained in:
Vasil Chimev
2019-04-23 17:47:29 +03:00
committed by GitHub
parent 4e56c89f7d
commit d35e14ed0f
23 changed files with 414 additions and 168 deletions

View File

@ -0,0 +1,5 @@
<Page loaded="onLoaded">
<StackLayout>
<Button id="button" text="button"></Button>
</StackLayout>
</Page>

View File

@ -0,0 +1,3 @@
export function onLoaded() {
console.log("Button page loaded!");
}

View File

@ -0,0 +1,5 @@
<Page loaded="onLoaded">
<StackLayout>
<Button id="button" text="button"></Button>
</StackLayout>
</Page>

View File

@ -0,0 +1,3 @@
export function onLoaded() {
console.log("Label page loaded!");
}

View File

@ -0,0 +1,5 @@
<Page loaded="onLoaded">
<StackLayout>
<Label id="label" text="label"></Label>
</StackLayout>
</Page>

View File

@ -1,37 +1,29 @@
import * as app from "tns-core-modules/application/application";
import * as frame from "tns-core-modules/ui/frame";
import * as helper from "../ui/helper"; import * as helper from "../ui/helper";
import * as TKUnit from "../TKUnit"; import * as TKUnit from "../TKUnit";
import * as app from "tns-core-modules/application/application";
import * as frame from "tns-core-modules/ui/frame";
import { Color } from "tns-core-modules/color"; import { Color } from "tns-core-modules/color";
import { parse } from "tns-core-modules/ui/builder"; import { isAndroid } from "tns-core-modules/platform";
import { createViewFromEntry } from "tns-core-modules/ui/builder";
import { Page } from "tns-core-modules/ui/page"; import { Page } from "tns-core-modules/ui/page";
import { Frame } from "tns-core-modules/ui/frame";
const appCssFileName = "./app/application.css"; const appCssFileName = "./app/application.css";
const appNewCssFileName = "./app/app-new.css"; const appNewCssFileName = "./app/app-new.css";
const appNewScssFileName = "./app/app-new.scss"; const appNewScssFileName = "./app/app-new.scss";
const appJsFileName = "./app/app.js"; const buttonCssFileName = "./app/button-page.css";
const appTsFileName = "./app/app.ts";
const mainPageCssFileName = "./app/main-page.css"; const buttonPageModuleName = "livesync/livesync-button-page";
const mainPageHtmlFileName = "./app/main-page.html"; const buttonHtmlPageFileName = "./livesync/livesync-button-page.html";
const mainPageXmlFileName = "./app/main-page.xml"; const buttonXmlPageFileName = "./livesync/livesync-button-page.xml";
const buttonJsPageFileName = "./livesync/livesync-button-page.js";
const buttonTsPageFileName = "./livesync/livesync-button-page.ts";
const labelPageModuleName = "livesync/livesync-label-page";
const black = new Color("black");
const green = new Color("green"); const green = new Color("green");
const mainPageTemplate = `
<Page>
<StackLayout>
<Label id="label" text="label"></Label>
</StackLayout>
</Page>`;
const pageTemplate = `
<Page>
<StackLayout>
<Button id="button" text="button"></Button>
</StackLayout>
</Page>`;
export function test_onLiveSync_ModuleContext_AppStyle_AppNewCss() { export function test_onLiveSync_ModuleContext_AppStyle_AppNewCss() {
_test_onLiveSync_ModuleContext_AppStyle(appNewCssFileName); _test_onLiveSync_ModuleContext_AppStyle(appNewCssFileName);
} }
@ -48,29 +40,29 @@ export function test_onLiveSync_ModuleContext_ModuleUndefined() {
_test_onLiveSync_ModuleContext({ type: "script", path: undefined }); _test_onLiveSync_ModuleContext({ type: "script", path: undefined });
} }
export function test_onLiveSync_ModuleContext_Script_AppJs() { export function test_onLiveSync_ModuleContext_Script_JsFile() {
_test_onLiveSync_ModuleContext({ type: "script", path: appJsFileName }); _test_onLiveSync_ModuleReplace({ type: "script", path: buttonJsPageFileName });
} }
export function test_onLiveSync_ModuleContext_Script_AppTs() { export function test_onLiveSync_ModuleContext_Script_TsFile() {
_test_onLiveSync_ModuleContext({ type: "script", path: appTsFileName }); _test_onLiveSync_ModuleReplace({ type: "script", path: buttonTsPageFileName });
} }
export function test_onLiveSync_ModuleContext_Style_MainPageCss() { export function test_onLiveSync_ModuleContext_Style_CssFile() {
_test_onLiveSync_ModuleContext_TypeStyle({ type: "style", path: mainPageCssFileName }); _test_onLiveSync_ModuleContext_TypeStyle({ type: "style", path: buttonCssFileName });
} }
export function test_onLiveSync_ModuleContext_Markup_MainPageHtml() { export function test_onLiveSync_ModuleContext_Markup_HtmlFile() {
_test_onLiveSync_ModuleContext({ type: "markup", path: mainPageHtmlFileName }); _test_onLiveSync_ModuleReplace({ type: "markup", path: buttonHtmlPageFileName });
} }
export function test_onLiveSync_ModuleContext_Markup_MainPageXml() { export function test_onLiveSync_ModuleContext_Markup_XmlFile() {
_test_onLiveSync_ModuleContext({ type: "markup", path: mainPageXmlFileName }); _test_onLiveSync_ModuleReplace({ type: "markup", path: buttonXmlPageFileName });
} }
export function setUpModule() { export function setUp() {
const mainPage = <Page>parse(mainPageTemplate); const labelPage = <Page>createViewFromEntry(({ moduleName: labelPageModuleName }));
helper.navigate(() => mainPage); helper.navigate(() => labelPage);
} }
export function tearDown() { export function tearDown() {
@ -79,32 +71,29 @@ export function tearDown() {
function _test_onLiveSync_ModuleContext_AppStyle(styleFileName: string) { function _test_onLiveSync_ModuleContext_AppStyle(styleFileName: string) {
const pageBeforeNavigation = helper.getCurrentPage(); const pageBeforeNavigation = helper.getCurrentPage();
const buttonPage = <Page>createViewFromEntry(({ moduleName: buttonPageModuleName }));
helper.navigateWithHistory(() => buttonPage);
const page = <Page>parse(pageTemplate);
helper.navigateWithHistory(() => page);
app.setCssFileName(styleFileName); app.setCssFileName(styleFileName);
const pageBeforeLiveSync = helper.getCurrentPage(); const pageBeforeLiveSync = helper.getCurrentPage();
global.__onLiveSync({ type: "style", path: styleFileName }); global.__onLiveSync({ type: "style", path: styleFileName });
const pageAfterLiveSync = helper.getCurrentPage(); const pageAfterLiveSync = helper.getCurrentPage();
TKUnit.waitUntilReady(() => pageAfterLiveSync.getViewById("button").style.color.toString() === green.toString()); TKUnit.waitUntilReady(() => pageAfterLiveSync.getViewById("button").style.color.toString() === green.toString());
TKUnit.assertTrue(pageAfterLiveSync.frame.canGoBack(), "Can NOT go back!");
TKUnit.assertTrue(pageAfterLiveSync.frame.canGoBack(), "App styles NOT applied - livesync navigation executed!"); TKUnit.assertEqual(pageAfterLiveSync, pageBeforeLiveSync, "Pages are different!");
TKUnit.assertEqual(pageAfterLiveSync, pageBeforeLiveSync, "Pages are different - livesync navigation executed!"); TKUnit.assertTrue(pageAfterLiveSync._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version is NOT applied!");
TKUnit.assertTrue(pageAfterLiveSync._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version NOT applied!");
helper.goBack(); helper.goBack();
const pageAfterNavigationBack = helper.getCurrentPage(); const pageAfterNavigationBack = helper.getCurrentPage();
TKUnit.assertEqual(pageAfterNavigationBack.getViewById("label").style.color, green, "App styles NOT applied on back navigation!"); TKUnit.assertEqual(pageAfterNavigationBack.getViewById("label").style.color, green, "App styles NOT applied on back navigation!");
TKUnit.assertEqual(pageBeforeNavigation, pageAfterNavigationBack, "Pages are different - livesync navigation executed!"); TKUnit.assertEqual(pageBeforeNavigation, pageAfterNavigationBack, "Pages are different");
TKUnit.assertTrue(pageAfterNavigationBack._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version is NOT applied!"); TKUnit.assertTrue(pageAfterNavigationBack._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version is NOT applied!");
} }
function _test_onLiveSync_ModuleContext(context: { type, path }) { function _test_onLiveSync_ModuleContext(context: { type, path }) {
const page = <Page>parse(pageTemplate); const buttonPage = <Page>createViewFromEntry(({ moduleName: buttonPageModuleName }));
helper.navigateWithHistory(() => page); helper.navigateWithHistory(() => buttonPage);
global.__onLiveSync({ type: context.type, path: context.path }); global.__onLiveSync({ type: context.type, path: context.path });
TKUnit.waitUntilReady(() => !!frame.topmost()); TKUnit.waitUntilReady(() => !!frame.topmost());
@ -113,27 +102,53 @@ function _test_onLiveSync_ModuleContext(context: { type, path }) {
TKUnit.assertTrue(topmostFrame.currentPage.getViewById("label").isLoaded); TKUnit.assertTrue(topmostFrame.currentPage.getViewById("label").isLoaded);
} }
function _test_onLiveSync_ModuleReplace(context: { type, path }) {
const pageBeforeNavigation = helper.getCurrentPage();
const buttonPage = <Page>createViewFromEntry(({ moduleName: buttonPageModuleName }));
helper.navigateWithHistory(() => buttonPage);
global.__onLiveSync({ type: context.type, path: context.path });
const topmostFrame = frame.topmost();
waitUntilLivesyncComplete(topmostFrame);
TKUnit.assertTrue(topmostFrame.currentPage.getViewById("button").isLoaded, "Button page is NOT loaded!");
TKUnit.assertEqual(topmostFrame.backStack.length, 1, "Backstack is clean!");
TKUnit.assertTrue(topmostFrame.canGoBack(), "Can NOT go back!");
helper.goBack();
const pageAfterBackNavigation = helper.getCurrentPage();
TKUnit.assertTrue(topmostFrame.currentPage.getViewById("label").isLoaded, "Label page is NOT loaded!");
TKUnit.assertEqual(topmostFrame.backStack.length, 0, "Backstack is NOT clean!");
TKUnit.assertEqual(pageBeforeNavigation, pageAfterBackNavigation, "Pages are different!");
}
function _test_onLiveSync_ModuleContext_TypeStyle(context: { type, path }) { function _test_onLiveSync_ModuleContext_TypeStyle(context: { type, path }) {
const pageBeforeNavigation = helper.getCurrentPage(); const pageBeforeNavigation = helper.getCurrentPage();
const buttonPage = <Page>createViewFromEntry(({ moduleName: buttonPageModuleName }));
const page = <Page>parse(pageTemplate); helper.navigateWithHistory(() => buttonPage);
helper.navigateWithHistory(() => page);
const pageBeforeLiveSync = helper.getCurrentPage(); const pageBeforeLiveSync = helper.getCurrentPage();
pageBeforeLiveSync._moduleName = "main-page"; pageBeforeLiveSync._moduleName = "button-page";
global.__onLiveSync({ type: context.type, path: context.path }); global.__onLiveSync({ type: context.type, path: context.path });
const topmostFrame = frame.topmost();
waitUntilLivesyncComplete(topmostFrame);
const pageAfterLiveSync = helper.getCurrentPage(); const pageAfterLiveSync = helper.getCurrentPage();
TKUnit.waitUntilReady(() => pageAfterLiveSync.getViewById("button").style.color.toString() === green.toString()); TKUnit.waitUntilReady(() => pageAfterLiveSync.getViewById("button").style.color.toString() === green.toString());
TKUnit.assertTrue(pageAfterLiveSync.frame.canGoBack(), "Can NOT go back!");
TKUnit.assertTrue(pageAfterLiveSync.frame.canGoBack(), "Local styles NOT applied - livesync navigation executed!"); TKUnit.assertEqual(topmostFrame.backStack.length, 1, "Backstack is clean!");
TKUnit.assertEqual(pageAfterLiveSync, pageBeforeLiveSync, "Pages are different - livesync navigation executed!"); TKUnit.assertTrue(pageAfterLiveSync._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version is NOT applied!");
TKUnit.assertTrue(pageAfterLiveSync._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version NOT applied!");
helper.goBack(); helper.goBack();
const pageAfterNavigationBack = helper.getCurrentPage(); const pageAfterNavigationBack = helper.getCurrentPage();
TKUnit.assertEqual(pageAfterNavigationBack.getViewById("label").style.color, black, "App styles applied on back navigation!"); TKUnit.assertEqual(pageBeforeNavigation, pageAfterNavigationBack, "Pages are different!");
TKUnit.assertEqual(pageBeforeNavigation, pageAfterNavigationBack, "Pages are different - livesync navigation executed!");
TKUnit.assertTrue(pageAfterNavigationBack._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version is NOT applied!"); TKUnit.assertTrue(pageAfterNavigationBack._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version is NOT applied!");
} }
function waitUntilLivesyncComplete(frame: Frame) {
if (isAndroid) {
TKUnit.waitUntilReady(() => frame._executingEntry === null);
} else {
TKUnit.waitUntilReady(() => frame.currentPage.isLoaded);
}
}

View File

@ -5,11 +5,11 @@
"repository": "<fill-your-repository-here>", "repository": "<fill-your-repository-here>",
"nativescript": { "nativescript": {
"id": "org.nativescript.UnitTestApp", "id": "org.nativescript.UnitTestApp",
"tns-ios": {
"version": "5.2.0"
},
"tns-android": { "tns-android": {
"version": "5.2.1" "version": "5.3.1"
},
"tns-ios": {
"version": "5.3.1"
} }
}, },
"dependencies": { "dependencies": {

View File

@ -83,22 +83,20 @@ export function livesync(rootView: View, context?: ModuleContext) {
events.notify(<EventData>{ eventName: "livesync", object: app }); events.notify(<EventData>{ eventName: "livesync", object: app });
const liveSyncCore = global.__onLiveSyncCore; const liveSyncCore = global.__onLiveSyncCore;
let reapplyAppStyles = false; let reapplyAppStyles = false;
let reapplyLocalStyles = false;
// ModuleContext is available only for Hot Module Replacement
if (context && context.path) { if (context && context.path) {
const extensions = ["css", "scss"]; const styleExtensions = ["css", "scss"];
const appStylesFullFileName = getCssFileName(); const appStylesFullFileName = getCssFileName();
const appStylesFileName = appStylesFullFileName.substring(0, appStylesFullFileName.lastIndexOf(".") + 1); const appStylesFileName = appStylesFullFileName.substring(0, appStylesFullFileName.lastIndexOf(".") + 1);
reapplyAppStyles = extensions.some(ext => context.path === appStylesFileName.concat(ext)); reapplyAppStyles = styleExtensions.some(ext => context.path === appStylesFileName.concat(ext));
if (!reapplyAppStyles) {
reapplyLocalStyles = extensions.some(ext => context.path.endsWith(ext));
}
} }
// Handle application styles
if (reapplyAppStyles && rootView) { if (reapplyAppStyles && rootView) {
rootView._onCssStateChange(); rootView._onCssStateChange();
} else if (liveSyncCore) { } else if (liveSyncCore) {
reapplyLocalStyles ? liveSyncCore(context) : liveSyncCore(); liveSyncCore(context);
} }
} }

View File

@ -10,6 +10,7 @@ import {
notify, launchEvent, resumeEvent, suspendEvent, exitEvent, lowMemoryEvent, notify, launchEvent, resumeEvent, suspendEvent, exitEvent, lowMemoryEvent,
orientationChangedEvent, setApplication, livesync, displayedEvent, getCssFileName orientationChangedEvent, setApplication, livesync, displayedEvent, getCssFileName
} from "./application-common"; } from "./application-common";
import { ModuleType } from "../ui/core/view/view-common";
// First reexport so that app module is initialized. // First reexport so that app module is initialized.
export * from "./application-common"; export * from "./application-common";
@ -106,6 +107,7 @@ class IOSApplication implements IOSApplicationDefinition {
get delegate(): typeof UIApplicationDelegate { get delegate(): typeof UIApplicationDelegate {
return this._delegate; return this._delegate;
} }
set delegate(value: typeof UIApplicationDelegate) { set delegate(value: typeof UIApplicationDelegate) {
if (this._delegate !== value) { if (this._delegate !== value) {
this._delegate = value; this._delegate = value;
@ -228,8 +230,16 @@ class IOSApplication implements IOSApplicationDefinition {
} }
public _onLivesync(context?: ModuleContext): void { public _onLivesync(context?: ModuleContext): void {
// If view can't handle livesync set window controller. // Handle application root module
if (this._rootView && !this._rootView._onLivesync(context)) { const isAppRootModuleChanged = context && context.path && context.path.includes(getMainEntry().moduleName) && context.type !== ModuleType.style;
// Set window content when:
// + Application root module is changed
// + View did not handle the change
// Note:
// The case when neither app root module is changed, nor livesync is handled on View,
// then changes will not apply until navigate forward to the module.
if (isAppRootModuleChanged || (this._rootView && !this._rootView._onLivesync(context))) {
this.setWindowContent(); this.setWindowContent();
} }
} }
@ -258,7 +268,6 @@ class IOSApplication implements IOSApplicationDefinition {
this._window.makeKeyAndVisible(); this._window.makeKeyAndVisible();
} }
} }
} }
const iosApp = new IOSApplication(); const iosApp = new IOSApplication();

View File

@ -100,10 +100,11 @@ export module categories {
export const Error: string; export const Error: string;
export const Animation: string; export const Animation: string;
export const Transition: string; export const Transition: string;
export const Livesync: string;
export const All: string;
export const separator: string; export const separator: string;
export const All: string;
export function concat(...categories: string[]): string; export function concat(...categories: string[]): string;
} }
@ -125,7 +126,7 @@ export interface TraceWriter {
} }
/** /**
* An interface used to trace information about specific event. * An interface used to trace information about specific event.
*/ */
export interface EventListener { export interface EventListener {
filter: string; filter: string;
@ -133,7 +134,7 @@ export interface EventListener {
} }
/** /**
* An interface used to for handling trace error * An interface used to for handling trace error
*/ */
export interface ErrorHandler { export interface ErrorHandler {
handlerError(error: Error); handlerError(error: Error);
@ -141,4 +142,4 @@ export interface ErrorHandler {
export class DefaultErrorHandler implements ErrorHandler { export class DefaultErrorHandler implements ErrorHandler {
handlerError(error); handlerError(error);
} }

View File

@ -129,9 +129,22 @@ export module categories {
export const Error = "Error"; export const Error = "Error";
export const Animation = "Animation"; export const Animation = "Animation";
export const Transition = "Transition"; export const Transition = "Transition";
export const All = VisualTreeEvents + "," + Layout + "," + Style + "," + ViewHierarchy + "," + NativeLifecycle + "," + Debug + "," + Navigation + "," + Test + "," + Binding + "," + Error + "," + Animation + "," + Transition; export const Livesync = "Livesync";
export const separator = ","; export const separator = ",";
export const All = VisualTreeEvents + separator
+ Layout + separator
+ Style + separator
+ ViewHierarchy + separator
+ NativeLifecycle + separator
+ Debug + separator
+ Navigation + separator
+ Test + separator
+ Binding + separator
+ Error + separator
+ Animation + separator
+ Transition + separator
+ Livesync;
export function concat(): string { export function concat(): string {
let result: string; let result: string;

View File

@ -160,7 +160,7 @@ export abstract class ViewBase extends Observable {
/** /**
* @deprecated use showModal with ShowModalOptions instead * @deprecated use showModal with ShowModalOptions instead
* *
* Shows the View contained in moduleName as a modal view. * Shows the View contained in moduleName as a modal view.
* @param moduleName - The name of the module to load starting from the application root. * @param moduleName - The name of the module to load starting from the application root.
* @param context - Any context you want to pass to the modally shown view. * @param context - Any context you want to pass to the modally shown view.
@ -175,7 +175,7 @@ export abstract class ViewBase extends Observable {
/** /**
* @deprecated use showModal with ShowModalOptions instead * @deprecated use showModal with ShowModalOptions instead
* *
* Shows the view passed as parameter as a modal view. * Shows the view passed as parameter as a modal view.
* @param view - View instance to be shown modally. * @param view - View instance to be shown modally.
* @param context - Any context you want to pass to the modally shown view. This same context will be available in the arguments of the shownModally event handler. * @param context - Any context you want to pass to the modally shown view. This same context will be available in the arguments of the shownModally event handler.
@ -367,7 +367,7 @@ export abstract class ViewBase extends Observable {
public _goToVisualState(state: string): void; public _goToVisualState(state: string): void;
/** /**
* @deprecated * @deprecated
* *
* This used to be the way to set attribute values in early {N} versions. * This used to be the way to set attribute values in early {N} versions.
* Now attributes are expected to be set as plain properties on the view instances. * Now attributes are expected to be set as plain properties on the view instances.
*/ */

View File

@ -7,8 +7,7 @@ import {
import { import {
ViewBase, Property, booleanConverter, eachDescendant, EventData, layout, ViewBase, Property, booleanConverter, eachDescendant, EventData, layout,
getEventOrGestureName, traceEnabled, traceWrite, traceCategories, getEventOrGestureName, traceEnabled, traceWrite, traceCategories,
InheritedProperty, InheritedProperty, ShowModalOptions
ShowModalOptions
} from "../view-base"; } from "../view-base";
import { HorizontalAlignment, VerticalAlignment, Visibility, Length, PercentLength } from "../../styling/style-properties"; import { HorizontalAlignment, VerticalAlignment, Visibility, Length, PercentLength } from "../../styling/style-properties";
@ -38,6 +37,12 @@ function ensureAnimationModule() {
} }
} }
export enum ModuleType {
markup = "markup",
script = "script",
style = "style"
}
export function CSSType(type: string): ClassDecorator { export function CSSType(type: string): ClassDecorator {
return (cls) => { return (cls) => {
cls.prototype.cssType = type; cls.prototype.cssType = type;
@ -138,12 +143,22 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
} }
public _onLivesync(context?: ModuleContext): boolean { public _onLivesync(context?: ModuleContext): boolean {
if (traceEnabled()) {
traceWrite(`${this}._onLivesync(${JSON.stringify(context)})`, traceCategories.Livesync);
}
_rootModalViews.forEach(v => v.closeModal()); _rootModalViews.forEach(v => v.closeModal());
_rootModalViews.length = 0; _rootModalViews.length = 0;
// Currently, we pass `context` only for style modules if (context && context.type && context.path) {
if (context && context.path) { // Handle local styles
return this.changeLocalStyles(context.path); if (context.type === ModuleType.style) {
return this.changeLocalStyles(context.path);
}
// Handle module markup and script changes
else {
return this.changeModule(context);
}
} }
return false; return false;
@ -156,11 +171,16 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
return true; return true;
}); });
} }
// Do not execute frame navigation for a change in styles
// Do not reset activity/window content for local styles changes
return true; return true;
} }
private changeStyles(view: ViewBase, contextPath: string): boolean { private changeStyles(view: ViewBase, contextPath: string): boolean {
if (traceEnabled()) {
traceWrite(`${view}.${view._moduleName}`, traceCategories.Livesync);
}
if (view._moduleName && contextPath.includes(view._moduleName)) { if (view._moduleName && contextPath.includes(view._moduleName)) {
(<this>view).changeCssFile(contextPath); (<this>view).changeCssFile(contextPath);
return true; return true;
@ -168,6 +188,23 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
return false; return false;
} }
private changeModule(context: ModuleContext): boolean {
eachDescendant(this, (child: ViewBase) => {
if (traceEnabled()) {
traceWrite(`${child}.${child._moduleName}`, traceCategories.Livesync);
}
// Handle changes in module's Page
if (child._moduleName && context.path.includes(child._moduleName) && child.page) {
child.page._onLivesync(context);
}
return true;
});
// Do not reset activity/window content for module changes
return true;
}
_setupAsRootView(context: any): void { _setupAsRootView(context: any): void {
super._setupAsRootView(context); super._setupAsRootView(context);
if (!this._styleScope) { if (!this._styleScope) {

View File

@ -232,22 +232,22 @@ export function _getAnimatedEntries(frameId: number): Set<BackstackEntry> {
export function _updateTransitions(entry: ExpandedEntry): void { export function _updateTransitions(entry: ExpandedEntry): void {
const fragment = entry.fragment; const fragment = entry.fragment;
const enterTransitionListener = entry.enterTransitionListener; const enterTransitionListener = entry.enterTransitionListener;
if (enterTransitionListener) { if (enterTransitionListener && fragment) {
fragment.setEnterTransition(enterTransitionListener.transition); fragment.setEnterTransition(enterTransitionListener.transition);
} }
const exitTransitionListener = entry.exitTransitionListener; const exitTransitionListener = entry.exitTransitionListener;
if (exitTransitionListener) { if (exitTransitionListener && fragment) {
fragment.setExitTransition(exitTransitionListener.transition); fragment.setExitTransition(exitTransitionListener.transition);
} }
const reenterTransitionListener = entry.reenterTransitionListener; const reenterTransitionListener = entry.reenterTransitionListener;
if (reenterTransitionListener) { if (reenterTransitionListener && fragment) {
fragment.setReenterTransition(reenterTransitionListener.transition); fragment.setReenterTransition(reenterTransitionListener.transition);
} }
const returnTransitionListener = entry.returnTransitionListener; const returnTransitionListener = entry.returnTransitionListener;
if (returnTransitionListener) { if (returnTransitionListener && fragment) {
fragment.setReturnTransition(returnTransitionListener.transition); fragment.setReturnTransition(returnTransitionListener.transition);
} }
} }
@ -374,7 +374,7 @@ function getAnimationListener(): android.animation.Animator.AnimatorListener {
return AnimationListener; return AnimationListener;
} }
function addToWaitingQueue(entry: ExpandedEntry): void { function addToWaitingQueue(entry: ExpandedEntry): void {
const frameId = entry.frameId; const frameId = entry.frameId;
let entries = waitingQueue.get(frameId); let entries = waitingQueue.get(frameId);
@ -659,7 +659,7 @@ function setupAllAnimation(entry: ExpandedEntry, transition: Transition): void {
setupExitAndPopEnterAnimation(entry, transition); setupExitAndPopEnterAnimation(entry, transition);
const listener = getAnimationListener(); const listener = getAnimationListener();
// setupAllAnimation is called only for new fragments so we don't // setupAllAnimation is called only for new fragments so we don't
// need to clearAnimationListener for enter & popExit animators. // need to clearAnimationListener for enter & popExit animators.
const enterAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.enter); const enterAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.enter);
enterAnimator.transitionType = AndroidTransitionType.enter; enterAnimator.transitionType = AndroidTransitionType.enter;
@ -720,7 +720,7 @@ function transitionOrAnimationCompleted(entry: ExpandedEntry): void {
if (entries.size === 0) { if (entries.size === 0) {
const frame = entry.resolvedPage.frame; const frame = entry.resolvedPage.frame;
// We have 0 or 1 entry per frameId in completedEntries // We have 0 or 1 entry per frameId in completedEntries
// So there is no need to make it to Set like waitingQueue // So there is no need to make it to Set like waitingQueue
const previousCompletedAnimationEntry = completedEntries.get(frameId); const previousCompletedAnimationEntry = completedEntries.get(frameId);
completedEntries.delete(frameId); completedEntries.delete(frameId);
waitingQueue.delete(frameId); waitingQueue.delete(frameId);
@ -730,8 +730,8 @@ function transitionOrAnimationCompleted(entry: ExpandedEntry): void {
// Will be null if Frame is shown modally... // Will be null if Frame is shown modally...
// transitionOrAnimationCompleted fires again (probably bug in android). // transitionOrAnimationCompleted fires again (probably bug in android).
if (current) { if (current) {
const isBack = frame._isBack; const navType = frame.navigationType;
setTimeout(() => frame.setCurrent(current, isBack)); setTimeout(() => frame.setCurrent(current, navType));
} }
} else { } else {
completedEntries.set(frameId, entry); completedEntries.set(frameId, entry);

View File

@ -11,6 +11,12 @@ import { profile } from "../../profiling";
import { frameStack, topmost as frameStackTopmost, _pushInFrameStack, _popFromFrameStack, _removeFromFrameStack } from "./frame-stack"; import { frameStack, topmost as frameStackTopmost, _pushInFrameStack, _popFromFrameStack, _removeFromFrameStack } from "./frame-stack";
export * from "../core/view"; export * from "../core/view";
export enum NavigationType {
back,
forward,
replace
}
function buildEntryFromArgs(arg: any): NavigationEntry { function buildEntryFromArgs(arg: any): NavigationEntry {
let entry: NavigationEntry; let entry: NavigationEntry;
if (typeof arg === "string") { if (typeof arg === "string") {
@ -48,6 +54,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
public _isInFrameStack = false; public _isInFrameStack = false;
public static defaultAnimatedNavigation = true; public static defaultAnimatedNavigation = true;
public static defaultTransition: NavigationTransition; public static defaultTransition: NavigationTransition;
public navigationType: NavigationType;
// TODO: Currently our navigation will not be synchronized in case users directly call native navigation methods like Activity.startActivity. // TODO: Currently our navigation will not be synchronized in case users directly call native navigation methods like Activity.startActivity.
@ -206,7 +213,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
return this._currentEntry === entry; return this._currentEntry === entry;
} }
public setCurrent(entry: BackstackEntry, isBack: boolean): void { public setCurrent(entry: BackstackEntry, navigationType: NavigationType): void {
const newPage = entry.resolvedPage; const newPage = entry.resolvedPage;
// In case we navigated forward to a page that was in the backstack // In case we navigated forward to a page that was in the backstack
// with clearHistory: true // with clearHistory: true
@ -217,6 +224,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
this._currentEntry = entry; this._currentEntry = entry;
const isBack = navigationType === NavigationType.back;
if (isBack) { if (isBack) {
this._pushInFrameStack(); this._pushInFrameStack();
} }
@ -229,15 +237,18 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
this._executingEntry = null; this._executingEntry = null;
} }
public _updateBackstack(entry: BackstackEntry, isBack: boolean): void { public _updateBackstack(entry: BackstackEntry, navigationType: NavigationType): void {
const isBack = navigationType === NavigationType.back;
const isReplace = navigationType === NavigationType.replace;
this.raiseCurrentPageNavigatedEvents(isBack); this.raiseCurrentPageNavigatedEvents(isBack);
const current = this._currentEntry; const current = this._currentEntry;
// Do nothing for Hot Module Replacement
if (isBack) { if (isBack) {
const index = this._backStack.indexOf(entry); const index = this._backStack.indexOf(entry);
this._backStack.splice(index + 1).forEach(e => this._removeEntry(e)); this._backStack.splice(index + 1).forEach(e => this._removeEntry(e));
this._backStack.pop(); this._backStack.pop();
} else { } else if (!isReplace) {
if (entry.entry.clearHistory) { if (entry.entry.clearHistory) {
this._backStack.forEach(e => this._removeEntry(e)); this._backStack.forEach(e => this._removeEntry(e));
this._backStack.length = 0; this._backStack.length = 0;
@ -345,7 +356,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
} }
@profile @profile
private performNavigation(navigationContext: NavigationContext) { public performNavigation(navigationContext: NavigationContext) {
const navContext = navigationContext.entry; const navContext = navigationContext.entry;
this._executingEntry = navContext; this._executingEntry = navContext;
this._onNavigatingTo(navContext, navigationContext.isBackNavigation); this._onNavigatingTo(navContext, navigationContext.isBackNavigation);
@ -563,35 +574,39 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
return result; return result;
} }
public _onLivesync(context?: ModuleContext): boolean { public _onLivesync(): boolean {
// Execute a navigation if not handled on `View` level // Reset activity/window content when:
if (!super._onLivesync(context)) { // + Changes are not handled on View
if (!this._currentEntry || !this._currentEntry.entry) { // + There is no ModuleContext
if (traceEnabled()) {
traceWrite(`${this}._onLivesync()`, traceCategories.Livesync);
}
if (!this._currentEntry || !this._currentEntry.entry) {
return false;
}
const currentEntry = this._currentEntry.entry;
const newEntry: NavigationEntry = {
animated: false,
clearHistory: true,
context: currentEntry.context,
create: currentEntry.create,
moduleName: currentEntry.moduleName,
backstackVisible: currentEntry.backstackVisible
}
// If create returns the same page instance we can't recreate it.
// Instead of navigation set activity content.
// This could happen if current page was set in XML as a Page instance.
if (newEntry.create) {
const page = newEntry.create();
if (page === this.currentPage) {
return false; return false;
} }
const currentEntry = this._currentEntry.entry;
const newEntry: NavigationEntry = {
animated: false,
clearHistory: true,
context: currentEntry.context,
create: currentEntry.create,
moduleName: currentEntry.moduleName,
backstackVisible: currentEntry.backstackVisible
}
// If create returns the same page instance we can't recreate it.
// Instead of navigation set activity content.
// This could happen if current page was set in XML as a Page instance.
if (newEntry.create) {
const page = newEntry.create();
if (page === this.currentPage) {
return false;
}
}
this.navigate(newEntry);
} }
this.navigate(newEntry);
return true; return true;
} }
} }

View File

@ -1,15 +1,16 @@
// Definitions. // Definitions.
import { import {
AndroidFrame as AndroidFrameDefinition, BackstackEntry, AndroidFrame as AndroidFrameDefinition, AndroidActivityCallbacks,
NavigationTransition, AndroidFragmentCallbacks, AndroidActivityCallbacks AndroidFragmentCallbacks, BackstackEntry, NavigationTransition
} from "."; } from ".";
import { ModuleType } from "../../ui/core/view/view-common";
import { Page } from "../page"; import { Page } from "../page";
// Types. // Types.
import * as application from "../../application"; import * as application from "../../application";
import { import {
FrameBase, stack, goBack, View, Observable, FrameBase, goBack, stack, NavigationContext, NavigationType,
traceEnabled, traceWrite, traceCategories, traceError Observable, View, traceCategories, traceEnabled, traceError, traceWrite
} from "./frame-common"; } from "./frame-common";
import { import {
@ -21,6 +22,7 @@ import { profile } from "../../profiling";
// TODO: Remove this and get it from global to decouple builder for angular // TODO: Remove this and get it from global to decouple builder for angular
import { createViewFromEntry } from "../builder"; import { createViewFromEntry } from "../builder";
import { getModuleName } from "../../utils/utils";
export * from "./frame-common"; export * from "./frame-common";
@ -87,8 +89,16 @@ export function reloadPage(context?: ModuleContext): void {
const callbacks: AndroidActivityCallbacks = activity[CALLBACKS]; const callbacks: AndroidActivityCallbacks = activity[CALLBACKS];
if (callbacks) { if (callbacks) {
const rootView: View = callbacks.getRootView(); const rootView: View = callbacks.getRootView();
// Handle application root module
const isAppRootModuleChanged = context && context.path && context.path.includes(application.getMainEntry().moduleName) && context.type !== ModuleType.style;
if (!rootView || !rootView._onLivesync(context)) { // Reset activity content when:
// + Application root module is changed
// + View did not handle the change
// Note:
// The case when neither app root module is changed, neighter livesync is handled on View,
// then changes will not apply until navigate forward to the module.
if (isAppRootModuleChanged || !rootView || !rootView._onLivesync(context)) {
callbacks.resetActivityContent(activity); callbacks.resetActivityContent(activity);
} }
} else { } else {
@ -104,7 +114,6 @@ export class Frame extends FrameBase {
private _containerViewId: number = -1; private _containerViewId: number = -1;
private _tearDownPending = false; private _tearDownPending = false;
private _attachedToWindow = false; private _attachedToWindow = false;
public _isBack: boolean = true;
private _cachedAnimatorState: AnimatorState; private _cachedAnimatorState: AnimatorState;
constructor() { constructor() {
@ -263,11 +272,11 @@ export class Frame extends FrameBase {
return newFragment; return newFragment;
} }
public setCurrent(entry: BackstackEntry, isBack: boolean): void { public setCurrent(entry: BackstackEntry, navigationType: NavigationType): void {
const current = this._currentEntry; const current = this._currentEntry;
const currentEntryChanged = current !== entry; const currentEntryChanged = current !== entry;
if (currentEntryChanged) { if (currentEntryChanged) {
this._updateBackstack(entry, isBack); this._updateBackstack(entry, navigationType);
// If activity was destroyed we need to destroy fragment and UI // If activity was destroyed we need to destroy fragment and UI
// of current and new entries. // of current and new entries.
@ -296,7 +305,7 @@ export class Frame extends FrameBase {
} }
} }
super.setCurrent(entry, isBack); super.setCurrent(entry, navigationType);
// If we had real navigation process queue. // If we had real navigation process queue.
this._processNavigationQueue(entry.resolvedPage); this._processNavigationQueue(entry.resolvedPage);
@ -330,10 +339,48 @@ export class Frame extends FrameBase {
return false; return false;
} }
public _onLivesync(context?: ModuleContext): boolean {
if (traceEnabled()) {
traceWrite(`${this}._onLivesync(${JSON.stringify(context)})`, traceCategories.Livesync);
}
if (!this._currentEntry || !this._currentEntry.entry) {
return false;
}
if (context && context.type && context.path) {
// Set NavigationType.replace for HMR.
this.navigationType = NavigationType.replace;
const currentBackstackEntry = this._currentEntry;
const contextModuleName = getModuleName(context.path);
const newPage = <Page>createViewFromEntry({ moduleName: contextModuleName });
const newBackstackEntry: BackstackEntry = {
entry: currentBackstackEntry.entry,
resolvedPage: newPage,
navDepth: currentBackstackEntry.navDepth,
fragmentTag: currentBackstackEntry.fragmentTag,
frameId: currentBackstackEntry.frameId
};
const navContext: NavigationContext = { entry: newBackstackEntry, isBackNavigation: false };
this.performNavigation(navContext);
return true;
} else {
// Fallback
return super._onLivesync();
}
}
@profile @profile
public _navigateCore(newEntry: BackstackEntry) { public _navigateCore(newEntry: BackstackEntry) {
super._navigateCore(newEntry); super._navigateCore(newEntry);
this._isBack = false; // NavigationType.replace for HMR.
// Otherwise, default to NavigationType.forward.
const isReplace = this.navigationType === NavigationType.replace;
if (!isReplace) {
this.navigationType = NavigationType.forward;
}
// set frameId here so that we could use it in fragment.transitions // set frameId here so that we could use it in fragment.transitions
newEntry.frameId = this._android.frameId; newEntry.frameId = this._android.frameId;
@ -360,7 +407,10 @@ export class Frame extends FrameBase {
navDepth = -1; navDepth = -1;
} }
navDepth++; if (!isReplace) {
navDepth++;
}
fragmentId++; fragmentId++;
const newFragmentTag = `fragment${fragmentId}[${navDepth}]`; const newFragmentTag = `fragment${fragmentId}[${navDepth}]`;
const newFragment = this.createFragment(newEntry, newFragmentTag); const newFragment = this.createFragment(newEntry, newFragmentTag);
@ -383,7 +433,7 @@ export class Frame extends FrameBase {
} }
public _goBackCore(backstackEntry: BackstackEntry) { public _goBackCore(backstackEntry: BackstackEntry) {
this._isBack = true; this.navigationType = NavigationType.back;
super._goBackCore(backstackEntry); super._goBackCore(backstackEntry);
navDepth = backstackEntry.navDepth; navDepth = backstackEntry.navDepth;
@ -1282,4 +1332,4 @@ export function setActivityCallbacks(activity: android.support.v7.app.AppCompatA
export function setFragmentCallbacks(fragment: android.support.v4.app.Fragment): void { export function setFragmentCallbacks(fragment: android.support.v4.app.Fragment): void {
fragment[CALLBACKS] = new FragmentCallbacksImplementation(); fragment[CALLBACKS] = new FragmentCallbacksImplementation();
} }

View File

@ -3,14 +3,14 @@
* @module "ui/frame" * @module "ui/frame"
*/ /** */ */ /** */
import { NavigationType } from "./frame-common";
import { Page, View, Observable, EventData } from "../page"; import { Page, View, Observable, EventData } from "../page";
import { Transition } from "../transition"; import { Transition } from "../transition";
export * from "../page"; export * from "../page";
/** /**
* Represents the logical View unit that is responsible for navigation withing an application. * Represents the logical View unit that is responsible for navigation within an application.
* Typically an application will have a Frame object at a root level.
* Nested frames are supported, enabling hierarchical navigation scenarios. * Nested frames are supported, enabling hierarchical navigation scenarios.
*/ */
export class Frame extends View { export class Frame extends View {
@ -113,12 +113,13 @@ export class Frame extends View {
* @param entry to check * @param entry to check
*/ */
isCurrent(entry: BackstackEntry): boolean; isCurrent(entry: BackstackEntry): boolean;
/** /**
* @private * @private
* @param entry to set as current * @param entry to set as current
* @param isBack true when we set current because of back navigation. * @param navigationType
*/ */
setCurrent(entry: BackstackEntry, isBack: boolean): void; setCurrent(entry: BackstackEntry, navigationType: NavigationType): void;
/** /**
* @private * @private
*/ */
@ -143,6 +144,11 @@ export class Frame extends View {
* @private * @private
*/ */
_updateActionBar(page?: Page, disableNavBarAnimation?: boolean); _updateActionBar(page?: Page, disableNavBarAnimation?: boolean);
/**
* @private
* @param navigationContext
*/
public performNavigation(navigationContext: NavigationContext): void;
/** /**
* @private * @private
*/ */
@ -154,7 +160,7 @@ export class Frame extends View {
/** /**
* @private * @private
*/ */
_updateBackstack(entry: BackstackEntry, isBack: boolean): void; _updateBackstack(entry: BackstackEntry, navigationType: NavigationType): void;
/** /**
* @private * @private
*/ */
@ -167,10 +173,12 @@ export class Frame extends View {
* @private * @private
*/ */
_removeFromFrameStack(); _removeFromFrameStack();
/** /**
* @private * @private
* Represents the type of navigation.
*/ */
_isBack?: boolean; navigationType: NavigationType;
//@endprivate //@endprivate
/** /**
@ -275,6 +283,14 @@ export interface NavigationEntry extends ViewEntry {
clearHistory?: boolean; clearHistory?: boolean;
} }
/**
* Represents a context passed to navigation methods.
*/
export interface NavigationContext {
entry: BackstackEntry;
isBackNavigation: boolean;
}
/** /**
* Represents an object specifying a page navigation transition. * Represents an object specifying a page navigation transition.
*/ */

View File

@ -1,12 +1,18 @@
// Definitions. // Definitions.
import { iOSFrame as iOSFrameDefinition, BackstackEntry, NavigationTransition } from "."; import {
iOSFrame as iOSFrameDefinition, BackstackEntry, NavigationTransition
} from ".";
import { Page } from "../page"; import { Page } from "../page";
import { profile } from "../../profiling"; import { profile } from "../../profiling";
//Types. //Types.
import { FrameBase, View, layout, traceEnabled, traceWrite, traceCategories, isCategorySet } from "./frame-common"; import {
FrameBase, View, isCategorySet, layout, NavigationContext,
NavigationType, traceCategories, traceEnabled, traceWrite
} from "./frame-common";
import { _createIOSAnimatedTransitioning } from "./fragment.transitions"; import { _createIOSAnimatedTransitioning } from "./fragment.transitions";
import { createViewFromEntry } from "../builder";
import * as utils from "../../utils/utils"; import * as utils from "../../utils/utils";
export * from "./frame-common"; export * from "./frame-common";
@ -14,9 +20,10 @@ export * from "./frame-common";
const majorVersion = utils.ios.MajorVersion; const majorVersion = utils.ios.MajorVersion;
const ENTRY = "_entry"; const ENTRY = "_entry";
const DELEGATE = "_delegate";
const NAV_DEPTH = "_navDepth"; const NAV_DEPTH = "_navDepth";
const TRANSITION = "_transition"; const TRANSITION = "_transition";
const DELEGATE = "_delegate"; const NON_ANIMATED_TRANSITION = "non-animated";
let navDepth = -1; let navDepth = -1;
@ -46,18 +53,57 @@ export class Frame extends FrameBase {
return this._ios; return this._ios;
} }
public setCurrent(entry: BackstackEntry, isBack: boolean): void { public setCurrent(entry: BackstackEntry, navigationType: NavigationType): void {
const current = this._currentEntry; const current = this._currentEntry;
const currentEntryChanged = current !== entry; const currentEntryChanged = current !== entry;
if (currentEntryChanged) { if (currentEntryChanged) {
this._updateBackstack(entry, isBack); this._updateBackstack(entry, navigationType);
super.setCurrent(entry, isBack); super.setCurrent(entry, navigationType);
}
}
public _onLivesync(context?: ModuleContext): boolean {
if (traceEnabled()) {
traceWrite(`${this}._onLivesync(${JSON.stringify(context)})`, traceCategories.Livesync);
}
if (!this._currentEntry || !this._currentEntry.entry) {
return false;
}
if (context && context.type && context.path) {
// Set NavigationType.replace for HMR.
// When `viewDidAppear()` set to NavigationType.forward.
this.navigationType = NavigationType.replace;
const currentBackstackEntry = this._currentEntry;
const contextModuleName = utils.getModuleName(context.path);
const newPage = <Page>createViewFromEntry({ moduleName: contextModuleName });
const newBackstackEntry: BackstackEntry = {
entry: currentBackstackEntry.entry,
resolvedPage: newPage,
navDepth: currentBackstackEntry.navDepth,
fragmentTag: undefined
}
const navContext: NavigationContext = { entry: newBackstackEntry, isBackNavigation: false };
this.performNavigation(navContext);
return true;
} else {
// Fallback
return super._onLivesync();
} }
} }
@profile @profile
public _navigateCore(backstackEntry: BackstackEntry) { public _navigateCore(backstackEntry: BackstackEntry) {
// NavigationType.replace for HMR.
// Otherwise, default to NavigationType.forward.
const isReplace = this.navigationType === NavigationType.replace;
if (!isReplace) {
this.navigationType = NavigationType.forward;
}
super._navigateCore(backstackEntry); super._navigateCore(backstackEntry);
let viewController: UIViewController = backstackEntry.resolvedPage.ios; let viewController: UIViewController = backstackEntry.resolvedPage.ios;
@ -69,7 +115,9 @@ export class Frame extends FrameBase {
if (clearHistory) { if (clearHistory) {
navDepth = -1; navDepth = -1;
} }
navDepth++; if (!isReplace) {
navDepth++;
}
let navigationTransition: NavigationTransition; let navigationTransition: NavigationTransition;
let animated = this.currentPage ? this._getIsAnimatedNavigation(backstackEntry.entry) : false; let animated = this.currentPage ? this._getIsAnimatedNavigation(backstackEntry.entry) : false;
@ -81,7 +129,7 @@ export class Frame extends FrameBase {
} }
else { else {
//https://github.com/NativeScript/NativeScript/issues/1787 //https://github.com/NativeScript/NativeScript/issues/1787
viewController[TRANSITION] = { name: "non-animated" }; viewController[TRANSITION] = { name: NON_ANIMATED_TRANSITION };
} }
let nativeTransition = _getNativeTransition(navigationTransition, true); let nativeTransition = _getNativeTransition(navigationTransition, true);
@ -136,7 +184,8 @@ export class Frame extends FrameBase {
} }
// We should hide the current entry from the back stack. // We should hide the current entry from the back stack.
if (!Frame._isEntryBackstackVisible(this._currentEntry)) { // This is the case for HMR when NavigationType.replace.
if (!Frame._isEntryBackstackVisible(this._currentEntry) || isReplace) {
let newControllers = NSMutableArray.alloc<UIViewController>().initWithArray(this._ios.controller.viewControllers); let newControllers = NSMutableArray.alloc<UIViewController>().initWithArray(this._ios.controller.viewControllers);
if (newControllers.count === 0) { if (newControllers.count === 0) {
throw new Error("Wrong controllers count."); throw new Error("Wrong controllers count.");
@ -168,6 +217,7 @@ export class Frame extends FrameBase {
} }
public _goBackCore(backstackEntry: BackstackEntry) { public _goBackCore(backstackEntry: BackstackEntry) {
this.navigationType = NavigationType.back;
super._goBackCore(backstackEntry); super._goBackCore(backstackEntry);
navDepth = backstackEntry[NAV_DEPTH]; navDepth = backstackEntry[NAV_DEPTH];
@ -469,7 +519,7 @@ class UINavigationControllerImpl extends UINavigationController {
traceWrite(`UINavigationControllerImpl.popViewControllerAnimated(${animated}); transition: ${JSON.stringify(navigationTransition)}`, traceCategories.NativeLifecycle); traceWrite(`UINavigationControllerImpl.popViewControllerAnimated(${animated}); transition: ${JSON.stringify(navigationTransition)}`, traceCategories.NativeLifecycle);
} }
if (navigationTransition && navigationTransition.name === "non-animated") { if (navigationTransition && navigationTransition.name === NON_ANIMATED_TRANSITION) {
//https://github.com/NativeScript/NativeScript/issues/1787 //https://github.com/NativeScript/NativeScript/issues/1787
return super.popViewControllerAnimated(false); return super.popViewControllerAnimated(false);
} }
@ -493,7 +543,7 @@ class UINavigationControllerImpl extends UINavigationController {
traceWrite(`UINavigationControllerImpl.popToViewControllerAnimated(${viewController}, ${animated}); transition: ${JSON.stringify(navigationTransition)}`, traceCategories.NativeLifecycle); traceWrite(`UINavigationControllerImpl.popToViewControllerAnimated(${viewController}, ${animated}); transition: ${JSON.stringify(navigationTransition)}`, traceCategories.NativeLifecycle);
} }
if (navigationTransition && navigationTransition.name === "non-animated") { if (navigationTransition && navigationTransition.name === NON_ANIMATED_TRANSITION) {
//https://github.com/NativeScript/NativeScript/issues/1787 //https://github.com/NativeScript/NativeScript/issues/1787
return super.popToViewControllerAnimated(viewController, false); return super.popToViewControllerAnimated(viewController, false);
} }

View File

@ -99,6 +99,10 @@ export class PageBase extends ContentView implements PageDefinition {
}; };
} }
public _onLivesync(context?: ModuleContext): boolean {
return this.frame ? this.frame._onLivesync(context) : false;
}
@profile @profile
public onNavigatingTo(context: any, isBackNavigation: boolean, bindingContext?: any) { public onNavigatingTo(context: any, isBackNavigation: boolean, bindingContext?: any) {
this._navigationContext = context; this._navigationContext = context;
@ -190,4 +194,4 @@ export const androidStatusBarBackgroundProperty = new CssProperty<Style, Color>(
name: "androidStatusBarBackground", cssName: "android-status-bar-background", name: "androidStatusBarBackground", cssName: "android-status-bar-background",
equalityComparer: Color.equals, valueConverter: (v) => new Color(v) equalityComparer: Color.equals, valueConverter: (v) => new Color(v)
}); });
androidStatusBarBackgroundProperty.register(Style); androidStatusBarBackgroundProperty.register(Style);

View File

@ -1,11 +1,11 @@
// Definitions. // Definitions.
import { Frame } from "../frame"; import { Frame } from "../frame";
import { NavigationType } from "../frame/frame-common";
// Types. // Types.
import { ios as iosView } from "../core/view"; import { ios as iosView } from "../core/view";
import { import {
PageBase, View, layout, PageBase, View, layout, actionBarHiddenProperty, statusBarStyleProperty, Color
actionBarHiddenProperty, statusBarStyleProperty, Color
} from "./page-common"; } from "./page-common";
import { profile } from "../../profiling"; import { profile } from "../../profiling";
@ -20,7 +20,7 @@ const majorVersion = iosUtils.MajorVersion;
function isBackNavigationTo(page: Page, entry): boolean { function isBackNavigationTo(page: Page, entry): boolean {
const frame = page.frame; const frame = page.frame;
if (!frame) { if (!frame || frame.navigationType === NavigationType.replace) {
return false; return false;
} }
@ -133,14 +133,20 @@ class UIViewControllerImpl extends UIViewController {
const newEntry = this[ENTRY]; const newEntry = this[ENTRY];
let isBack: boolean; let isBack: boolean;
let navType = frame.navigationType;
// We are on the current page which happens when navigation is canceled so isBack should be false. // We are on the current page which happens when navigation is canceled so isBack should be false.
if (frame.currentPage === owner && frame._navigationQueue.length === 0) { if (navType !== NavigationType.replace && frame.currentPage === owner && frame._navigationQueue.length === 0) {
isBack = false; isBack = false;
navType = NavigationType.forward;
} else { } else {
isBack = isBackNavigationTo(owner, newEntry); isBack = isBackNavigationTo(owner, newEntry);
if (isBack) {
navType = NavigationType.back;
}
} }
frame.setCurrent(newEntry, isBack); frame.setCurrent(newEntry, navType);
frame.navigationType = isBack ? NavigationType.back : NavigationType.forward;
// If page was shown with custom animation - we need to set the navigationController.delegate to the animatedDelegate. // If page was shown with custom animation - we need to set the navigationController.delegate to the animatedDelegate.
frame.ios.controller.delegate = this[DELEGATE]; frame.ios.controller.delegate = this[DELEGATE];
@ -182,7 +188,7 @@ class UIViewControllerImpl extends UIViewController {
const frame = owner.frame; const frame = owner.frame;
// Skip navigation events if we are hiding because we are about to show a modal page, // Skip navigation events if we are hiding because we are about to show a modal page,
// or because we are closing a modal page, // or because we are closing a modal page,
// or because we are in tab and another controller is selected. // or because we are in tab and another controller is selected.
const tab = this.tabBarController; const tab = this.tabBarController;
if (owner.onNavigatingFrom && !owner._presentedViewController && !this.presentingViewController && frame && frame.currentPage === owner) { if (owner.onNavigatingFrom && !owner._presentedViewController && !this.presentingViewController && frame && frame.currentPage === owner) {

View File

@ -28,6 +28,11 @@ export function convertString(value: any): any {
return result; return result;
} }
export function getModuleName(path: string): string {
let moduleName = path.replace("./", "");
return moduleName.substring(0, moduleName.lastIndexOf("."));
}
export module layout { export module layout {
const MODE_SHIFT = 30; const MODE_SHIFT = 30;
const MODE_MASK = 0x3 << MODE_SHIFT; const MODE_MASK = 0x3 << MODE_SHIFT;
@ -148,4 +153,4 @@ export function hasDuplicates(arr: Array<any>): boolean {
export function eliminateDuplicates(arr: Array<any>): Array<any> { export function eliminateDuplicates(arr: Array<any>): Array<any> {
return Array.from(new Set(arr)); return Array.from(new Set(arr));
} }

View File

@ -185,13 +185,13 @@ export module ad {
export module ios { export module ios {
/** /**
* @deprecated use the respective native property directly * @deprecated use the respective native property directly
* *
* Checks if the property is a function and if it is, calls it on this. * Checks if the property is a function and if it is, calls it on this.
* Designed to support backward compatibility for methods that became properties. * Designed to support backward compatibility for methods that became properties.
* Will not work on delegates since it checks if the propertyValue is a function, and delegates are marshalled as functions. * Will not work on delegates since it checks if the propertyValue is a function, and delegates are marshalled as functions.
* Example: getter(NSRunLoop, NSRunLoop.currentRunLoop).runUntilDate(NSDate.dateWithTimeIntervalSinceNow(waitTime)); * Example: getter(NSRunLoop, NSRunLoop.currentRunLoop).runUntilDate(NSDate.dateWithTimeIntervalSinceNow(waitTime));
*/ */
export function getter<T>(_this: any, propertyValue: T | {(): T}): T; export function getter<T>(_this: any, propertyValue: T | { (): T }): T;
// Common properties between UILabel, UITextView and UITextField // Common properties between UILabel, UITextView and UITextField
export interface TextUIView { export interface TextUIView {
@ -255,7 +255,7 @@ export module ios {
* @param rootViewController The root UIViewController instance to start searching from (normally window.rootViewController). * @param rootViewController The root UIViewController instance to start searching from (normally window.rootViewController).
* Returns the visible UIViewController. * Returns the visible UIViewController.
*/ */
export function getVisibleViewController(rootViewController: any/* UIViewController*/ ): any/* UIViewController*/; export function getVisibleViewController(rootViewController: any/* UIViewController*/): any/* UIViewController*/;
} }
/** /**
@ -305,6 +305,12 @@ export function escapeRegexSymbols(source: string): string
*/ */
export function convertString(value: any): any export function convertString(value: any): any
/**
* Gets module name from path.
* @param path The module path.
*/
export function getModuleName(path: string): string
/** /**
* Sorts an array by using merge sort algorithm (which ensures stable sort since the built-in Array.sort() does not promise a stable sort). * Sorts an array by using merge sort algorithm (which ensures stable sort since the built-in Array.sort() does not promise a stable sort).
* @param arr - array to be sorted * @param arr - array to be sorted