diff --git a/apps/app/ui-tests-app/css/progress-switch.xml b/apps/app/ui-tests-app/css/progress-switch.xml
index d928327ef..208d79649 100644
--- a/apps/app/ui-tests-app/css/progress-switch.xml
+++ b/apps/app/ui-tests-app/css/progress-switch.xml
@@ -1,7 +1,16 @@
+
+
+
+
+
+
+
+
+
diff --git a/apps/app/ui-tests-app/issues/issue-5125.ts b/apps/app/ui-tests-app/issues/issue-5125.ts
new file mode 100644
index 000000000..d97bfeca8
--- /dev/null
+++ b/apps/app/ui-tests-app/issues/issue-5125.ts
@@ -0,0 +1,18 @@
+import { NavigatedData } from "tns-core-modules/ui/page";
+import { Page } from "tns-core-modules/ui/page";
+import { Observable } from "tns-core-modules/data/observable";
+
+export class HomeViewModel extends Observable {
+ public stringValue = "test";
+ public stringNumericValue = "12345";
+ public numericValue = 12345;
+
+ constructor() {
+ super();
+ }
+}
+
+export function onNavigatingTo(args: NavigatedData) {
+ const page = args.object;
+ page.bindingContext = new HomeViewModel();
+}
\ No newline at end of file
diff --git a/apps/app/ui-tests-app/issues/issue-5125.xml b/apps/app/ui-tests-app/issues/issue-5125.xml
new file mode 100644
index 000000000..f23c26f97
--- /dev/null
+++ b/apps/app/ui-tests-app/issues/issue-5125.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/app/ui-tests-app/issues/main-page.ts b/apps/app/ui-tests-app/issues/main-page.ts
index 7afaa8783..587041d48 100644
--- a/apps/app/ui-tests-app/issues/main-page.ts
+++ b/apps/app/ui-tests-app/issues/main-page.ts
@@ -26,6 +26,7 @@ export function loadExamples() {
examples.set("tabview-with-scrollview_4022", "issues/tabview-with-scrollview_4022");
examples.set("3354-ios", "issues/issue-3354");
examples.set("4450", "issues/issue-4450");
+ examples.set("5125", "issues/issue-5125");
examples.set("5274", "issues/issue-5274");
examples.set("ng-repo-1599", "issues/issue-ng-repo-1599");
examples.set("ng-repo-1626", "issues/issue-ng-repo-1626");
diff --git a/tests/app/app/app.ts b/tests/app/app/app.ts
index 4e6e9330a..10dd7bb5c 100644
--- a/tests/app/app/app.ts
+++ b/tests/app/app/app.ts
@@ -8,24 +8,6 @@ else {
import * as application from "tns-core-modules/application";
-// Specify custom UIApplicationDelegate.
-/*
-class MyDelegate extends UIResponder implements UIApplicationDelegate {
- public static ObjCProtocols = [UIApplicationDelegate];
-
- applicationDidFinishLaunchingWithOptions(application: UIApplication, launchOptions: NSDictionary): boolean {
- console.log("applicationWillFinishLaunchingWithOptions: " + launchOptions)
- return true;
- }
-
- applicationDidBecomeActive(application: UIApplication): void {
- console.log("applicationDidBecomeActive: " + application)
- }
-}
-
-application.ios.delegate = MyDelegate;
-*/
-
if (application.ios) {
// Observe application notifications.
application.ios.addNotificationObserver(UIApplicationDidFinishLaunchingNotification, (notification: NSNotification) => {
@@ -34,6 +16,18 @@ if (application.ios) {
}
// Common events for both Android and iOS.
+application.on(application.displayedEvent, function (args: application.ApplicationEventData) {
+ (global).isDisplayedEventFired = true;
+
+ if (args.android) {
+ // For Android applications, args.android is an android activity class.
+ console.log("Activity: " + args.android);
+ } else if (args.ios) {
+ // For iOS applications, args.ios is UIApplication.
+ console.log("UIApplication: " + args.ios);
+ }
+});
+
application.on(application.launchEvent, function (args: application.ApplicationEventData) {
if (args.android) {
// For Android applications, args.android is an android.content.Intent class.
diff --git a/tests/app/app/main-page.css b/tests/app/app/button-page.css
similarity index 100%
rename from tests/app/app/main-page.css
rename to tests/app/app/button-page.css
diff --git a/tests/app/app/button-page.xml b/tests/app/app/button-page.xml
new file mode 100644
index 000000000..21dc437ae
--- /dev/null
+++ b/tests/app/app/button-page.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/tests/app/application/application-tests-common.ts b/tests/app/application/application-tests-common.ts
index 0e7a76e30..29be43736 100644
--- a/tests/app/application/application-tests-common.ts
+++ b/tests/app/application/application-tests-common.ts
@@ -13,11 +13,16 @@ if (app.android) {
import * as TKUnit from "../TKUnit";
-export var testInitialized = function () {
+export function testInitialized() {
if (platform.device.os === platform.platformNames.android) {
// we have the android defined
TKUnit.assert(app.android, "Application module not properly intialized");
} else if (platform.device.os === platform.platformNames.ios) {
TKUnit.assert(app.ios, "Application module not properly intialized");
}
-}
+}
+
+export function testDisplayedEvent() {
+ // global.isDisplayedEventFired flag is set in app.ts application.displayedEvent handler
+ TKUnit.assert((global).isDisplayedEventFired, "application.displayedEvent not fired");
+}
diff --git a/tests/app/livesync/livesync-button-page.ts b/tests/app/livesync/livesync-button-page.ts
new file mode 100644
index 000000000..d42484063
--- /dev/null
+++ b/tests/app/livesync/livesync-button-page.ts
@@ -0,0 +1,3 @@
+export function onLoaded() {
+ console.log("Button page loaded!");
+}
diff --git a/tests/app/livesync/livesync-button-page.xml b/tests/app/livesync/livesync-button-page.xml
new file mode 100644
index 000000000..21dc437ae
--- /dev/null
+++ b/tests/app/livesync/livesync-button-page.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/tests/app/livesync/livesync-label-page.ts b/tests/app/livesync/livesync-label-page.ts
new file mode 100644
index 000000000..2c2aa20c2
--- /dev/null
+++ b/tests/app/livesync/livesync-label-page.ts
@@ -0,0 +1,3 @@
+export function onLoaded() {
+ console.log("Label page loaded!");
+}
diff --git a/tests/app/livesync/livesync-label-page.xml b/tests/app/livesync/livesync-label-page.xml
new file mode 100644
index 000000000..483c877cc
--- /dev/null
+++ b/tests/app/livesync/livesync-label-page.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/tests/app/livesync/livesync-tests.ts b/tests/app/livesync/livesync-tests.ts
index da1cc9b2c..7eafac365 100644
--- a/tests/app/livesync/livesync-tests.ts
+++ b/tests/app/livesync/livesync-tests.ts
@@ -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 = `
-
-
-
-
- `;
-
-const pageTemplate = `
-
-
-
-
- `;
-
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 = parse(mainPageTemplate);
- helper.navigate(() => mainPage);
+export function setUp() {
+ const labelPage = 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 = createViewFromEntry(({ moduleName: buttonPageModuleName }));
+ helper.navigateWithHistory(() => buttonPage);
- const 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 = parse(pageTemplate);
- helper.navigateWithHistory(() => page);
+ const buttonPage = 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 = 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 = parse(pageTemplate);
- helper.navigateWithHistory(() => page);
+ const buttonPage = 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!");
-}
\ No newline at end of file
+}
+
+function waitUntilLivesyncComplete(frame: Frame) {
+ if (isAndroid) {
+ TKUnit.waitUntilReady(() => frame._executingEntry === null);
+ } else {
+ TKUnit.waitUntilReady(() => frame.currentPage.isLoaded);
+ }
+}
diff --git a/tests/app/ui/builder/builder-tests.ts b/tests/app/ui/builder/builder-tests.ts
index bfb65a2d0..f9cb5885d 100644
--- a/tests/app/ui/builder/builder-tests.ts
+++ b/tests/app/ui/builder/builder-tests.ts
@@ -1,26 +1,32 @@
import { path } from "tns-core-modules/file-system";
import { loadPage } from "tns-core-modules/ui/builder";
-import { assertEqual, assertNull } from "../../TKUnit";
+import { assertEqual, assertNull, assertThrows } from "../../TKUnit";
const COMPONENT_MODULE = "component-module";
+const MISSING_MODULE = "missing-module";
const LABEL = "label";
-function getViewComponent() {
- const moduleNamePath = path.join(__dirname, COMPONENT_MODULE);
- const fileName = path.join(__dirname, `${COMPONENT_MODULE}.xml`);
+function getViewComponent(componentModule: string) {
+ const moduleNamePath = path.join(__dirname, componentModule);
+ const fileName = path.join(__dirname, `${componentModule}.xml`);
const view = loadPage(moduleNamePath, fileName);
return view;
}
export function test_view_is_module_root_component() {
- const view = getViewComponent();
+ const view = getViewComponent(COMPONENT_MODULE);
const actualModule = view._moduleName;
assertEqual(actualModule, COMPONENT_MODULE, `View<${view}> is NOT root component of module <${COMPONENT_MODULE}>.`);
}
export function test_view_is_NOT_module_root_component() {
- const view = getViewComponent();
+ const view = getViewComponent(COMPONENT_MODULE);
const nestedView = view.getViewById(`${LABEL}`);
const undefinedModule = nestedView._moduleName;
assertNull(undefinedModule, `View<${nestedView}> should NOT be a root component of a module.`);
}
+
+export function test_load_component_from_missing_module_throws() {
+ assertThrows(() => getViewComponent(MISSING_MODULE),
+ "Loading component from a missing module SHOULD throw an error.")
+}
diff --git a/tests/app/xml-declaration/xml-declaration-tests.ts b/tests/app/xml-declaration/xml-declaration-tests.ts
index 65ff4208f..a6abf5415 100644
--- a/tests/app/xml-declaration/xml-declaration-tests.ts
+++ b/tests/app/xml-declaration/xml-declaration-tests.ts
@@ -35,9 +35,10 @@ export function test_parse_IsDefined() {
TKUnit.assertTrue(types.isFunction(builder.parse), "ui/builder should have parse method!");
};
-export function test_load_ShouldNotCrashWithInvalidFileName() {
- var v = builder.load(fs.path.join(__dirname, "mainPage1.xml"));
- TKUnit.assertTrue(types.isUndefined(v), "Expected result: undefined; Actual result: " + v + ";");
+export function test_load_ShouldThrowWithInvalidFileName() {
+ let fileName = fs.path.join(__dirname, "invalid-page.xml");
+ TKUnit.assertThrows(() => builder.load(fileName),
+ "Loading component from a missing module SHOULD throw an error.");
};
export function test_load_ShouldNotCrashWithoutExports() {
@@ -300,7 +301,7 @@ export function test_parse_ShouldSetCanvasAttachedProperties() {
var child = absLayout.getChildAt(0);
var left = absoluteLayoutModule.AbsoluteLayout.getLeft(child);
-
+
TKUnit.assert(Length.equals(left, Length.parse("1")), `Expected result for canvas left: 1; Actual result: ${(left).value};`)
var top = absoluteLayoutModule.AbsoluteLayout.getTop(child);
diff --git a/tests/package.json b/tests/package.json
index b821ee2b6..4be9613ee 100644
--- a/tests/package.json
+++ b/tests/package.json
@@ -5,11 +5,11 @@
"repository": "",
"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": {
diff --git a/tns-core-modules-widgets/android/gradlew b/tns-core-modules-widgets/android/gradlew
index 9d82f7891..cccdd3d51 100755
--- a/tns-core-modules-widgets/android/gradlew
+++ b/tns-core-modules-widgets/android/gradlew
@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
##############################################################################
##
@@ -6,42 +6,6 @@
##
##############################################################################
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
- echo "$*"
-}
-
-die ( ) {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
-esac
-
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -60,6 +24,46 @@ cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -85,7 +89,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -150,11 +154,19 @@ if $cygwin ; then
esac
fi
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=$(save "$@")
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/tns-core-modules-widgets/android/gradlew.bat b/tns-core-modules-widgets/android/gradlew.bat
index aec99730b..e95643d6a 100644
--- a/tns-core-modules-widgets/android/gradlew.bat
+++ b/tns-core-modules-widgets/android/gradlew.bat
@@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +46,9 @@ echo location of your Java installation.
goto fail
:init
-@rem Get command-line arguments, handling Windowz variants
+@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
diff --git a/tns-core-modules-widgets/android/widgets/gradlew b/tns-core-modules-widgets/android/widgets/gradlew
index 9d82f7891..cccdd3d51 100644
--- a/tns-core-modules-widgets/android/widgets/gradlew
+++ b/tns-core-modules-widgets/android/widgets/gradlew
@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
##############################################################################
##
@@ -6,42 +6,6 @@
##
##############################################################################
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
- echo "$*"
-}
-
-die ( ) {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
-esac
-
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -60,6 +24,46 @@ cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -85,7 +89,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -150,11 +154,19 @@ if $cygwin ; then
esac
fi
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=$(save "$@")
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/tns-core-modules-widgets/android/widgets/gradlew.bat b/tns-core-modules-widgets/android/widgets/gradlew.bat
index aec99730b..e95643d6a 100644
--- a/tns-core-modules-widgets/android/widgets/gradlew.bat
+++ b/tns-core-modules-widgets/android/widgets/gradlew.bat
@@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +46,9 @@ echo location of your Java installation.
goto fail
:init
-@rem Get command-line arguments, handling Windowz variants
+@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
diff --git a/tns-core-modules/application/application-common.ts b/tns-core-modules/application/application-common.ts
index b585578fa..20b39cd50 100644
--- a/tns-core-modules/application/application-common.ts
+++ b/tns-core-modules/application/application-common.ts
@@ -83,22 +83,20 @@ export function livesync(rootView: View, context?: ModuleContext) {
events.notify({ 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);
}
}
diff --git a/tns-core-modules/application/application.ios.ts b/tns-core-modules/application/application.ios.ts
index c776fdcf4..3d38f5889 100644
--- a/tns-core-modules/application/application.ios.ts
+++ b/tns-core-modules/application/application.ios.ts
@@ -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";
@@ -19,7 +20,7 @@ import { createViewFromEntry } from "../ui/builder";
import { ios as iosView, View } from "../ui/core/view";
import { Frame, NavigationEntry } from "../ui/frame";
import { ios } from "../utils/utils";
-import { profile, level as profilingLevel, Level } from "../profiling";
+import { profile } from "../profiling";
const getVisibleViewController = ios.getVisibleViewController;
@@ -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;
@@ -133,7 +135,7 @@ class IOSApplication implements IOSApplicationDefinition {
@profile
private didFinishLaunchingWithOptions(notification: NSNotification) {
- if (!displayedOnce && profilingLevel() >= Level.lifecycle) {
+ if (!displayedOnce) {
displayedLinkTarget = CADisplayLinkTarget.new();
displayedLink = CADisplayLink.displayLinkWithTargetSelector(displayedLinkTarget, "onDisplayed");
displayedLink.addToRunLoopForMode(NSRunLoop.mainRunLoop, NSDefaultRunLoopMode);
@@ -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();
diff --git a/tns-core-modules/trace/trace.d.ts b/tns-core-modules/trace/trace.d.ts
index 838a8729f..86e886e5b 100644
--- a/tns-core-modules/trace/trace.d.ts
+++ b/tns-core-modules/trace/trace.d.ts
@@ -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);
-}
\ No newline at end of file
+}
diff --git a/tns-core-modules/trace/trace.ts b/tns-core-modules/trace/trace.ts
index 497fcf11e..2d54bf33e 100644
--- a/tns-core-modules/trace/trace.ts
+++ b/tns-core-modules/trace/trace.ts
@@ -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;
diff --git a/tns-core-modules/ui/builder/builder.ts b/tns-core-modules/ui/builder/builder.ts
index d3dd8e099..454c33971 100644
--- a/tns-core-modules/ui/builder/builder.ts
+++ b/tns-core-modules/ui/builder/builder.ts
@@ -60,7 +60,9 @@ export function load(pathOrOptions: string | LoadOptions, context?: any): View {
export function loadPage(moduleNamePath: string, fileName: string, context?: any): View {
const componentModule = loadInternal(fileName, context, moduleNamePath);
const componentView = componentModule && componentModule.component;
- markAsModuleRoot(componentView, moduleNamePath);
+ if (componentView && moduleNamePath) {
+ markAsModuleRoot(componentView, moduleNamePath);
+ }
return componentView;
}
@@ -164,6 +166,10 @@ function loadInternal(fileName: string, context?: any, moduleNamePath?: string):
(componentModule.component).exports = context;
}
+ if (!componentModule) {
+ throw new Error("Failed to load component from module: " + filePathRelativeToApp + " or file: " + fileName);
+ }
+
return componentModule;
}
diff --git a/tns-core-modules/ui/core/view-base/view-base.d.ts b/tns-core-modules/ui/core/view-base/view-base.d.ts
index cbd910d46..b77b9e81f 100644
--- a/tns-core-modules/ui/core/view-base/view-base.d.ts
+++ b/tns-core-modules/ui/core/view-base/view-base.d.ts
@@ -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.
*/
diff --git a/tns-core-modules/ui/core/view/view-common.ts b/tns-core-modules/ui/core/view/view-common.ts
index 9dfab6815..a9c627a84 100644
--- a/tns-core-modules/ui/core/view/view-common.ts
+++ b/tns-core-modules/ui/core/view/view-common.ts
@@ -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)) {
(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) {
diff --git a/tns-core-modules/ui/frame/fragment.transitions.android.ts b/tns-core-modules/ui/frame/fragment.transitions.android.ts
index d192705b8..70f1fba7b 100644
--- a/tns-core-modules/ui/frame/fragment.transitions.android.ts
+++ b/tns-core-modules/ui/frame/fragment.transitions.android.ts
@@ -232,22 +232,22 @@ export function _getAnimatedEntries(frameId: number): Set {
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 = 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);
diff --git a/tns-core-modules/ui/frame/frame-common.ts b/tns-core-modules/ui/frame/frame-common.ts
index 6c55fcdb2..1346385e0 100644
--- a/tns-core-modules/ui/frame/frame-common.ts
+++ b/tns-core-modules/ui/frame/frame-common.ts
@@ -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;
}
}
diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts
index f878f1709..0192ec102 100644
--- a/tns-core-modules/ui/frame/frame.android.ts
+++ b/tns-core-modules/ui/frame/frame.android.ts
@@ -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 = 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();
-}
\ No newline at end of file
+}
diff --git a/tns-core-modules/ui/frame/frame.d.ts b/tns-core-modules/ui/frame/frame.d.ts
index 5983e9aa1..36fdc6b95 100644
--- a/tns-core-modules/ui/frame/frame.d.ts
+++ b/tns-core-modules/ui/frame/frame.d.ts
@@ -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.
*/
diff --git a/tns-core-modules/ui/frame/frame.ios.ts b/tns-core-modules/ui/frame/frame.ios.ts
index 4fcbf0902..d85cc5189 100644
--- a/tns-core-modules/ui/frame/frame.ios.ts
+++ b/tns-core-modules/ui/frame/frame.ios.ts
@@ -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 = 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().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);
}
diff --git a/tns-core-modules/ui/gestures/gestures.ios.ts b/tns-core-modules/ui/gestures/gestures.ios.ts
index b9dd31450..f2753760a 100644
--- a/tns-core-modules/ui/gestures/gestures.ios.ts
+++ b/tns-core-modules/ui/gestures/gestures.ios.ts
@@ -351,22 +351,30 @@ class TouchGestureRecognizer extends UIGestureRecognizer {
touchesBeganWithEvent(touches: NSSet, event: any): void {
this.executeCallback(TouchAction.down, touches, event);
- this.view.touchesBeganWithEvent(touches, event);
+ if (this.view) {
+ this.view.touchesBeganWithEvent(touches, event);
+ }
}
touchesMovedWithEvent(touches: NSSet, event: any): void {
this.executeCallback(TouchAction.move, touches, event);
- this.view.touchesMovedWithEvent(touches, event);
+ if (this.view) {
+ this.view.touchesMovedWithEvent(touches, event);
+ }
}
touchesEndedWithEvent(touches: NSSet, event: any): void {
this.executeCallback(TouchAction.up, touches, event);
- this.view.touchesEndedWithEvent(touches, event);
+ if (this.view) {
+ this.view.touchesEndedWithEvent(touches, event);
+ }
}
touchesCancelledWithEvent(touches: NSSet, event: any): void {
this.executeCallback(TouchAction.cancel, touches, event);
- this.view.touchesCancelledWithEvent(touches, event);
+ if (this.view) {
+ this.view.touchesCancelledWithEvent(touches, event);
+ }
}
private executeCallback(action: string, touches: NSSet, event: any): void {
diff --git a/tns-core-modules/ui/page/page-common.ts b/tns-core-modules/ui/page/page-common.ts
index 28814e186..f8c152c71 100644
--- a/tns-core-modules/ui/page/page-common.ts
+++ b/tns-core-modules/ui/page/page-common.ts
@@ -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