diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6df7a483f..2ca487fb3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,25 @@
Cross Platform Modules Changelog
==============================
+
+## [4.2.1](https://github.com/NativeScript/NativeScript/compare/4.2.0...4.2.1) (2018-09-18)
+
+### Bug Fixes
+
+* enable reportProgress property for NativeScirpt Angular's HTTPClient ([#6154](https://github.com/NativeScript/NativeScript/issues/6154)) ([349850f](https://github.com/NativeScript/NativeScript/commit/349850f))
+* **ios:** listview scrollToIndex crash with async data ([#6182](https://github.com/NativeScript/NativeScript/issues/6182)) ([ca6cccb](https://github.com/NativeScript/NativeScript/commit/ca6cccb))
+* **ios:** touch delegate does not call base class touch methods ([#6113](https://github.com/NativeScript/NativeScript/pull/6113)) ([284cd5](https://github.com/NativeScript/NativeScript/commit/284cd5))
+* **ios:** TimePicker minuteInterval property ([#6116](https://github.com/NativeScript/NativeScript/issues/6116)) ([ca9bad6](https://github.com/NativeScript/NativeScript/commit/ca9bad6))
+* **android:** parallel navigation actions should not be triggered ([#6275](https://github.com/NativeScript/NativeScript/issues/6275)) ([405ccae](https://github.com/NativeScript/NativeScript/commit/405ccae))
+* **android:** HEAD request should return statusCode ([fe35567](https://github.com/NativeScript/NativeScript/commit/fe35567))
+* observable array reduce bug ([#6219](https://github.com/NativeScript/NativeScript/issues/6219)) ([b028dd9](https://github.com/NativeScript/NativeScript/commit/b028dd9))
+* Page and Frame isLoaded undefined checks ([#6255](https://github.com/NativeScript/NativeScript/issues/6255)) ([4a11cf9](https://github.com/NativeScript/NativeScript/commit/4a11cf9))
+* **android/platform:** reinitialise screen metrics on orientation change ([#6164](https://github.com/NativeScript/NativeScript/issues/6164)) ([040781c](https://github.com/NativeScript/NativeScript/commit/040781c))
+* **ios:** nowrap label measure in horizontal stack layout ([#6186](https://github.com/NativeScript/NativeScript/issues/6186)) ([efd5f8d](https://github.com/NativeScript/NativeScript/commit/efd5f8d))
+* **list-view:** Layout list-view items on request ([#6159](https://github.com/NativeScript/NativeScript/issues/6159)) ([115a4c1](https://github.com/NativeScript/NativeScript/commit/115a4c1))
+* **modals:** application activityBackPressed event not fired for modals ([#6261](https://github.com/NativeScript/NativeScript/issues/6261)) ([13d4f34](https://github.com/NativeScript/NativeScript/commit/13d4f34))
+
+
# [4.2.0](https://github.com/NativeScript/NativeScript/compare/4.1.1...4.2.0) (2018-08-08)
diff --git a/README.md b/README.md
index 35b55a981..9b44f162d 100644
--- a/README.md
+++ b/README.md
@@ -80,6 +80,6 @@ We [worked together with the Google Angular team](http://angularjs.blogspot.com/
## Get Help
-Please, use [github issues](https://github.com/NativeScript/NativeScript/issues) strictly for [reporting a bugs](CONTRIBUTING.md#bugs) or [requesting features](CONTRIBUTING.md#features). For general NativeScript questions and support, check out the [NativeScript community forum](https://discourse.nativescript.org/) or ask our experts in [NativeScript community Slack channel](http://developer.telerik.com/wp-login.php?action=slack-invitation).
+Please, use [github issues](https://github.com/NativeScript/NativeScript/issues) strictly for [reporting a bugs](CONTRIBUTING.md#bugs) or [requesting features](CONTRIBUTING.md#features). For general NativeScript questions and support, check out [Stack Overflow](https://stackoverflow.com/questions/tagged/nativescript) or ask our experts in [NativeScript community Slack channel](http://developer.telerik.com/wp-login.php?action=slack-invitation).

diff --git a/apps/app/ui-tests-app/events/main-page.ts b/apps/app/ui-tests-app/events/main-page.ts
index 2033fb6b5..ae563e442 100644
--- a/apps/app/ui-tests-app/events/main-page.ts
+++ b/apps/app/ui-tests-app/events/main-page.ts
@@ -14,6 +14,7 @@ export function loadExamples() {
examples.set("gestures", "events/gestures");
examples.set("touch", "events/touch-event");
examples.set("pan", "events/pan-event");
+ examples.set("swipe-passtrough", "events/swipe-event-passtrough");
examples.set("handlers", "events/handlers");
examples.set("console", "events/console");
examples.set("i61", "events/i61");
diff --git a/apps/app/ui-tests-app/events/swipe-event-passtrough.ts b/apps/app/ui-tests-app/events/swipe-event-passtrough.ts
new file mode 100644
index 000000000..3f768634a
--- /dev/null
+++ b/apps/app/ui-tests-app/events/swipe-event-passtrough.ts
@@ -0,0 +1,22 @@
+import { EventData } from "tns-core-modules/data/observable";
+import { Page } from "tns-core-modules/ui/page";
+import { SwipeGestureEventData } from "tns-core-modules/ui/gestures";
+import { TextView } from "tns-core-modules/ui/text-view";
+
+let outputText: TextView;
+export function navigatingTo(args: EventData) {
+ var page = args.object;
+ outputText = page.getViewById("output");
+}
+
+export function onSwipe(data: SwipeGestureEventData) {
+ const msg = `swipe state:${data.direction}`;
+ console.log(msg);
+ outputText.text += msg + "\n";
+}
+
+export function onTap(args) {
+ const msg = `tapEvent triggered`;
+ console.log(msg);
+ outputText.text += msg + "\n";
+}
diff --git a/apps/app/ui-tests-app/events/swipe-event-passtrough.xml b/apps/app/ui-tests-app/events/swipe-event-passtrough.xml
new file mode 100644
index 000000000..2a92d765b
--- /dev/null
+++ b/apps/app/ui-tests-app/events/swipe-event-passtrough.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/app/ui-tests-app/layouts/main-page.ts b/apps/app/ui-tests-app/layouts/main-page.ts
index 3111f135b..5d0b7adf2 100644
--- a/apps/app/ui-tests-app/layouts/main-page.ts
+++ b/apps/app/ui-tests-app/layouts/main-page.ts
@@ -21,6 +21,7 @@ export function loadExamples() {
examples.set("pgrid", "layouts-percent/grid");
examples.set("pstack", "layouts-percent/stack");
examples.set("pwrap", "layouts-percent/wrap");
+ examples.set("passThroughParent", "layouts/passThroughParent");
examples.set("stacklayout-6059", "layouts/stacklayout-6059");
return examples;
diff --git a/apps/app/ui-tests-app/layouts/passThroughParent.ts b/apps/app/ui-tests-app/layouts/passThroughParent.ts
new file mode 100644
index 000000000..1080e0b3c
--- /dev/null
+++ b/apps/app/ui-tests-app/layouts/passThroughParent.ts
@@ -0,0 +1,19 @@
+export function onOuterWrapLayoutTap() {
+ console.log("on outer wrap layout tap");
+}
+
+export function onStackLayoutThrowTap() {
+ throw new Error("Should not tap layout with IsPassThroughParentEnabled=true");
+}
+
+export function onUserInteractionDisabledTap() {
+ throw new Error("Should not tap button with IsUserInteractionEnabled=false");
+}
+
+export function onDisabledThrowTap() {
+ throw new Error("Should not tap button with IsEnabled=false");
+}
+
+export function onTap() {
+ console.log("on button tap");
+}
\ No newline at end of file
diff --git a/apps/app/ui-tests-app/layouts/passThroughParent.xml b/apps/app/ui-tests-app/layouts/passThroughParent.xml
new file mode 100644
index 000000000..d6795f742
--- /dev/null
+++ b/apps/app/ui-tests-app/layouts/passThroughParent.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build-docs.sh b/build-docs.sh
index e7bfa5a8d..1bea9fcee 100755
--- a/build-docs.sh
+++ b/build-docs.sh
@@ -27,7 +27,7 @@ extract_snippets() {
npm install markdown-snippet-injector
- for SNIPPET_DIR in {tests,apps,tns-core-modules} ; do
+ for SNIPPET_DIR in {tests/app,apps/app,tns-core-modules} ; do
echo "Extracting snippets from: $SNIPPET_DIR"
node "$BIN" --root="$SNIPPET_DIR" --target="$TARGET_DIR" \
--sourceext=".js|.ts|.xml|.html|.css"
diff --git a/e2e/config/appium.capabilities.json b/e2e/config/appium.capabilities.json
index c13c15e75..f7112699b 100644
--- a/e2e/config/appium.capabilities.json
+++ b/e2e/config/appium.capabilities.json
@@ -83,6 +83,18 @@
"fullReset": false,
"app": ""
},
+ "android28": {
+ "platformName": "Android",
+ "platformVersion": "28",
+ "deviceName": "Emulator-Api28-Google",
+ "avd": "Emulator-Api28-Google",
+ "lt": 60000,
+ "appActivity": "com.tns.NativeScriptActivity",
+ "newCommandTimeout": 720,
+ "noReset": true,
+ "fullReset": false,
+ "app": ""
+ },
"sim.iPhone7.iOS110": {
"platformName": "iOS",
"platformVersion": "11.2",
diff --git a/e2e/modal-navigation/app/android-back-button/android-back-button-page.ts b/e2e/modal-navigation/app/android-back-button/android-back-button-page.ts
new file mode 100644
index 000000000..dcf20cde3
--- /dev/null
+++ b/e2e/modal-navigation/app/android-back-button/android-back-button-page.ts
@@ -0,0 +1,32 @@
+import { android as androidApp, AndroidActivityBackPressedEventData } from "tns-core-modules/application";
+import { fromObject, Observable } from "tns-core-modules/data/observable"
+
+let context: Observable;
+function activityBackPressedCallback(args: AndroidActivityBackPressedEventData) {
+ if (context && context.get("shouldCancel")) {
+ context.set("shouldCancel", false);
+ context.set("message", "Back-pressed canceled!");
+ args.cancel = true;
+ }
+}
+export function onLoaded(args) {
+ console.log("back-button modal test loaded");
+ context = fromObject({
+ message: "First back-press will be canceled",
+ shouldCancel: true
+ });
+
+ args.object.bindingContext = context;
+
+ if (androidApp) {
+ androidApp.on("activityBackPressed", activityBackPressedCallback);
+ }
+}
+
+export function onUnloaded(args) {
+ console.log("back-button modal test unloaded");
+
+ if (androidApp) {
+ androidApp.off("activityBackPressed", activityBackPressedCallback);
+ }
+}
diff --git a/e2e/modal-navigation/app/android-back-button/android-back-button-page.xml b/e2e/modal-navigation/app/android-back-button/android-back-button-page.xml
new file mode 100644
index 000000000..3da48a7a9
--- /dev/null
+++ b/e2e/modal-navigation/app/android-back-button/android-back-button-page.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/e2e/modal-navigation/app/app.android.css b/e2e/modal-navigation/app/app.android.css
new file mode 100644
index 000000000..5151a975a
--- /dev/null
+++ b/e2e/modal-navigation/app/app.android.css
@@ -0,0 +1,19 @@
+#home-page {
+ font-size: 8;
+ margin: 0px;
+ padding: 0px;
+}
+
+#action-bar-home-page{
+ font-size: 10;
+ margin: 0px;
+ padding: 0px;
+}
+
+#home-page Button {
+ margin-bottom: 5px;
+ border-color: gray;
+ border-width: 2px;
+ border-radius: 8px;
+ padding: 0px;
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation/app/app.css b/e2e/modal-navigation/app/app.css
index ff57461c5..e69de29bb 100644
--- a/e2e/modal-navigation/app/app.css
+++ b/e2e/modal-navigation/app/app.css
@@ -1,13 +0,0 @@
-/*
-In NativeScript, the app.css file is where you place CSS rules that
-you would like to apply to your entire application. Check out
-http://docs.nativescript.org/ui/styling for a full list of the CSS
-selectors and properties you can use to style UI components.
-
-/*
-For example, the following CSS rule changes the font size of all UI
-components that have the btn class name.
-*/
-.btn {
- font-size: 18;
-}
diff --git a/e2e/modal-navigation/app/app.ios.css b/e2e/modal-navigation/app/app.ios.css
new file mode 100644
index 000000000..d123538c3
--- /dev/null
+++ b/e2e/modal-navigation/app/app.ios.css
@@ -0,0 +1,14 @@
+
+#home-page {
+ font-size: 13;
+ margin: 0px;
+ padding: 0px;
+}
+
+#home-page Button {
+ margin-bottom: 20px;
+ padding: 20px;
+ border-color: gray;
+ border-width: 2px;
+ border-radius: 8px;
+}
\ No newline at end of file
diff --git a/e2e/modal-navigation/app/home/home-page.ts b/e2e/modal-navigation/app/home/home-page.ts
index 6537e5b79..54cb458fa 100644
--- a/e2e/modal-navigation/app/home/home-page.ts
+++ b/e2e/modal-navigation/app/home/home-page.ts
@@ -57,6 +57,15 @@ export function onModalLayout(args: EventData) {
false);
}
+export function onAndroidBackEvents(args: EventData) {
+ const view = args.object as View;
+ view.showModal(
+ "android-back-button/android-back-button-page",
+ null,
+ () => console.log("android-back-button modal page layout closed"),
+ true, true, true);
+}
+
export function onModalTabView(args: EventData) {
const fullscreen = false;
const animated = false;
diff --git a/e2e/modal-navigation/app/home/home-page.xml b/e2e/modal-navigation/app/home/home-page.xml
index 4abbc0488..67a6197eb 100644
--- a/e2e/modal-navigation/app/home/home-page.xml
+++ b/e2e/modal-navigation/app/home/home-page.xml
@@ -1,23 +1,28 @@
-
-
-
+ navigatingFrom="onNavigatingFrom"
+ navigatedTo="onNavigatedTo"
+ navigatedFrom="onNavigatedFrom">
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/modal-navigation/app/modal-no-page/modal-no-page.xml b/e2e/modal-navigation/app/modal-no-page/modal-no-page.xml
index dde7de6cb..6005c6a74 100644
--- a/e2e/modal-navigation/app/modal-no-page/modal-no-page.xml
+++ b/e2e/modal-navigation/app/modal-no-page/modal-no-page.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/e2e/modal-navigation/e2e/android-back-button.e2e-spec.ts b/e2e/modal-navigation/e2e/android-back-button.e2e-spec.ts
new file mode 100644
index 000000000..e40afbe37
--- /dev/null
+++ b/e2e/modal-navigation/e2e/android-back-button.e2e-spec.ts
@@ -0,0 +1,39 @@
+import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium";
+import { Screen } from "./screen"
+import { assert } from "chai";
+
+const exampleAndroidBackBtnEvents = "Android Back Btn Events";
+
+describe("android-navigate-back", () => {
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async () => {
+ driver = await createDriver();
+ screen = new Screen(driver);
+ const btnShowNestedModalFrame = await driver.findElementByText(exampleAndroidBackBtnEvents);
+ await btnShowNestedModalFrame.click();
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ after(async () => {
+ await driver.resetApp();
+ });
+
+ it("should skip first navigate back", async function () {
+ if (driver.isIOS) {
+ this.skip();
+ }
+
+ await driver.navBack();
+ const textElement = await driver.findElementsByText("will cancel next back press: false", SearchOptions.contains, 10);
+ assert.isTrue(textElement !== null);
+ await driver.navBack();
+ await screen.loadedHome();
+ })
+})
\ No newline at end of file
diff --git a/e2e/modal-navigation/e2e/modal-frame.e2e-spec.ts b/e2e/modal-navigation/e2e/modal-frame.e2e-spec.ts
index 2c9b95206..79010a093 100644
--- a/e2e/modal-navigation/e2e/modal-frame.e2e-spec.ts
+++ b/e2e/modal-navigation/e2e/modal-frame.e2e-spec.ts
@@ -33,8 +33,7 @@ describe("modal-frame:", () => {
afterEach(async function () {
if (this.currentTest.state === "failed") {
- await driver.logPageSource(this.currentTest.title);
- await driver.logScreenshot(this.currentTest.title);
+ await driver.logTestArtifacts(this.currentTest.title);
await driver.resetApp();
await screen[root]();
}
diff --git a/e2e/modal-navigation/e2e/modal-layout.e2e-spec.ts b/e2e/modal-navigation/e2e/modal-layout.e2e-spec.ts
index 7145be191..189f94bd7 100644
--- a/e2e/modal-navigation/e2e/modal-layout.e2e-spec.ts
+++ b/e2e/modal-navigation/e2e/modal-layout.e2e-spec.ts
@@ -36,8 +36,7 @@ describe("modal-layout:", () => {
afterEach(async function () {
if (this.currentTest.state === "failed") {
- await driver.logPageSource(this.currentTest.title);
- await driver.logScreenshot(this.currentTest.title);
+ await driver.logTestArtifacts(this.currentTest.title);
await driver.resetApp();
await screen[root]();
}
diff --git a/e2e/modal-navigation/e2e/modal-page.e2e-spec.ts b/e2e/modal-navigation/e2e/modal-page.e2e-spec.ts
index 6ccec5546..8d891a284 100644
--- a/e2e/modal-navigation/e2e/modal-page.e2e-spec.ts
+++ b/e2e/modal-navigation/e2e/modal-page.e2e-spec.ts
@@ -35,8 +35,7 @@ describe("modal-page:", () => {
afterEach(async function () {
if (this.currentTest.state === "failed") {
- await driver.logPageSource(this.currentTest.title);
- await driver.logScreenshot(this.currentTest.title);
+ await driver.logTestArtifacts(this.currentTest.title);
await driver.resetApp();
await screen[root]();
}
diff --git a/e2e/modal-navigation/e2e/modal-tab.e2e-spec.ts b/e2e/modal-navigation/e2e/modal-tab.e2e-spec.ts
index f5a505760..8726e35ac 100644
--- a/e2e/modal-navigation/e2e/modal-tab.e2e-spec.ts
+++ b/e2e/modal-navigation/e2e/modal-tab.e2e-spec.ts
@@ -38,8 +38,7 @@ describe("modal-tab:", () => {
afterEach(async function () {
if (this.currentTest.state === "failed") {
- await driver.logPageSource(this.currentTest.title);
- await driver.logScreenshot(this.currentTest.title);
+ await driver.logTestArtifacts(this.currentTest.title);
await driver.resetApp();
await screen[root]();
}
diff --git a/e2e/modal-navigation/package.json b/e2e/modal-navigation/package.json
index 1b2392472..f2e994c31 100644
--- a/e2e/modal-navigation/package.json
+++ b/e2e/modal-navigation/package.json
@@ -23,10 +23,12 @@
"nativescript-dev-appium": "next",
"nativescript-dev-typescript": "next",
"nativescript-dev-webpack": "next",
+ "rimraf": "^2.6.2",
"typescript": "^3.0.3"
},
"scripts": {
- "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
- "e2e-watch": "tsc -p e2e --watch"
+ "e2e": "npm run clean-e2e && tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
+ "e2e-watch": "tsc -p e2e --watch",
+ "clean-e2e": "rimraf 'e2e/**/*.js' 'e2e/**/*.js.map' 'e2e/**/*.map'"
}
}
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 e780386dc..e6298d714 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
@@ -253,7 +253,15 @@ export abstract class ViewBase extends Observable {
public bind(options: BindingOptions, source?: Object): void;
public unbind(property: string): void;
+ /**
+ * Invalidates the layout of the view and triggers a new layout pass.
+ */
public requestLayout(): void;
+
+ /**
+ * Iterates over children of type ViewBase.
+ * @param callback Called for each child of type ViewBase. Iteration stops if this method returns falsy value.
+ */
public eachChild(callback: (child: ViewBase) => boolean): void;
public _addView(view: ViewBase, atIndex?: number): void;
diff --git a/tns-core-modules/ui/core/view-base/view-base.ts b/tns-core-modules/ui/core/view-base/view-base.ts
index e1196636f..6fe0a1bc8 100644
--- a/tns-core-modules/ui/core/view-base/view-base.ts
+++ b/tns-core-modules/ui/core/view-base/view-base.ts
@@ -595,13 +595,13 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
public loadView(view: ViewBase): void {
- if (!view.isLoaded) {
+ if (view && !view.isLoaded) {
view.callLoaded();
}
}
public unloadView(view: ViewBase): void {
- if (view.isLoaded) {
+ if (view && view.isLoaded) {
view.callUnloaded();
}
}
diff --git a/tns-core-modules/ui/core/view/view.android.ts b/tns-core-modules/ui/core/view/view.android.ts
index ec481b3b4..948763d86 100644
--- a/tns-core-modules/ui/core/view/view.android.ts
+++ b/tns-core-modules/ui/core/view/view.android.ts
@@ -1,7 +1,6 @@
// Definitions.
import { Point, CustomLayoutView as CustomLayoutViewDefinition, dip } from ".";
import { GestureTypes, GestureEventData } from "../../gestures";
-import { AndroidActivityBackPressedEventData } from "../../../application";
// Types.
import {
ViewCommon, layout, isEnabledProperty, originXProperty, originYProperty, automationTextProperty, isUserInteractionEnabledProperty,
@@ -22,6 +21,7 @@ import {
import { Background, ad as androidBackground } from "../../styling/background";
import { profile } from "../../../profiling";
import { topmost } from "../../frame/frame-stack";
+import { AndroidActivityBackPressedEventData, android as androidApp } from "../../../application";
export * from "./view-common";
@@ -72,12 +72,7 @@ function initializeTouchListener(): void {
onTouch(view: android.view.View, event: android.view.MotionEvent): boolean {
const owner = this.owner;
- for (let type in owner._gestureObservers) {
- let list = owner._gestureObservers[type];
- list.forEach(element => {
- element.androidOnTouchEvent(event);
- });
- }
+ owner.handleGestureTouch(event);
let nativeView = owner.nativeViewProtected;
if (!nativeView || !nativeView.onTouchEvent) {
@@ -112,6 +107,13 @@ function initializeDialogFragment() {
activity: view._context,
cancel: false,
};
+
+ // Fist fire application.android global event
+ androidApp.notify(args);
+ if (args.cancel) {
+ return;
+ }
+
view.notify(args);
if (!args.cancel && !view.onBackPressed()) {
@@ -178,7 +180,7 @@ function initializeDialogFragment() {
}
const owner = this.owner;
- if (!owner.isLoaded) {
+ if (owner && !owner.isLoaded) {
owner.callLoaded();
}
@@ -194,7 +196,7 @@ function initializeDialogFragment() {
}
const owner = this.owner;
- if (owner.isLoaded) {
+ if (owner && owner.isLoaded) {
owner.callUnloaded();
}
}
@@ -320,6 +322,18 @@ export class View extends ViewCommon {
return false;
}
+ public handleGestureTouch(event: android.view.MotionEvent): any {
+ for (let type in this._gestureObservers) {
+ let list = this._gestureObservers[type];
+ list.forEach(element => {
+ element.androidOnTouchEvent(event);
+ });
+ }
+ if (this.parent instanceof View) {
+ this.parent.handleGestureTouch(event);
+ }
+ }
+
private hasGestureObservers() {
return this._gestureObservers && Object.keys(this._gestureObservers).length > 0
}
@@ -343,15 +357,20 @@ export class View extends ViewCommon {
}
private setOnTouchListener() {
- if (this.nativeViewProtected && this.hasGestureObservers()) {
- this.touchListenerIsSet = true;
- if (this.nativeViewProtected.setClickable) {
- this.nativeViewProtected.setClickable(true);
- }
+ if (!this.nativeViewProtected || !this.hasGestureObservers()) {
+ return;
+ }
+
+ // do not set noop listener that handles the event (disabled listener) if IsUserInteractionEnabled is
+ // false as we might need the ability for the event to pass through to a parent view
+ initializeTouchListener();
+ this.touchListener = this.touchListener || new TouchListener(this);
+ this.nativeViewProtected.setOnTouchListener(this.touchListener);
- initializeTouchListener();
- this.touchListener = this.touchListener || new TouchListener(this);
- this.nativeViewProtected.setOnTouchListener(this.touchListener);
+ this.touchListenerIsSet = true;
+
+ if (this.nativeViewProtected.setClickable) {
+ this.nativeViewProtected.setClickable(this.isUserInteractionEnabled);
}
}
@@ -490,7 +509,7 @@ export class View extends ViewCommon {
public getLocationRelativeTo(otherView: ViewCommon): Point {
if (!this.nativeViewProtected || !this.nativeViewProtected.getWindowToken() ||
- !otherView.nativeViewProtected || !otherView.nativeViewProtected.getWindowToken() ||
+ !otherView || !otherView.nativeViewProtected || !otherView.nativeViewProtected.getWindowToken() ||
this.nativeViewProtected.getWindowToken() !== otherView.nativeViewProtected.getWindowToken()) {
return undefined;
}
@@ -591,15 +610,8 @@ export class View extends ViewCommon {
}
[isUserInteractionEnabledProperty.setNative](value: boolean) {
- if (!value) {
- initializeDisabledListener();
- // User interaction is disabled -- we stop it and we do not care whether someone wants to listen for gestures.
- this.nativeViewProtected.setOnTouchListener(disableUserInteractionListener);
- } else {
- this.setOnTouchListener();
- if (!this.touchListenerIsSet) {
- this.nativeViewProtected.setOnTouchListener(null);
- }
+ if (this.nativeViewProtected.setClickable) {
+ this.nativeViewProtected.setClickable(value);
}
}
diff --git a/tns-core-modules/ui/core/view/view.d.ts b/tns-core-modules/ui/core/view/view.d.ts
index 0ea4b37ea..83efcd184 100644
--- a/tns-core-modules/ui/core/view/view.d.ts
+++ b/tns-core-modules/ui/core/view/view.d.ts
@@ -585,7 +585,11 @@ export abstract class View extends ViewBase {
_getNativeViewsCount(): number;
_eachLayoutView(callback: (View) => void): void;
-
+
+ /**
+ * Iterates over children of type View.
+ * @param callback Called for each child of type View. Iteration stops if this method returns falsy value.
+ */
public eachChildView(callback: (view: View) => boolean): void;
//@private
diff --git a/tns-core-modules/ui/core/view/view.ios.ts b/tns-core-modules/ui/core/view/view.ios.ts
index e3023948e..485d25f9d 100644
--- a/tns-core-modules/ui/core/view/view.ios.ts
+++ b/tns-core-modules/ui/core/view/view.ios.ts
@@ -1,4 +1,4 @@
-// Definitions.
+// Definitions.
import { Point, View as ViewDefinition, dip } from ".";
import { ViewBase } from "../view-base";
import { booleanConverter, Property } from "../view";
diff --git a/tns-core-modules/ui/frame/frame-common.ts b/tns-core-modules/ui/frame/frame-common.ts
index 1c93c3d39..fe6c61ba4 100644
--- a/tns-core-modules/ui/frame/frame-common.ts
+++ b/tns-core-modules/ui/frame/frame-common.ts
@@ -531,6 +531,10 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
public _onLivesync(): boolean {
super._onLivesync();
+ if (!this._currentEntry || !this._currentEntry.entry) {
+ return false;
+ }
+
const currentEntry = this._currentEntry.entry;
const newEntry: NavigationEntry = {
animated: false,
diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts
index a8917fb23..fa0f2bace 100644
--- a/tns-core-modules/ui/frame/frame.android.ts
+++ b/tns-core-modules/ui/frame/frame.android.ts
@@ -9,7 +9,7 @@ import { Page } from "../page";
import * as application from "../../application";
import {
FrameBase, stack, goBack, View, Observable,
- traceEnabled, traceWrite, traceCategories
+ traceEnabled, traceWrite, traceCategories, traceError
} from "./frame-common";
import {
@@ -138,7 +138,7 @@ export class Frame extends FrameBase {
// In this case call _navigateCore in order to recreate the current fragment.
// Don't call navigate because it will fire navigation events.
// As JS instances are alive it is already done for the current page.
- if (!this.isLoaded || !this._attachedToWindow) {
+ if (!this.isLoaded || this._executingEntry || !this._attachedToWindow) {
return;
}
@@ -696,8 +696,23 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
}
const entry = this.entry;
+ if (!entry) {
+ traceError(`${fragment}.onCreateView: entry is null or undefined`);
+ return null;
+ }
+
const page = entry.resolvedPage;
+ if (!page) {
+ traceError(`${fragment}.onCreateView: entry has no resolvedPage`);
+ return null;
+ }
+
const frame = this.frame;
+ if (!frame) {
+ traceError(`${fragment}.onCreateView: this.frame is null or undefined`);
+ return null;
+ }
+
if (page.parent === frame) {
// If we are navigating to a page that was destroyed
// reinitialize its UI.
@@ -706,12 +721,12 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
page._setupUI(context);
}
} else {
- if (!this.frame._styleScope) {
+ if (!frame._styleScope) {
// Make sure page will have styleScope even if parents don't.
page._updateStyleScope();
}
- this.frame._addView(page);
+ frame._addView(page);
}
if (frame.isLoaded && !page.isLoaded) {
diff --git a/tns-core-modules/ui/layouts/layout-base-common.ts b/tns-core-modules/ui/layouts/layout-base-common.ts
index 167d0a363..104338337 100644
--- a/tns-core-modules/ui/layouts/layout-base-common.ts
+++ b/tns-core-modules/ui/layouts/layout-base-common.ts
@@ -106,6 +106,7 @@ export class LayoutBaseCommon extends CustomLayoutView implements LayoutBaseDefi
}
public clipToBounds: boolean;
+ public isPassThroughParentEnabled: boolean;
public _childIndexToNativeChildIndex(index?: number): number {
if (index === undefined) {
@@ -151,3 +152,6 @@ export class LayoutBaseCommon extends CustomLayoutView implements LayoutBaseDefi
export const clipToBoundsProperty = new Property({ name: "clipToBounds", defaultValue: true, valueConverter: booleanConverter });
clipToBoundsProperty.register(LayoutBaseCommon);
+
+export const isPassThroughParentEnabledProperty = new Property({ name: "isPassThroughParentEnabled", defaultValue: false, valueConverter: booleanConverter });
+isPassThroughParentEnabledProperty.register(LayoutBaseCommon);
diff --git a/tns-core-modules/ui/layouts/layout-base.android.ts b/tns-core-modules/ui/layouts/layout-base.android.ts
index 2f0eb60dd..17b70fde4 100644
--- a/tns-core-modules/ui/layouts/layout-base.android.ts
+++ b/tns-core-modules/ui/layouts/layout-base.android.ts
@@ -1,5 +1,5 @@
import {
- LayoutBaseCommon, clipToBoundsProperty,
+ LayoutBaseCommon, clipToBoundsProperty, isPassThroughParentEnabledProperty,
paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length
} from "./layout-base-common";
@@ -25,6 +25,10 @@ export class LayoutBase extends LayoutBaseCommon {
console.warn(`clipToBounds with value false is not supported on Android. You can use this.android.getParent().setClipChildren(false) as an alternative`);
}
+ [isPassThroughParentEnabledProperty.setNative](value: boolean) {
+ (this.nativeViewProtected).setPassThroughParent(value);
+ }
+
[paddingTopProperty.getDefault](): Length {
return { value: this._defaultPaddingTop, unit: "px" };
}
diff --git a/tns-core-modules/ui/layouts/layout-base.d.ts b/tns-core-modules/ui/layouts/layout-base.d.ts
index ded023594..4024b9209 100644
--- a/tns-core-modules/ui/layouts/layout-base.d.ts
+++ b/tns-core-modules/ui/layouts/layout-base.d.ts
@@ -96,6 +96,14 @@ export class LayoutBase extends CustomLayoutView {
* Gets or sets a value indicating whether to clip the content of this layout.
*/
clipToBounds: boolean;
+
+ /**
+ * Gets or sets a value indicating whether touch event should pass through to a parent view of the
+ * layout container in case an interactive child view did not handle it.
+ * Default value of this property is false. This does not affect the appearance of the view.
+ */
+ isPassThroughParentEnabled: boolean;
}
export const clipToBoundsProperty: Property;
+export const isPassThroughParentEnabledProperty: Property;
diff --git a/tns-core-modules/ui/layouts/layout-base.ios.ts b/tns-core-modules/ui/layouts/layout-base.ios.ts
index 61a09df2a..30283f394 100644
--- a/tns-core-modules/ui/layouts/layout-base.ios.ts
+++ b/tns-core-modules/ui/layouts/layout-base.ios.ts
@@ -1,10 +1,9 @@
-import { LayoutBaseCommon, clipToBoundsProperty, View, layout } from "./layout-base-common";
-import { ios as iosUtils } from "../../utils/utils";
+import {
+ LayoutBaseCommon, clipToBoundsProperty, isPassThroughParentEnabledProperty, View
+} from "./layout-base-common";
export * from "./layout-base-common";
-const majorVersion = iosUtils.MajorVersion;
-
export class LayoutBase extends LayoutBaseCommon {
nativeViewProtected: UIView;
@@ -37,4 +36,8 @@ export class LayoutBase extends LayoutBaseCommon {
[clipToBoundsProperty.setNative](value: boolean) {
this._setNativeClipToBounds();
}
+
+ [isPassThroughParentEnabledProperty.setNative](value: boolean) {
+ (this.nativeViewProtected).setPassThroughParent(value);
+ }
}
\ No newline at end of file
diff --git a/tns-core-modules/ui/styling/style-scope.ts b/tns-core-modules/ui/styling/style-scope.ts
index c1028201e..8e0df7dd1 100644
--- a/tns-core-modules/ui/styling/style-scope.ts
+++ b/tns-core-modules/ui/styling/style-scope.ts
@@ -357,7 +357,7 @@ export class CssState {
* As a result, at some point in time, the selectors matched have to be requerried from the style scope and applied to the view.
*/
public onChange(): void {
- if (this.view.isLoaded) {
+ if (this.view && this.view.isLoaded) {
this.unsubscribeFromDynamicUpdates();
this.updateMatch();
this.subscribeForDynamicUpdates();