mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 19:26:42 +08:00
feat(hmr): preserve navigation history on applying changes (#7146)
This commit is contained in:
5
tests/app/app/button-page.xml
Normal file
5
tests/app/app/button-page.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<Page loaded="onLoaded">
|
||||
<StackLayout>
|
||||
<Button id="button" text="button"></Button>
|
||||
</StackLayout>
|
||||
</Page>
|
3
tests/app/livesync/livesync-button-page.ts
Normal file
3
tests/app/livesync/livesync-button-page.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function onLoaded() {
|
||||
console.log("Button page loaded!");
|
||||
}
|
5
tests/app/livesync/livesync-button-page.xml
Normal file
5
tests/app/livesync/livesync-button-page.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<Page loaded="onLoaded">
|
||||
<StackLayout>
|
||||
<Button id="button" text="button"></Button>
|
||||
</StackLayout>
|
||||
</Page>
|
3
tests/app/livesync/livesync-label-page.ts
Normal file
3
tests/app/livesync/livesync-label-page.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function onLoaded() {
|
||||
console.log("Label page loaded!");
|
||||
}
|
5
tests/app/livesync/livesync-label-page.xml
Normal file
5
tests/app/livesync/livesync-label-page.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<Page loaded="onLoaded">
|
||||
<StackLayout>
|
||||
<Label id="label" text="label"></Label>
|
||||
</StackLayout>
|
||||
</Page>
|
@ -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 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 { 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 { Frame } from "tns-core-modules/ui/frame";
|
||||
|
||||
const appCssFileName = "./app/application.css";
|
||||
const appNewCssFileName = "./app/app-new.css";
|
||||
const appNewScssFileName = "./app/app-new.scss";
|
||||
const appJsFileName = "./app/app.js";
|
||||
const appTsFileName = "./app/app.ts";
|
||||
const mainPageCssFileName = "./app/main-page.css";
|
||||
const mainPageHtmlFileName = "./app/main-page.html";
|
||||
const mainPageXmlFileName = "./app/main-page.xml";
|
||||
const buttonCssFileName = "./app/button-page.css";
|
||||
|
||||
const buttonPageModuleName = "livesync/livesync-button-page";
|
||||
const buttonHtmlPageFileName = "./livesync/livesync-button-page.html";
|
||||
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 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() {
|
||||
_test_onLiveSync_ModuleContext_AppStyle(appNewCssFileName);
|
||||
}
|
||||
@ -48,29 +40,29 @@ export function test_onLiveSync_ModuleContext_ModuleUndefined() {
|
||||
_test_onLiveSync_ModuleContext({ type: "script", path: undefined });
|
||||
}
|
||||
|
||||
export function test_onLiveSync_ModuleContext_Script_AppJs() {
|
||||
_test_onLiveSync_ModuleContext({ type: "script", path: appJsFileName });
|
||||
export function test_onLiveSync_ModuleContext_Script_JsFile() {
|
||||
_test_onLiveSync_ModuleReplace({ type: "script", path: buttonJsPageFileName });
|
||||
}
|
||||
|
||||
export function test_onLiveSync_ModuleContext_Script_AppTs() {
|
||||
_test_onLiveSync_ModuleContext({ type: "script", path: appTsFileName });
|
||||
export function test_onLiveSync_ModuleContext_Script_TsFile() {
|
||||
_test_onLiveSync_ModuleReplace({ type: "script", path: buttonTsPageFileName });
|
||||
}
|
||||
|
||||
export function test_onLiveSync_ModuleContext_Style_MainPageCss() {
|
||||
_test_onLiveSync_ModuleContext_TypeStyle({ type: "style", path: mainPageCssFileName });
|
||||
export function test_onLiveSync_ModuleContext_Style_CssFile() {
|
||||
_test_onLiveSync_ModuleContext_TypeStyle({ type: "style", path: buttonCssFileName });
|
||||
}
|
||||
|
||||
export function test_onLiveSync_ModuleContext_Markup_MainPageHtml() {
|
||||
_test_onLiveSync_ModuleContext({ type: "markup", path: mainPageHtmlFileName });
|
||||
export function test_onLiveSync_ModuleContext_Markup_HtmlFile() {
|
||||
_test_onLiveSync_ModuleReplace({ type: "markup", path: buttonHtmlPageFileName });
|
||||
}
|
||||
|
||||
export function test_onLiveSync_ModuleContext_Markup_MainPageXml() {
|
||||
_test_onLiveSync_ModuleContext({ type: "markup", path: mainPageXmlFileName });
|
||||
export function test_onLiveSync_ModuleContext_Markup_XmlFile() {
|
||||
_test_onLiveSync_ModuleReplace({ type: "markup", path: buttonXmlPageFileName });
|
||||
}
|
||||
|
||||
export function setUpModule() {
|
||||
const mainPage = <Page>parse(mainPageTemplate);
|
||||
helper.navigate(() => mainPage);
|
||||
export function setUp() {
|
||||
const labelPage = <Page>createViewFromEntry(({ moduleName: labelPageModuleName }));
|
||||
helper.navigate(() => labelPage);
|
||||
}
|
||||
|
||||
export function tearDown() {
|
||||
@ -79,32 +71,29 @@ export function tearDown() {
|
||||
|
||||
function _test_onLiveSync_ModuleContext_AppStyle(styleFileName: string) {
|
||||
const pageBeforeNavigation = helper.getCurrentPage();
|
||||
const buttonPage = <Page>createViewFromEntry(({ moduleName: buttonPageModuleName }));
|
||||
helper.navigateWithHistory(() => buttonPage);
|
||||
|
||||
const page = <Page>parse(pageTemplate);
|
||||
helper.navigateWithHistory(() => page);
|
||||
app.setCssFileName(styleFileName);
|
||||
|
||||
const pageBeforeLiveSync = helper.getCurrentPage();
|
||||
global.__onLiveSync({ type: "style", path: styleFileName });
|
||||
|
||||
const pageAfterLiveSync = helper.getCurrentPage();
|
||||
TKUnit.waitUntilReady(() => pageAfterLiveSync.getViewById("button").style.color.toString() === green.toString());
|
||||
|
||||
TKUnit.assertTrue(pageAfterLiveSync.frame.canGoBack(), "App styles NOT applied - livesync navigation executed!");
|
||||
TKUnit.assertEqual(pageAfterLiveSync, pageBeforeLiveSync, "Pages are different - livesync navigation executed!");
|
||||
TKUnit.assertTrue(pageAfterLiveSync._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version NOT applied!");
|
||||
TKUnit.assertTrue(pageAfterLiveSync.frame.canGoBack(), "Can NOT go back!");
|
||||
TKUnit.assertEqual(pageAfterLiveSync, pageBeforeLiveSync, "Pages are different!");
|
||||
TKUnit.assertTrue(pageAfterLiveSync._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version is NOT applied!");
|
||||
|
||||
helper.goBack();
|
||||
|
||||
const pageAfterNavigationBack = helper.getCurrentPage();
|
||||
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!");
|
||||
}
|
||||
|
||||
function _test_onLiveSync_ModuleContext(context: { type, path }) {
|
||||
const page = <Page>parse(pageTemplate);
|
||||
helper.navigateWithHistory(() => page);
|
||||
const buttonPage = <Page>createViewFromEntry(({ moduleName: buttonPageModuleName }));
|
||||
helper.navigateWithHistory(() => buttonPage);
|
||||
global.__onLiveSync({ type: context.type, path: context.path });
|
||||
|
||||
TKUnit.waitUntilReady(() => !!frame.topmost());
|
||||
@ -113,27 +102,53 @@ function _test_onLiveSync_ModuleContext(context: { type, path }) {
|
||||
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 }) {
|
||||
const pageBeforeNavigation = helper.getCurrentPage();
|
||||
|
||||
const page = <Page>parse(pageTemplate);
|
||||
helper.navigateWithHistory(() => page);
|
||||
const buttonPage = <Page>createViewFromEntry(({ moduleName: buttonPageModuleName }));
|
||||
helper.navigateWithHistory(() => buttonPage);
|
||||
|
||||
const pageBeforeLiveSync = helper.getCurrentPage();
|
||||
pageBeforeLiveSync._moduleName = "main-page";
|
||||
pageBeforeLiveSync._moduleName = "button-page";
|
||||
|
||||
global.__onLiveSync({ type: context.type, path: context.path });
|
||||
const topmostFrame = frame.topmost();
|
||||
waitUntilLivesyncComplete(topmostFrame);
|
||||
|
||||
const pageAfterLiveSync = helper.getCurrentPage();
|
||||
TKUnit.waitUntilReady(() => pageAfterLiveSync.getViewById("button").style.color.toString() === green.toString());
|
||||
|
||||
TKUnit.assertTrue(pageAfterLiveSync.frame.canGoBack(), "Local styles NOT applied - livesync navigation executed!");
|
||||
TKUnit.assertEqual(pageAfterLiveSync, pageBeforeLiveSync, "Pages are different - livesync navigation executed!");
|
||||
TKUnit.assertTrue(pageAfterLiveSync._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version NOT applied!");
|
||||
TKUnit.assertTrue(pageAfterLiveSync.frame.canGoBack(), "Can NOT go back!");
|
||||
TKUnit.assertEqual(topmostFrame.backStack.length, 1, "Backstack is clean!");
|
||||
TKUnit.assertTrue(pageAfterLiveSync._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version is NOT applied!");
|
||||
|
||||
helper.goBack();
|
||||
|
||||
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 - livesync navigation executed!");
|
||||
TKUnit.assertEqual(pageBeforeNavigation, pageAfterNavigationBack, "Pages are different!");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,11 @@
|
||||
"repository": "<fill-your-repository-here>",
|
||||
"nativescript": {
|
||||
"id": "org.nativescript.UnitTestApp",
|
||||
"tns-ios": {
|
||||
"version": "5.2.0"
|
||||
},
|
||||
"tns-android": {
|
||||
"version": "5.2.1"
|
||||
"version": "5.3.1"
|
||||
},
|
||||
"tns-ios": {
|
||||
"version": "5.3.1"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -83,22 +83,20 @@ export function livesync(rootView: View, context?: ModuleContext) {
|
||||
events.notify(<EventData>{ eventName: "livesync", object: app });
|
||||
const liveSyncCore = global.__onLiveSyncCore;
|
||||
let reapplyAppStyles = false;
|
||||
let reapplyLocalStyles = false;
|
||||
|
||||
// ModuleContext is available only for Hot Module Replacement
|
||||
if (context && context.path) {
|
||||
const extensions = ["css", "scss"];
|
||||
const styleExtensions = ["css", "scss"];
|
||||
const appStylesFullFileName = getCssFileName();
|
||||
const appStylesFileName = appStylesFullFileName.substring(0, appStylesFullFileName.lastIndexOf(".") + 1);
|
||||
reapplyAppStyles = extensions.some(ext => context.path === appStylesFileName.concat(ext));
|
||||
if (!reapplyAppStyles) {
|
||||
reapplyLocalStyles = extensions.some(ext => context.path.endsWith(ext));
|
||||
}
|
||||
reapplyAppStyles = styleExtensions.some(ext => context.path === appStylesFileName.concat(ext));
|
||||
}
|
||||
|
||||
// Handle application styles
|
||||
if (reapplyAppStyles && rootView) {
|
||||
rootView._onCssStateChange();
|
||||
} else if (liveSyncCore) {
|
||||
reapplyLocalStyles ? liveSyncCore(context) : liveSyncCore();
|
||||
liveSyncCore(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
notify, launchEvent, resumeEvent, suspendEvent, exitEvent, lowMemoryEvent,
|
||||
orientationChangedEvent, setApplication, livesync, displayedEvent, getCssFileName
|
||||
} from "./application-common";
|
||||
import { ModuleType } from "../ui/core/view/view-common";
|
||||
|
||||
// First reexport so that app module is initialized.
|
||||
export * from "./application-common";
|
||||
@ -106,6 +107,7 @@ class IOSApplication implements IOSApplicationDefinition {
|
||||
get delegate(): typeof UIApplicationDelegate {
|
||||
return this._delegate;
|
||||
}
|
||||
|
||||
set delegate(value: typeof UIApplicationDelegate) {
|
||||
if (this._delegate !== value) {
|
||||
this._delegate = value;
|
||||
@ -228,8 +230,16 @@ class IOSApplication implements IOSApplicationDefinition {
|
||||
}
|
||||
|
||||
public _onLivesync(context?: ModuleContext): void {
|
||||
// If view can't handle livesync set window controller.
|
||||
if (this._rootView && !this._rootView._onLivesync(context)) {
|
||||
// Handle application root module
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -258,7 +268,6 @@ class IOSApplication implements IOSApplicationDefinition {
|
||||
this._window.makeKeyAndVisible();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const iosApp = new IOSApplication();
|
||||
|
11
tns-core-modules/trace/trace.d.ts
vendored
11
tns-core-modules/trace/trace.d.ts
vendored
@ -100,10 +100,11 @@ export module categories {
|
||||
export const Error: string;
|
||||
export const Animation: string;
|
||||
export const Transition: string;
|
||||
|
||||
export const All: string;
|
||||
export const Livesync: string;
|
||||
|
||||
export const separator: string;
|
||||
export const All: 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 {
|
||||
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 {
|
||||
handlerError(error: Error);
|
||||
@ -141,4 +142,4 @@ export interface ErrorHandler {
|
||||
|
||||
export class DefaultErrorHandler implements ErrorHandler {
|
||||
handlerError(error);
|
||||
}
|
||||
}
|
||||
|
@ -129,9 +129,22 @@ export module categories {
|
||||
export const Error = "Error";
|
||||
export const Animation = "Animation";
|
||||
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 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 {
|
||||
let result: string;
|
||||
|
@ -160,7 +160,7 @@ export abstract class ViewBase extends Observable {
|
||||
|
||||
/**
|
||||
* @deprecated use showModal with ShowModalOptions instead
|
||||
*
|
||||
*
|
||||
* 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 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
|
||||
*
|
||||
*
|
||||
* Shows the view passed as parameter as a modal view.
|
||||
* @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.
|
||||
@ -367,7 +367,7 @@ export abstract class ViewBase extends Observable {
|
||||
public _goToVisualState(state: string): void;
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
@ -7,8 +7,7 @@ import {
|
||||
import {
|
||||
ViewBase, Property, booleanConverter, eachDescendant, EventData, layout,
|
||||
getEventOrGestureName, traceEnabled, traceWrite, traceCategories,
|
||||
InheritedProperty,
|
||||
ShowModalOptions
|
||||
InheritedProperty, ShowModalOptions
|
||||
} from "../view-base";
|
||||
|
||||
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 {
|
||||
return (cls) => {
|
||||
cls.prototype.cssType = type;
|
||||
@ -138,12 +143,22 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
}
|
||||
|
||||
public _onLivesync(context?: ModuleContext): boolean {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`${this}._onLivesync(${JSON.stringify(context)})`, traceCategories.Livesync);
|
||||
}
|
||||
|
||||
_rootModalViews.forEach(v => v.closeModal());
|
||||
_rootModalViews.length = 0;
|
||||
|
||||
// Currently, we pass `context` only for style modules
|
||||
if (context && context.path) {
|
||||
return this.changeLocalStyles(context.path);
|
||||
if (context && context.type && context.path) {
|
||||
// Handle local styles
|
||||
if (context.type === ModuleType.style) {
|
||||
return this.changeLocalStyles(context.path);
|
||||
}
|
||||
// Handle module markup and script changes
|
||||
else {
|
||||
return this.changeModule(context);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -156,11 +171,16 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
// Do not execute frame navigation for a change in styles
|
||||
|
||||
// Do not reset activity/window content for local styles changes
|
||||
return true;
|
||||
}
|
||||
|
||||
private changeStyles(view: ViewBase, contextPath: string): boolean {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`${view}.${view._moduleName}`, traceCategories.Livesync);
|
||||
}
|
||||
|
||||
if (view._moduleName && contextPath.includes(view._moduleName)) {
|
||||
(<this>view).changeCssFile(contextPath);
|
||||
return true;
|
||||
@ -168,6 +188,23 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
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 {
|
||||
super._setupAsRootView(context);
|
||||
if (!this._styleScope) {
|
||||
|
@ -232,22 +232,22 @@ export function _getAnimatedEntries(frameId: number): Set<BackstackEntry> {
|
||||
export function _updateTransitions(entry: ExpandedEntry): void {
|
||||
const fragment = entry.fragment;
|
||||
const enterTransitionListener = entry.enterTransitionListener;
|
||||
if (enterTransitionListener) {
|
||||
if (enterTransitionListener && fragment) {
|
||||
fragment.setEnterTransition(enterTransitionListener.transition);
|
||||
}
|
||||
|
||||
const exitTransitionListener = entry.exitTransitionListener;
|
||||
if (exitTransitionListener) {
|
||||
if (exitTransitionListener && fragment) {
|
||||
fragment.setExitTransition(exitTransitionListener.transition);
|
||||
}
|
||||
|
||||
const reenterTransitionListener = entry.reenterTransitionListener;
|
||||
if (reenterTransitionListener) {
|
||||
if (reenterTransitionListener && fragment) {
|
||||
fragment.setReenterTransition(reenterTransitionListener.transition);
|
||||
}
|
||||
|
||||
const returnTransitionListener = entry.returnTransitionListener;
|
||||
if (returnTransitionListener) {
|
||||
if (returnTransitionListener && fragment) {
|
||||
fragment.setReturnTransition(returnTransitionListener.transition);
|
||||
}
|
||||
}
|
||||
@ -374,7 +374,7 @@ function getAnimationListener(): android.animation.Animator.AnimatorListener {
|
||||
|
||||
return AnimationListener;
|
||||
}
|
||||
|
||||
|
||||
function addToWaitingQueue(entry: ExpandedEntry): void {
|
||||
const frameId = entry.frameId;
|
||||
let entries = waitingQueue.get(frameId);
|
||||
@ -659,7 +659,7 @@ function setupAllAnimation(entry: ExpandedEntry, transition: Transition): void {
|
||||
setupExitAndPopEnterAnimation(entry, transition);
|
||||
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.
|
||||
const enterAnimator = <ExpandedAnimator>transition.createAndroidAnimator(AndroidTransitionType.enter);
|
||||
enterAnimator.transitionType = AndroidTransitionType.enter;
|
||||
@ -720,7 +720,7 @@ function transitionOrAnimationCompleted(entry: ExpandedEntry): void {
|
||||
if (entries.size === 0) {
|
||||
const frame = entry.resolvedPage.frame;
|
||||
// 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);
|
||||
completedEntries.delete(frameId);
|
||||
waitingQueue.delete(frameId);
|
||||
@ -730,8 +730,8 @@ function transitionOrAnimationCompleted(entry: ExpandedEntry): void {
|
||||
// Will be null if Frame is shown modally...
|
||||
// transitionOrAnimationCompleted fires again (probably bug in android).
|
||||
if (current) {
|
||||
const isBack = frame._isBack;
|
||||
setTimeout(() => frame.setCurrent(current, isBack));
|
||||
const navType = frame.navigationType;
|
||||
setTimeout(() => frame.setCurrent(current, navType));
|
||||
}
|
||||
} else {
|
||||
completedEntries.set(frameId, entry);
|
||||
|
@ -11,6 +11,12 @@ import { profile } from "../../profiling";
|
||||
import { frameStack, topmost as frameStackTopmost, _pushInFrameStack, _popFromFrameStack, _removeFromFrameStack } from "./frame-stack";
|
||||
export * from "../core/view";
|
||||
|
||||
export enum NavigationType {
|
||||
back,
|
||||
forward,
|
||||
replace
|
||||
}
|
||||
|
||||
function buildEntryFromArgs(arg: any): NavigationEntry {
|
||||
let entry: NavigationEntry;
|
||||
if (typeof arg === "string") {
|
||||
@ -48,6 +54,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
|
||||
public _isInFrameStack = false;
|
||||
public static defaultAnimatedNavigation = true;
|
||||
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.
|
||||
|
||||
@ -206,7 +213,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
|
||||
return this._currentEntry === entry;
|
||||
}
|
||||
|
||||
public setCurrent(entry: BackstackEntry, isBack: boolean): void {
|
||||
public setCurrent(entry: BackstackEntry, navigationType: NavigationType): void {
|
||||
const newPage = entry.resolvedPage;
|
||||
// In case we navigated forward to a page that was in the backstack
|
||||
// with clearHistory: true
|
||||
@ -217,6 +224,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
|
||||
|
||||
this._currentEntry = entry;
|
||||
|
||||
const isBack = navigationType === NavigationType.back;
|
||||
if (isBack) {
|
||||
this._pushInFrameStack();
|
||||
}
|
||||
@ -229,15 +237,18 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
|
||||
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);
|
||||
const current = this._currentEntry;
|
||||
|
||||
// Do nothing for Hot Module Replacement
|
||||
if (isBack) {
|
||||
const index = this._backStack.indexOf(entry);
|
||||
this._backStack.splice(index + 1).forEach(e => this._removeEntry(e));
|
||||
this._backStack.pop();
|
||||
} else {
|
||||
} else if (!isReplace) {
|
||||
if (entry.entry.clearHistory) {
|
||||
this._backStack.forEach(e => this._removeEntry(e));
|
||||
this._backStack.length = 0;
|
||||
@ -345,7 +356,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
|
||||
}
|
||||
|
||||
@profile
|
||||
private performNavigation(navigationContext: NavigationContext) {
|
||||
public performNavigation(navigationContext: NavigationContext) {
|
||||
const navContext = navigationContext.entry;
|
||||
this._executingEntry = navContext;
|
||||
this._onNavigatingTo(navContext, navigationContext.isBackNavigation);
|
||||
@ -563,35 +574,39 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
|
||||
return result;
|
||||
}
|
||||
|
||||
public _onLivesync(context?: ModuleContext): boolean {
|
||||
// Execute a navigation if not handled on `View` level
|
||||
if (!super._onLivesync(context)) {
|
||||
if (!this._currentEntry || !this._currentEntry.entry) {
|
||||
public _onLivesync(): boolean {
|
||||
// Reset activity/window content when:
|
||||
// + Changes are not handled on View
|
||||
// + 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
// Definitions.
|
||||
import {
|
||||
AndroidFrame as AndroidFrameDefinition, BackstackEntry,
|
||||
NavigationTransition, AndroidFragmentCallbacks, AndroidActivityCallbacks
|
||||
AndroidFrame as AndroidFrameDefinition, AndroidActivityCallbacks,
|
||||
AndroidFragmentCallbacks, BackstackEntry, NavigationTransition
|
||||
} from ".";
|
||||
import { ModuleType } from "../../ui/core/view/view-common";
|
||||
import { Page } from "../page";
|
||||
|
||||
// Types.
|
||||
import * as application from "../../application";
|
||||
import {
|
||||
FrameBase, stack, goBack, View, Observable,
|
||||
traceEnabled, traceWrite, traceCategories, traceError
|
||||
FrameBase, goBack, stack, NavigationContext, NavigationType,
|
||||
Observable, View, traceCategories, traceEnabled, traceError, traceWrite
|
||||
} from "./frame-common";
|
||||
|
||||
import {
|
||||
@ -21,6 +22,7 @@ import { profile } from "../../profiling";
|
||||
|
||||
// TODO: Remove this and get it from global to decouple builder for angular
|
||||
import { createViewFromEntry } from "../builder";
|
||||
import { getModuleName } from "../../utils/utils";
|
||||
|
||||
export * from "./frame-common";
|
||||
|
||||
@ -87,8 +89,16 @@ export function reloadPage(context?: ModuleContext): void {
|
||||
const callbacks: AndroidActivityCallbacks = activity[CALLBACKS];
|
||||
if (callbacks) {
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
@ -104,7 +114,6 @@ export class Frame extends FrameBase {
|
||||
private _containerViewId: number = -1;
|
||||
private _tearDownPending = false;
|
||||
private _attachedToWindow = false;
|
||||
public _isBack: boolean = true;
|
||||
private _cachedAnimatorState: AnimatorState;
|
||||
|
||||
constructor() {
|
||||
@ -263,11 +272,11 @@ export class Frame extends FrameBase {
|
||||
return newFragment;
|
||||
}
|
||||
|
||||
public setCurrent(entry: BackstackEntry, isBack: boolean): void {
|
||||
public setCurrent(entry: BackstackEntry, navigationType: NavigationType): void {
|
||||
const current = this._currentEntry;
|
||||
const currentEntryChanged = current !== entry;
|
||||
if (currentEntryChanged) {
|
||||
this._updateBackstack(entry, isBack);
|
||||
this._updateBackstack(entry, navigationType);
|
||||
|
||||
// If activity was destroyed we need to destroy fragment and UI
|
||||
// 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.
|
||||
this._processNavigationQueue(entry.resolvedPage);
|
||||
@ -330,10 +339,48 @@ export class Frame extends FrameBase {
|
||||
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
|
||||
public _navigateCore(newEntry: BackstackEntry) {
|
||||
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
|
||||
newEntry.frameId = this._android.frameId;
|
||||
@ -360,7 +407,10 @@ export class Frame extends FrameBase {
|
||||
navDepth = -1;
|
||||
}
|
||||
|
||||
navDepth++;
|
||||
if (!isReplace) {
|
||||
navDepth++;
|
||||
}
|
||||
|
||||
fragmentId++;
|
||||
const newFragmentTag = `fragment${fragmentId}[${navDepth}]`;
|
||||
const newFragment = this.createFragment(newEntry, newFragmentTag);
|
||||
@ -383,7 +433,7 @@ export class Frame extends FrameBase {
|
||||
}
|
||||
|
||||
public _goBackCore(backstackEntry: BackstackEntry) {
|
||||
this._isBack = true;
|
||||
this.navigationType = NavigationType.back;
|
||||
super._goBackCore(backstackEntry);
|
||||
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 {
|
||||
fragment[CALLBACKS] = new FragmentCallbacksImplementation();
|
||||
}
|
||||
}
|
||||
|
28
tns-core-modules/ui/frame/frame.d.ts
vendored
28
tns-core-modules/ui/frame/frame.d.ts
vendored
@ -3,14 +3,14 @@
|
||||
* @module "ui/frame"
|
||||
*/ /** */
|
||||
|
||||
import { NavigationType } from "./frame-common";
|
||||
import { Page, View, Observable, EventData } from "../page";
|
||||
import { Transition } from "../transition";
|
||||
|
||||
export * from "../page";
|
||||
|
||||
/**
|
||||
* Represents the logical View unit that is responsible for navigation withing an application.
|
||||
* Typically an application will have a Frame object at a root level.
|
||||
* Represents the logical View unit that is responsible for navigation within an application.
|
||||
* Nested frames are supported, enabling hierarchical navigation scenarios.
|
||||
*/
|
||||
export class Frame extends View {
|
||||
@ -113,12 +113,13 @@ export class Frame extends View {
|
||||
* @param entry to check
|
||||
*/
|
||||
isCurrent(entry: BackstackEntry): boolean;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @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
|
||||
*/
|
||||
@ -143,6 +144,11 @@ export class Frame extends View {
|
||||
* @private
|
||||
*/
|
||||
_updateActionBar(page?: Page, disableNavBarAnimation?: boolean);
|
||||
/**
|
||||
* @private
|
||||
* @param navigationContext
|
||||
*/
|
||||
public performNavigation(navigationContext: NavigationContext): void;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ -154,7 +160,7 @@ export class Frame extends View {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_updateBackstack(entry: BackstackEntry, isBack: boolean): void;
|
||||
_updateBackstack(entry: BackstackEntry, navigationType: NavigationType): void;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ -167,10 +173,12 @@ export class Frame extends View {
|
||||
* @private
|
||||
*/
|
||||
_removeFromFrameStack();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Represents the type of navigation.
|
||||
*/
|
||||
_isBack?: boolean;
|
||||
navigationType: NavigationType;
|
||||
//@endprivate
|
||||
|
||||
/**
|
||||
@ -275,6 +283,14 @@ export interface NavigationEntry extends ViewEntry {
|
||||
clearHistory?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a context passed to navigation methods.
|
||||
*/
|
||||
export interface NavigationContext {
|
||||
entry: BackstackEntry;
|
||||
isBackNavigation: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an object specifying a page navigation transition.
|
||||
*/
|
||||
|
@ -1,12 +1,18 @@
|
||||
// Definitions.
|
||||
import { iOSFrame as iOSFrameDefinition, BackstackEntry, NavigationTransition } from ".";
|
||||
import {
|
||||
iOSFrame as iOSFrameDefinition, BackstackEntry, NavigationTransition
|
||||
} from ".";
|
||||
import { Page } from "../page";
|
||||
import { profile } from "../../profiling";
|
||||
|
||||
//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 { createViewFromEntry } from "../builder";
|
||||
import * as utils from "../../utils/utils";
|
||||
|
||||
export * from "./frame-common";
|
||||
@ -14,9 +20,10 @@ export * from "./frame-common";
|
||||
const majorVersion = utils.ios.MajorVersion;
|
||||
|
||||
const ENTRY = "_entry";
|
||||
const DELEGATE = "_delegate";
|
||||
const NAV_DEPTH = "_navDepth";
|
||||
const TRANSITION = "_transition";
|
||||
const DELEGATE = "_delegate";
|
||||
const NON_ANIMATED_TRANSITION = "non-animated";
|
||||
|
||||
let navDepth = -1;
|
||||
|
||||
@ -46,18 +53,57 @@ export class Frame extends FrameBase {
|
||||
return this._ios;
|
||||
}
|
||||
|
||||
public setCurrent(entry: BackstackEntry, isBack: boolean): void {
|
||||
public setCurrent(entry: BackstackEntry, navigationType: NavigationType): void {
|
||||
const current = this._currentEntry;
|
||||
const currentEntryChanged = current !== entry;
|
||||
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
|
||||
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);
|
||||
|
||||
let viewController: UIViewController = backstackEntry.resolvedPage.ios;
|
||||
@ -69,7 +115,9 @@ export class Frame extends FrameBase {
|
||||
if (clearHistory) {
|
||||
navDepth = -1;
|
||||
}
|
||||
navDepth++;
|
||||
if (!isReplace) {
|
||||
navDepth++;
|
||||
}
|
||||
|
||||
let navigationTransition: NavigationTransition;
|
||||
let animated = this.currentPage ? this._getIsAnimatedNavigation(backstackEntry.entry) : false;
|
||||
@ -81,7 +129,7 @@ export class Frame extends FrameBase {
|
||||
}
|
||||
else {
|
||||
//https://github.com/NativeScript/NativeScript/issues/1787
|
||||
viewController[TRANSITION] = { name: "non-animated" };
|
||||
viewController[TRANSITION] = { name: NON_ANIMATED_TRANSITION };
|
||||
}
|
||||
|
||||
let nativeTransition = _getNativeTransition(navigationTransition, true);
|
||||
@ -136,7 +184,8 @@ export class Frame extends FrameBase {
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (newControllers.count === 0) {
|
||||
throw new Error("Wrong controllers count.");
|
||||
@ -168,6 +217,7 @@ export class Frame extends FrameBase {
|
||||
}
|
||||
|
||||
public _goBackCore(backstackEntry: BackstackEntry) {
|
||||
this.navigationType = NavigationType.back;
|
||||
super._goBackCore(backstackEntry);
|
||||
navDepth = backstackEntry[NAV_DEPTH];
|
||||
|
||||
@ -469,7 +519,7 @@ class UINavigationControllerImpl extends UINavigationController {
|
||||
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
|
||||
return super.popViewControllerAnimated(false);
|
||||
}
|
||||
@ -493,7 +543,7 @@ class UINavigationControllerImpl extends UINavigationController {
|
||||
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
|
||||
return super.popToViewControllerAnimated(viewController, false);
|
||||
}
|
||||
|
@ -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
|
||||
public onNavigatingTo(context: any, isBackNavigation: boolean, bindingContext?: any) {
|
||||
this._navigationContext = context;
|
||||
@ -190,4 +194,4 @@ export const androidStatusBarBackgroundProperty = new CssProperty<Style, Color>(
|
||||
name: "androidStatusBarBackground", cssName: "android-status-bar-background",
|
||||
equalityComparer: Color.equals, valueConverter: (v) => new Color(v)
|
||||
});
|
||||
androidStatusBarBackgroundProperty.register(Style);
|
||||
androidStatusBarBackgroundProperty.register(Style);
|
||||
|
@ -1,11 +1,11 @@
|
||||
// Definitions.
|
||||
import { Frame } from "../frame";
|
||||
import { NavigationType } from "../frame/frame-common";
|
||||
|
||||
// Types.
|
||||
import { ios as iosView } from "../core/view";
|
||||
import {
|
||||
PageBase, View, layout,
|
||||
actionBarHiddenProperty, statusBarStyleProperty, Color
|
||||
PageBase, View, layout, actionBarHiddenProperty, statusBarStyleProperty, Color
|
||||
} from "./page-common";
|
||||
|
||||
import { profile } from "../../profiling";
|
||||
@ -20,7 +20,7 @@ const majorVersion = iosUtils.MajorVersion;
|
||||
|
||||
function isBackNavigationTo(page: Page, entry): boolean {
|
||||
const frame = page.frame;
|
||||
if (!frame) {
|
||||
if (!frame || frame.navigationType === NavigationType.replace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -133,14 +133,20 @@ class UIViewControllerImpl extends UIViewController {
|
||||
const newEntry = this[ENTRY];
|
||||
|
||||
let isBack: boolean;
|
||||
let navType = frame.navigationType;
|
||||
// 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;
|
||||
navType = NavigationType.forward;
|
||||
} else {
|
||||
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.
|
||||
frame.ios.controller.delegate = this[DELEGATE];
|
||||
@ -182,7 +188,7 @@ class UIViewControllerImpl extends UIViewController {
|
||||
|
||||
const frame = owner.frame;
|
||||
// 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.
|
||||
const tab = this.tabBarController;
|
||||
if (owner.onNavigatingFrom && !owner._presentedViewController && !this.presentingViewController && frame && frame.currentPage === owner) {
|
||||
|
@ -28,6 +28,11 @@ export function convertString(value: any): any {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getModuleName(path: string): string {
|
||||
let moduleName = path.replace("./", "");
|
||||
return moduleName.substring(0, moduleName.lastIndexOf("."));
|
||||
}
|
||||
|
||||
export module layout {
|
||||
const MODE_SHIFT = 30;
|
||||
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> {
|
||||
return Array.from(new Set(arr));
|
||||
}
|
||||
}
|
||||
|
12
tns-core-modules/utils/utils.d.ts
vendored
12
tns-core-modules/utils/utils.d.ts
vendored
@ -185,13 +185,13 @@ export module ad {
|
||||
export module ios {
|
||||
/**
|
||||
* @deprecated use the respective native property directly
|
||||
*
|
||||
*
|
||||
* 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.
|
||||
* 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));
|
||||
*/
|
||||
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
|
||||
export interface TextUIView {
|
||||
@ -255,7 +255,7 @@ export module ios {
|
||||
* @param rootViewController The root UIViewController instance to start searching from (normally window.rootViewController).
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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).
|
||||
* @param arr - array to be sorted
|
||||
|
Reference in New Issue
Block a user