diff --git a/CodingConvention.md b/CodingConvention.md index 80f1e0127..826e49bd6 100644 --- a/CodingConvention.md +++ b/CodingConvention.md @@ -279,7 +279,7 @@ if (a) return "winning"; ```TypeScript -if(condition) { +if (condition) { console.log("winning"); } @@ -293,15 +293,15 @@ if (!condition) { ```TypeScript -if(condition === true) { +if (condition === true) { console.log("losing"); } -if(condition !== true) { +if (condition !== true) { console.log("losing"); } -if(condition !== false) { +if (condition !== false) { console.log("losing"); } @@ -314,7 +314,7 @@ Do not use the **Yoda Conditions** when writing boolean expressions: ```TypeScript let num; -if(num >= 0) { +if (num >= 0) { console.log("winning"); } ``` @@ -323,14 +323,14 @@ if(num >= 0) { ```TypeScript let num; -if(0 <= num) { +if (0 <= num) { console.log("losing"); } ``` **NOTE** It is OK to use constants on the left when comparing for a range. ```TypeScript -if(0 <= num && num <= 100) { +if (0 <= num && num <= 100) { console.log("winning"); } ``` diff --git a/LICENSE b/LICENSE index 9d63a0a67..061c44028 100755 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright (c) 2015-2018 Progress Software Corporation + Copyright (c) 2015-2019 Progress Software Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/apps/app/ui-tests-app/app.ts b/apps/app/ui-tests-app/app.ts index cdeed4d82..e12bb5d9d 100644 --- a/apps/app/ui-tests-app/app.ts +++ b/apps/app/ui-tests-app/app.ts @@ -81,6 +81,13 @@ application.on(application.uncaughtErrorEvent, function(args: application.Unhand console.log("### stack: " + args.error.stack); }); +application.on(application.discardedErrorEvent, function(args: application.DiscardedErrorEventData) { + console.log("### [Discarded] NativeScriptError: " + args.error); + console.log("### [Discarded] nativeException: " + (args.error).nativeException); + console.log("### [Discarded] stackTrace: " + (args.error).stackTrace); + console.log("### [Discarded] stack: " + args.error.stack); +}); + application.setCssFileName("ui-tests-app/app.css"); application.start({ moduleName: "ui-tests-app/main-page" }); diff --git a/e2e/config/mocha.opts b/e2e/config/mocha.opts index 796ec4724..a10a923f7 100644 --- a/e2e/config/mocha.opts +++ b/e2e/config/mocha.opts @@ -1,4 +1,4 @@ ---timeout 80000 +--timeout 300000 --recursive e2e --reporter mocha-multi --reporter-options spec=-,mocha-junit-reporter=test-results.xml \ No newline at end of file diff --git a/e2e/modal-navigation/e2e/android-back-button.e2e-spec.ts b/e2e/modal-navigation/e2e/android-back-button.e2e-spec.ts index e40afbe37..979728a6d 100644 --- a/e2e/modal-navigation/e2e/android-back-button.e2e-spec.ts +++ b/e2e/modal-navigation/e2e/android-back-button.e2e-spec.ts @@ -1,5 +1,5 @@ import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium"; -import { Screen } from "./screen" +import { Screen, driverDefaultWaitTime, elementDefaultWaitTimeInSeconds } from "./screen" import { assert } from "chai"; const exampleAndroidBackBtnEvents = "Android Back Btn Events"; @@ -10,6 +10,7 @@ describe("android-navigate-back", () => { before(async () => { driver = await createDriver(); + driver.defaultWaitTime = driverDefaultWaitTime; screen = new Screen(driver); const btnShowNestedModalFrame = await driver.findElementByText(exampleAndroidBackBtnEvents); await btnShowNestedModalFrame.click(); @@ -31,7 +32,7 @@ describe("android-navigate-back", () => { } await driver.navBack(); - const textElement = await driver.findElementsByText("will cancel next back press: false", SearchOptions.contains, 10); + const textElement = await driver.findElementsByText("will cancel next back press: false", SearchOptions.contains, elementDefaultWaitTimeInSeconds); assert.isTrue(textElement !== null); await driver.navBack(); await screen.loadedHome(); diff --git a/e2e/modal-navigation/e2e/modal-frame.e2e-spec.ts b/e2e/modal-navigation/e2e/modal-frame.e2e-spec.ts index 79010a093..e4438c8ba 100644 --- a/e2e/modal-navigation/e2e/modal-frame.e2e-spec.ts +++ b/e2e/modal-navigation/e2e/modal-frame.e2e-spec.ts @@ -1,5 +1,5 @@ import { AppiumDriver, createDriver } from "nativescript-dev-appium"; -import { Screen } from "./screen" +import { Screen, driverDefaultWaitTime } from "./screen" import { roots, modalFrameBackground, @@ -17,6 +17,7 @@ describe("modal-frame:", () => { before(async () => { driver = await createDriver(); + driver.defaultWaitTime = driverDefaultWaitTime; screen = new Screen(driver); }); diff --git a/e2e/modal-navigation/e2e/modal-layout.e2e-spec.ts b/e2e/modal-navigation/e2e/modal-layout.e2e-spec.ts index 189f94bd7..5222d0229 100644 --- a/e2e/modal-navigation/e2e/modal-layout.e2e-spec.ts +++ b/e2e/modal-navigation/e2e/modal-layout.e2e-spec.ts @@ -1,5 +1,5 @@ import { AppiumDriver, createDriver } from "nativescript-dev-appium"; -import { Screen } from "./screen" +import { Screen, driverDefaultWaitTime } from "./screen" import { roots, modalFrameBackground, @@ -17,6 +17,7 @@ describe("modal-layout:", () => { before(async () => { driver = await createDriver(); + driver.defaultWaitTime = driverDefaultWaitTime; screen = new Screen(driver); }); diff --git a/e2e/modal-navigation/e2e/modal-page.e2e-spec.ts b/e2e/modal-navigation/e2e/modal-page.e2e-spec.ts index 8d891a284..e2d0462ed 100644 --- a/e2e/modal-navigation/e2e/modal-page.e2e-spec.ts +++ b/e2e/modal-navigation/e2e/modal-page.e2e-spec.ts @@ -1,5 +1,5 @@ import { AppiumDriver, createDriver } from "nativescript-dev-appium"; -import { Screen } from "./screen" +import { Screen, driverDefaultWaitTime } from "./screen" import { roots, modalPageBackground, @@ -16,6 +16,7 @@ describe("modal-page:", () => { before(async () => { driver = await createDriver(); + driver.defaultWaitTime = driverDefaultWaitTime; screen = new Screen(driver); }); diff --git a/e2e/modal-navigation/e2e/modal-tab.e2e-spec.ts b/e2e/modal-navigation/e2e/modal-tab.e2e-spec.ts index 8726e35ac..8b784c1bb 100644 --- a/e2e/modal-navigation/e2e/modal-tab.e2e-spec.ts +++ b/e2e/modal-navigation/e2e/modal-tab.e2e-spec.ts @@ -1,5 +1,5 @@ import { AppiumDriver, createDriver } from "nativescript-dev-appium"; -import { Screen } from "./screen" +import { Screen, driverDefaultWaitTime } from "./screen" import { roots, modalFrameBackground, @@ -19,6 +19,7 @@ describe("modal-tab:", () => { before(async () => { driver = await createDriver(); + driver.defaultWaitTime = driverDefaultWaitTime; screen = new Screen(driver); }); diff --git a/e2e/modal-navigation/e2e/screen.ts b/e2e/modal-navigation/e2e/screen.ts index d2b4bb58a..8b28e6915 100644 --- a/e2e/modal-navigation/e2e/screen.ts +++ b/e2e/modal-navigation/e2e/screen.ts @@ -28,6 +28,9 @@ const closeModalNested = "Close Modal Nested"; const closeModal = "Close Modal"; const goBack = "Go Back"; +export const driverDefaultWaitTime = 10000; +export const elementDefaultWaitTimeInSeconds = 10; + export class Screen { private _driver: AppiumDriver @@ -37,30 +40,30 @@ export class Screen { } loadedHome = async () => { - const lblHome = await this._driver.findElementByText(home); + const lblHome = await this._driver.waitForElement(home); assert.isTrue(await lblHome.isDisplayed()); console.log(home + " loaded!"); } resetFrameRootView = async () => { console.log("Setting frame root ..."); - const btnResetFrameRootView = await this._driver.findElementByText(resetFrameRootView); + const btnResetFrameRootView = await this._driver.waitForElement(resetFrameRootView); await btnResetFrameRootView.tap(); } resetLayoutRootView = async () => { console.log("Setting layout root ..."); - const btnResetLayoutRootView = await this._driver.findElementByText(resetLayoutRootView); + const btnResetLayoutRootView = await this._driver.waitForElement(resetLayoutRootView); await btnResetLayoutRootView.tap(); } resetTabRootView = async () => { - const btnResetTabRootView = await this._driver.findElementByText(resetTabRootView); + const btnResetTabRootView = await this._driver.waitForElement(resetTabRootView); await btnResetTabRootView.tap(); } loadedTabRootView = async () => { - const tabFirst = await this._driver.findElementByText(first); + const tabFirst = await this._driver.waitForElement(first); assert.isTrue(await tabFirst.isDisplayed()); console.log("Tab root view loaded!"); } @@ -89,29 +92,29 @@ export class Screen { } showModalFrame = async () => { - const btnModalFrame = await this._driver.findElementByText(modalFrame); + const btnModalFrame = await this._driver.waitForElement(modalFrame); await btnModalFrame.tap(); } loadedModalFrame = async () => { - const lblModal = await this._driver.findElementByText(modal); + const lblModal = await this._driver.waitForElement(modal); assert.isTrue(await lblModal.isDisplayed()); console.log(modal + " loaded!"); } showModalPage = async () => { - const btnModalPage = await this._driver.findElementByText(modalPage); + const btnModalPage = await this._driver.waitForElement(modalPage); await btnModalPage.tap(); } loadedModalPage = async () => { - const btnShowNestedModalPage = await this._driver.findElementByText(showNestedModalPage); + const btnShowNestedModalPage = await this._driver.waitForElement(showNestedModalPage); assert.isTrue(await btnShowNestedModalPage.isDisplayed()); console.log("Modal Page loaded!"); } showModalLayout = async () => { - const btnModalLayout = await this._driver.findElementByText(modalLayout); + const btnModalLayout = await this._driver.waitForElement(modalLayout); await btnModalLayout.tap(); } @@ -120,99 +123,99 @@ export class Screen { } showModalTabView = async () => { - const btnModalTabView = await this._driver.findElementByText(modalTabView); + const btnModalTabView = await this._driver.waitForElement(modalTabView); await btnModalTabView.tap(); } loadedModalTabView = async () => { - const itemModalFirst = await this._driver.findElementByText(modalFirst); + const itemModalFirst = await this._driver.waitForElement(modalFirst); assert.isTrue(await itemModalFirst.isDisplayed()); console.log("Modal TabView loaded!"); } navigateToSecondPage = async () => { - const btnNavToSecondPage = await this._driver.findElementByText(navToSecondPage); + const btnNavToSecondPage = await this._driver.waitForElement(navToSecondPage); await btnNavToSecondPage.tap(); } showDialogConfirm = async () => { - const btnShowDialogConfirm = await this._driver.findElementByText(showDialog); + const btnShowDialogConfirm = await this._driver.waitForElement(showDialog); await btnShowDialogConfirm.tap(); } navigateToFirstItem = async () => { - const itemModalFirst = await this._driver.findElementByText(modalFirst); + const itemModalFirst = await this._driver.waitForElement(modalFirst); await itemModalFirst.tap(); } navigateToSecondItem = async () => { - const itemModalSecond = await this._driver.findElementByText(modalSecond); + const itemModalSecond = await this._driver.waitForElement(modalSecond); await itemModalSecond.tap(); } loadedConfirmDialog = async () => { - const lblDialogMessage = await this._driver.findElementByText(confirmDialogMessage); + const lblDialogMessage = await this._driver.waitForElement(confirmDialogMessage); assert.isTrue(await lblDialogMessage.isDisplayed()); console.log(dialogConfirm + " shown!"); } loadedSecondPage = async () => { - const lblModalSecond = await this._driver.findElementByText(modalSecond); + const lblModalSecond = await this._driver.waitForElement(modalSecond); assert.isTrue(await lblModalSecond.isDisplayed()); console.log(modalSecond + " loaded!"); } loadedFirstItem = async () => { - const lblModal = await this._driver.findElementByText(modal); + const lblModal = await this._driver.waitForElement(modal); assert.isTrue(await lblModal.isDisplayed()); console.log("First Item loaded!"); } loadedSecondItem = async () => { - const btnGoBack = await this._driver.findElementByText(goBack); + const btnGoBack = await this._driver.waitForElement(goBack); assert.isTrue(await btnGoBack.isDisplayed()); console.log("Second Item loaded!"); } closeDialog = async () => { - const btnYesDialog = await this._driver.findElementByText(confirmDialog); + const btnYesDialog = await this._driver.waitForElement(confirmDialog); await btnYesDialog.tap(); } goBackFromSecondPage = async () => { - const btnGoBackFromSecondPage = await this._driver.findElementByText(goBack); + const btnGoBackFromSecondPage = await this._driver.waitForElement(goBack); await btnGoBackFromSecondPage.tap(); } showNestedModalFrame = async () => { - const btnShowNestedModalFrame = await this._driver.findElementByText(showNestedModalFrame); + const btnShowNestedModalFrame = await this._driver.waitForElement(showNestedModalFrame); await btnShowNestedModalFrame.tap(); } loadedNestedModalFrame = async () => { - const lblModalNested = await this._driver.findElementByText(modalNested); + const lblModalNested = await this._driver.waitForElement(modalNested); assert.isTrue(await lblModalNested.isDisplayed()); console.log(modalNested + " loaded!"); } closeModalNested = async () => { - const btnCloseNestedModal = await this._driver.findElementByText(closeModalNested); + const btnCloseNestedModal = await this._driver.waitForElement(closeModalNested); await btnCloseNestedModal.tap(); } showNestedModalPage = async () => { - const btnShowNestedModalPage = await this._driver.findElementByText(showNestedModalPage); + const btnShowNestedModalPage = await this._driver.waitForElement(showNestedModalPage); await btnShowNestedModalPage.tap(); } loadedNestedModalPage = async () => { - const btnCloseModalNested = await this._driver.findElementByText(closeModalNested); + const btnCloseModalNested = await this._driver.waitForElement(closeModalNested); assert.isTrue(await btnCloseModalNested.isDisplayed()); console.log(closeModalNested + " loaded!"); } closeModal = async () => { - const btnCloseModal = await this._driver.findElementByText(closeModal); + const btnCloseModal = await this._driver.waitForElement(closeModal); await btnCloseModal.tap(); } diff --git a/e2e/nested-frame-navigation/app/frame-root/frame-home-page.ts b/e2e/nested-frame-navigation/app/frame-root/frame-home-page.ts index 563f6f73d..95859bef6 100644 --- a/e2e/nested-frame-navigation/app/frame-root/frame-home-page.ts +++ b/e2e/nested-frame-navigation/app/frame-root/frame-home-page.ts @@ -21,7 +21,7 @@ export function onNavigateSlide(args: EventData) { animated: true, transition: { name: "slide", - duration: 380, + duration: 300, curve: "easeIn" } }); @@ -34,7 +34,7 @@ export function onNavigateFlip(args: EventData) { animated: true, transition: { name: "flip", - duration: 380, + duration: 300, curve: "easeIn" } }); diff --git a/e2e/nested-frame-navigation/app/tab-page/tabs-page.ts b/e2e/nested-frame-navigation/app/frame-root/frame-multi-home-page.ts similarity index 95% rename from e2e/nested-frame-navigation/app/tab-page/tabs-page.ts rename to e2e/nested-frame-navigation/app/frame-root/frame-multi-home-page.ts index 563f6f73d..95859bef6 100644 --- a/e2e/nested-frame-navigation/app/tab-page/tabs-page.ts +++ b/e2e/nested-frame-navigation/app/frame-root/frame-multi-home-page.ts @@ -21,7 +21,7 @@ export function onNavigateSlide(args: EventData) { animated: true, transition: { name: "slide", - duration: 380, + duration: 300, curve: "easeIn" } }); @@ -34,7 +34,7 @@ export function onNavigateFlip(args: EventData) { animated: true, transition: { name: "flip", - duration: 380, + duration: 300, curve: "easeIn" } }); diff --git a/e2e/nested-frame-navigation/app/frame-root/frame-multi-home-page.xml b/e2e/nested-frame-navigation/app/frame-root/frame-multi-home-page.xml index 88d9c1371..c65c41a05 100644 --- a/e2e/nested-frame-navigation/app/frame-root/frame-multi-home-page.xml +++ b/e2e/nested-frame-navigation/app/frame-root/frame-multi-home-page.xml @@ -1,4 +1,4 @@ - + diff --git a/e2e/nested-frame-navigation/app/home/home-page.ts b/e2e/nested-frame-navigation/app/home/home-page.ts index 5cac9f426..af70f5a08 100644 --- a/e2e/nested-frame-navigation/app/home/home-page.ts +++ b/e2e/nested-frame-navigation/app/home/home-page.ts @@ -3,11 +3,11 @@ import { EventData } from "tns-core-modules/ui/core/view"; import { Button } from "tns-core-modules/ui/button"; export function onNavigateToLayoutFrame(args: EventData) { - application._resetRootView({ moduleName: "layout-root/layout-root-frame" }); + application._resetRootView({ moduleName: "layout-root/layout-frame-root" }); } export function onNavigateToLayoutMultiFrame(args: EventData) { - application._resetRootView({ moduleName: "layout-root/layout-root-multi-frame" }); + application._resetRootView({ moduleName: "layout-root/layout-multi-frame-root" }); } export function onNavigateToPageFrame(args: EventData) { @@ -31,9 +31,9 @@ export function onNavigateToTabsBottomPage(args: EventData) { } export function onNavigateToTabsTopRoot(args: EventData) { - application._resetRootView({ moduleName: "tab-root/tab-root-top" }); + application._resetRootView({ moduleName: "tab-root/tab-top-root" }); } export function onNavigateToTabsBottomRoot(args: EventData) { - application._resetRootView({ moduleName: "tab-root/tab-root-bottom" }); + application._resetRootView({ moduleName: "tab-root/tab-bottom-root" }); } diff --git a/e2e/nested-frame-navigation/app/home/home-page.xml b/e2e/nested-frame-navigation/app/home/home-page.xml index 229af254b..54367f44a 100644 --- a/e2e/nested-frame-navigation/app/home/home-page.xml +++ b/e2e/nested-frame-navigation/app/home/home-page.xml @@ -10,9 +10,9 @@ + + `; + +export function test_onLiveSync_HmrContext_AppStyle_AppNewCss() { + _test_onLiveSync_HmrContext_AppStyle(appNewCssFileName); +} + +export function test_onLiveSync_HmrContext_AppStyle_AppNewScss() { + _test_onLiveSync_HmrContext_AppStyle(appNewScssFileName); +} + +export function test_onLiveSync_HmrContext_ContextUndefined() { + _test_onLiveSync_HmrContext({ type: undefined, module: undefined }); +} + +export function test_onLiveSync_HmrContext_ModuleUndefined() { + _test_onLiveSync_HmrContext({ type: "script", module: undefined }); +} + +export function test_onLiveSync_HmrContext_Script_AppJs() { + _test_onLiveSync_HmrContext({ type: "script", module: appJsFileName }); +} + +export function test_onLiveSync_HmrContext_Script_AppTs() { + _test_onLiveSync_HmrContext({ type: "script", module: appTsFileName }); +} + +export function test_onLiveSync_HmrContext_Style_MainPageCss() { + _test_onLiveSync_HmrContext({ type: "style", module: mainPageCssFileName }); +} + +export function test_onLiveSync_HmrContext_Markup_MainPageHtml() { + _test_onLiveSync_HmrContext({ type: "markup", module: mainPageHtmlFileName }); +} + +export function test_onLiveSync_HmrContext_Markup_MainPageXml() { + _test_onLiveSync_HmrContext({ type: "markup", module: mainPageXmlFileName }); +} + +export function setUpModule() { + const mainPage = parse(mainPageTemplate); + helper.navigate(() => mainPage); +} + +export function tearDown() { + app.setCssFileName(appCssFileName); +} + +function _test_onLiveSync_HmrContext_AppStyle(styleFileName: string) { + const pageBeforeNavigation = helper.getCurrentPage(); + + const page = parse(pageTemplate); + helper.navigateWithHistory(() => page); + app.setCssFileName(styleFileName); + + const pageBeforeLiveSync = helper.getCurrentPage(); + global.__onLiveSync({ type: "style", module: 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!"); + + 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.assertTrue(pageAfterNavigationBack._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version is NOT applied!"); +} + +function _test_onLiveSync_HmrContext(context: { type, module }) { + const page = parse(pageTemplate); + helper.navigateWithHistory(() => page); + global.__onLiveSync({ type: context.type, module: context.module }); + + TKUnit.waitUntilReady(() => !!frame.topmost()); + const topmostFrame = frame.topmost(); + TKUnit.waitUntilReady(() => topmostFrame.currentPage && topmostFrame.currentPage.isLoaded && !topmostFrame.canGoBack()); + TKUnit.assertTrue(topmostFrame.currentPage.getViewById("label").isLoaded); +} \ No newline at end of file diff --git a/tests/app/testRunner.ts b/tests/app/testRunner.ts index a119642a0..b96bde95d 100644 --- a/tests/app/testRunner.ts +++ b/tests/app/testRunner.ts @@ -153,9 +153,6 @@ allTests["STYLE-PROPERTIES"] = stylePropertiesTests; import * as frameTests from "./ui/frame/frame-tests"; allTests["FRAME"] = frameTests; -import * as tabViewRootTests from "./ui/tab-view/tab-view-root-tests"; -allTests["TAB-VIEW-ROOT"] = tabViewRootTests; - import * as viewTests from "./ui/view/view-tests"; allTests["VIEW"] = viewTests; @@ -255,6 +252,12 @@ allTests["SEARCH-BAR"] = searchBarTests; import * as navigationTests from "./navigation/navigation-tests"; allTests["NAVIGATION"] = navigationTests; +import * as livesyncTests from "./livesync/livesync-tests"; +allTests["LIVESYNC"] = livesyncTests; + +import * as tabViewRootTests from "./ui/tab-view/tab-view-root-tests"; +allTests["TAB-VIEW-ROOT"] = tabViewRootTests; + import * as resetRootViewTests from "./ui/root-view/reset-root-view-tests"; allTests["RESET-ROOT-VIEW"] = resetRootViewTests; diff --git a/tns-core-modules/LICENSE b/tns-core-modules/LICENSE index 1c24a9142..ef8add3b4 100755 --- a/tns-core-modules/LICENSE +++ b/tns-core-modules/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright (c) 2015-2018 Telerik AD + Copyright (c) 2015-2019 Progress Software Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -198,4 +198,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/tns-core-modules/application/application-common.ts b/tns-core-modules/application/application-common.ts index 32ab9dc04..166c4e0d1 100644 --- a/tns-core-modules/application/application-common.ts +++ b/tns-core-modules/application/application-common.ts @@ -32,9 +32,17 @@ export function hasLaunched(): boolean { export { Observable }; -import { UnhandledErrorEventData, iOSApplication, AndroidApplication, CssChangedEventData, LoadAppCSSEventData } from "."; +import { + AndroidApplication, + CssChangedEventData, + getRootView, + iOSApplication, + LoadAppCSSEventData, + UnhandledErrorEventData, + DiscardedErrorEventData +} from "./application"; -export { UnhandledErrorEventData, CssChangedEventData, LoadAppCSSEventData }; +export { UnhandledErrorEventData, DiscardedErrorEventData, CssChangedEventData, LoadAppCSSEventData }; export const launchEvent = "launch"; export const suspendEvent = "suspend"; @@ -43,6 +51,7 @@ export const resumeEvent = "resume"; export const exitEvent = "exit"; export const lowMemoryEvent = "lowMemory"; export const uncaughtErrorEvent = "uncaughtError"; +export const discardedErrorEvent = "discardedError"; export const orientationChangedEvent = "orientationChanged"; let cssFile: string = "./app.css"; @@ -70,10 +79,22 @@ export function setApplication(instance: iOSApplication | AndroidApplication): v app = instance; } -export function livesync() { +export function livesync(context?: HmrContext) { events.notify({ eventName: "livesync", object: app }); const liveSyncCore = global.__onLiveSyncCore; - if (liveSyncCore) { + let reapplyAppCss = false + + if (context) { + const fullFileName = getCssFileName(); + const fileName = fullFileName.substring(0, fullFileName.lastIndexOf(".") + 1); + const extensions = ["css", "scss"]; + reapplyAppCss = extensions.some(ext => context.module === fileName.concat(ext)); + } + + const rootView = getRootView(); + if (reapplyAppCss && rootView) { + rootView._onCssStateChange(); + } else if (liveSyncCore) { liveSyncCore(); } } @@ -92,7 +113,7 @@ export function loadAppCss(): void { events.notify({ eventName: "loadAppCss", object: app, cssFile: getCssFileName() }); } catch (e) { throw new Error(`The file ${getCssFileName()} couldn't be loaded! ` + - `You may need to register it inside ./app/vendor.ts.`); + `You may need to register it inside ./app/vendor.ts.`); } } @@ -103,3 +124,7 @@ export function addCss(cssText: string): void { global.__onUncaughtError = function (error: NativeScriptError) { events.notify({ eventName: uncaughtErrorEvent, object: app, android: error, ios: error, error: error }); } + +global.__onDiscardedError = function (error: NativeScriptError) { + events.notify({ eventName: discardedErrorEvent, object: app, error: error }); +} diff --git a/tns-core-modules/application/application.android.ts b/tns-core-modules/application/application.android.ts index 0b33bba80..e27378534 100644 --- a/tns-core-modules/application/application.android.ts +++ b/tns-core-modules/application/application.android.ts @@ -212,12 +212,12 @@ export function getNativeApplication(): android.app.Application { return nativeApp; } -global.__onLiveSync = function () { +global.__onLiveSync = function __onLiveSync(context?: HmrContext) { if (androidApp && androidApp.paused) { return; } - livesync(); + livesync(context); }; function initLifecycleCallbacks() { @@ -290,15 +290,10 @@ function initLifecycleCallbacks() { onActivityResumed: profile("onActivityResumed", function (activity: android.support.v7.app.AppCompatActivity) { androidApp.foregroundActivity = activity; - if ((activity).isNativeScriptActivity) { - notify({ eventName: resumeEvent, object: androidApp, android: activity }); - androidApp.paused = false; - } - androidApp.notify({ eventName: ActivityResumed, object: androidApp, activity: activity }); }), - onActivitySaveInstanceState: profile("onActivityResumed", function (activity: android.support.v7.app.AppCompatActivity, outState: android.os.Bundle) { + onActivitySaveInstanceState: profile("onActivitySaveInstanceState", function (activity: android.support.v7.app.AppCompatActivity, outState: android.os.Bundle) { androidApp.notify({ eventName: SaveActivityState, object: androidApp, activity: activity, bundle: outState }); }), diff --git a/tns-core-modules/application/application.d.ts b/tns-core-modules/application/application.d.ts index 92ac02946..582a19786 100644 --- a/tns-core-modules/application/application.d.ts +++ b/tns-core-modules/application/application.d.ts @@ -21,6 +21,11 @@ export var displayedEvent: string; */ export var uncaughtErrorEvent: string; +/** + * String value used when hooking to discardedError event. + */ +export var discardedErrorEvent: string; + /** * String value used when hooking to suspend event. */ @@ -103,6 +108,13 @@ export interface UnhandledErrorEventData extends ApplicationEventData { error: NativeScriptError; } +/** + * Event data containing information about discarded application errors. + */ +export interface DiscardedErrorEventData extends ApplicationEventData { + error: NativeScriptError; +} + /** * Event data containing information about application css change. */ @@ -137,7 +149,7 @@ export function setResources(res: any): void; export function setResources(resources: any); /** - * Sets css file name for the application. + * Sets css file name for the application. */ export function setCssFileName(cssFile: string): void; @@ -199,7 +211,7 @@ export function shouldCreateRootFrame(): boolean; /** * A basic method signature to hook an event listener (shortcut alias to the addEventListener method). - * @param eventNames - String corresponding to events (e.g. "onLaunch"). Optionally could be used more events separated by `,` (e.g. "onLaunch", "onSuspend"). + * @param eventNames - String corresponding to events (e.g. "onLaunch"). Optionally could be used more events separated by `,` (e.g. "onLaunch", "onSuspend"). * @param callback - Callback function which will be executed when event is raised. * @param thisArg - An optional parameter which will be used as `this` context for callback execution. */ @@ -262,6 +274,11 @@ export function on(event: "lowMemory", callback: (args: ApplicationEventData) => */ export function on(event: "uncaughtError", callback: (args: UnhandledErrorEventData) => void, thisArg?: any); +/** + * This event is raised when an discarded error occurs while the application is running. + */ +export function on(event: "discardedError", callback: (args: DiscardedErrorEventData) => void, thisArg?: any); + /** * This event is raised the orientation of the current device has changed. */ @@ -403,13 +420,13 @@ export class AndroidApplication extends Observable { /** * Initialized the android-specific application object with the native android.app.Application instance. * This is useful when creating custom application types. - * @param nativeApp - the android.app.Application instance that started the app. + * @param nativeApp - the android.app.Application instance that started the app. */ init: (nativeApp) => void; /** * A basic method signature to hook an event listener (shortcut alias to the addEventListener method). - * @param eventNames - String corresponding to events (e.g. "propertyChange"). Optionally could be used more events separated by `,` (e.g. "propertyChange", "change"). + * @param eventNames - String corresponding to events (e.g. "propertyChange"). Optionally could be used more events separated by `,` (e.g. "propertyChange", "change"). * @param callback - Callback function which will be executed when event is raised. * @param thisArg - An optional parameter which will be used as `this` context for callback execution. */ @@ -516,7 +533,7 @@ export class AndroidApplication extends Observable { public static activityRequestPermissionsEvent: string; /** - * Register a BroadcastReceiver to be run in the main activity thread. The receiver will be called with any broadcast Intent that matches filter, in the main application thread. + * Register a BroadcastReceiver to be run in the main activity thread. The receiver will be called with any broadcast Intent that matches filter, in the main application thread. * For more information, please visit 'http://developer.android.com/reference/android/content/Context.html#registerReceiver%28android.content.BroadcastReceiver,%20android.content.IntentFilter%29' * @param intentFilter A string containing the intent filter. * @param onReceiveCallback A callback function that will be called each time the receiver receives a broadcast. @@ -524,7 +541,7 @@ export class AndroidApplication extends Observable { registerBroadcastReceiver(intentFilter: string, onReceiveCallback: (context: any /* android.content.Context */, intent: any /* android.content.Intent */) => void): void; /** - * Unregister a previously registered BroadcastReceiver. + * Unregister a previously registered BroadcastReceiver. * For more information, please visit 'http://developer.android.com/reference/android/content/Context.html#unregisterReceiver(android.content.BroadcastReceiver)' * @param intentFilter A string containing the intent filter with which the receiver was originally registered. */ diff --git a/tns-core-modules/application/application.ios.ts b/tns-core-modules/application/application.ios.ts index 89bcf2878..b69af024b 100644 --- a/tns-core-modules/application/application.ios.ts +++ b/tns-core-modules/application/application.ios.ts @@ -161,7 +161,7 @@ class IOSApplication implements IOSApplicationDefinition { this.setWindowContent(args.root); } else { this._window = UIApplication.sharedApplication.delegate.window; - } + } } @profile @@ -373,10 +373,10 @@ function setViewControllerView(view: View): void { } } -global.__onLiveSync = function () { +global.__onLiveSync = function __onLiveSync(context?: HmrContext) { if (!started) { return; } - livesync(); -} + livesync(context); +} \ No newline at end of file diff --git a/tns-core-modules/http/http-request/http-request.android.ts b/tns-core-modules/http/http-request/http-request.android.ts index 0877e6d0d..ff986956f 100644 --- a/tns-core-modules/http/http-request/http-request.android.ts +++ b/tns-core-modules/http/http-request/http-request.android.ts @@ -52,7 +52,10 @@ function ensureCompleteCallback() { onComplete: function (result: any, context: any) { // as a context we will receive the id of the request onRequestComplete(context, result); - } + }, + onError: function (error: string, context: any) { + onRequestError(error, context); + }, }); } @@ -148,6 +151,14 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A }); } +function onRequestError(error: string, requestId: number) { + var callbacks = pendingRequests[requestId]; + delete pendingRequests[requestId]; + if (callbacks) { + callbacks.rejectCallback(new Error(error)); + } +} + function buildJavaOptions(options: http.HttpRequestOptions) { if (typeof options.url !== "string") { throw new Error("Http request must provide a valid url."); diff --git a/tns-core-modules/module.d.ts b/tns-core-modules/module.d.ts index 5fd5d161d..f252e6295 100644 --- a/tns-core-modules/module.d.ts +++ b/tns-core-modules/module.d.ts @@ -3,7 +3,7 @@ declare var global: NodeJS.Global; interface ModuleResolver { /** * A function used to resolve the exports for a module. - * @param uri The name of the module to be resolved. + * @param uri The name of the module to be resolved. */ (uri: string): any; } @@ -18,7 +18,7 @@ declare namespace NodeJS { * Register all modules from a webpack context. * The context is one created using the following webpack utility: * https://webpack.github.io/docs/context.html - * + * * The extension map is optional, modules in the webpack context will have their original file extension (e.g. may be ".ts" or ".scss" etc.), * while the built-in module builders in {N} will look for ".js", ".css" or ".xml" files. Adding a map such as: * ``` @@ -51,9 +51,10 @@ declare namespace NodeJS { __native?: any; __inspector?: any; __extends: any; - __onLiveSync: () => void; + __onLiveSync: (context?: { type: string, module: string }) => void; __onLiveSyncCore: () => void; __onUncaughtError: (error: NativeScriptError) => void; + __onDiscardedError: (error: NativeScriptError) => void; TNS_WEBPACK?: boolean; __requireOverride?: (name: string, dir: string) => any; } @@ -64,6 +65,27 @@ declare function clearTimeout(timeoutId: number): void; declare function setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): number; declare function clearInterval(intervalId: number): void; +declare enum HmrType { + markup = "markup", + script = "script", + style = "style" +} + +/** + * Define a context for Hot Module Replacement. + */ +interface HmrContext { + /** + * The type of module for replacement. + */ + type: HmrType; + + /** + * The module for replacement. + */ + module: string; +} + /** * An extended JavaScript Error which will have the nativeError property initialized in case the error is caused by executing platform-specific code. */ diff --git a/tns-core-modules/package.json b/tns-core-modules/package.json index 64e1bc926..17a3e7421 100644 --- a/tns-core-modules/package.json +++ b/tns-core-modules/package.json @@ -1,7 +1,7 @@ { "name": "tns-core-modules", "description": "Telerik NativeScript Core Modules", - "version": "5.1.2", + "version": "5.2.0", "homepage": "https://www.nativescript.org", "repository": { "type": "git", @@ -26,7 +26,7 @@ "license": "Apache-2.0", "typings": "tns-core-modules.d.ts", "dependencies": { - "tns-core-modules-widgets": "5.1.2", + "tns-core-modules-widgets": "next", "tslib": "^1.9.3" }, "devDependencies": { @@ -38,8 +38,8 @@ }, "nativescript": { "platforms": { - "ios": "5.0.0", - "android": "5.0.0" + "ios": "4.0.0", + "android": "4.0.0" } }, "snapshot": { diff --git a/tns-core-modules/ui/action-bar/action-bar.ios.ts b/tns-core-modules/ui/action-bar/action-bar.ios.ts index 459a78614..17a96677f 100644 --- a/tns-core-modules/ui/action-bar/action-bar.ios.ts +++ b/tns-core-modules/ui/action-bar/action-bar.ios.ts @@ -6,7 +6,6 @@ import { layout, Color, traceMissingIcon } from "./action-bar-common"; import { fromFileOrResource } from "../../image-source"; import { ios as iosUtils } from "../../utils/utils"; -import { write as traceWrite, categories, messageType } from "../../trace"; export * from "./action-bar-common"; 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 1b2feb89a..0ef767f61 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 @@ -81,6 +81,12 @@ export interface ShowModalOptions { */ presentationStyle: any /* UIModalPresentationStyle */ } + android?: { + /** + * An optional parameter specifying whether the modal view can be dismissed when not in full-screen mode. + */ + cancelable?: boolean + } } export abstract class ViewBase extends Observable { diff --git a/tns-core-modules/ui/core/view/view.android.ts b/tns-core-modules/ui/core/view/view.android.ts index 66789f729..b176cbfd2 100644 --- a/tns-core-modules/ui/core/view/view.android.ts +++ b/tns-core-modules/ui/core/view/view.android.ts @@ -37,6 +37,7 @@ interface DialogOptions { owner: View; fullscreen: boolean; stretched: boolean; + cancelable: boolean; shownCallback: () => void; dismissCallback: () => void; } @@ -117,6 +118,7 @@ function initializeDialogFragment() { public owner: View; private _fullscreen: boolean; private _stretched: boolean; + private _cancelable: boolean; private _shownCallback: () => void; private _dismissCallback: () => void; @@ -130,13 +132,20 @@ function initializeDialogFragment() { const options = getModalOptions(ownerId); this.owner = options.owner; this._fullscreen = options.fullscreen; + this._cancelable = options.cancelable; this._stretched = options.stretched; this._dismissCallback = options.dismissCallback; this._shownCallback = options.shownCallback; this.owner._dialogFragment = this; this.setStyle(android.support.v4.app.DialogFragment.STYLE_NO_TITLE, 0); + + let theme = this.getTheme(); + if (this._fullscreen) { + // In fullscreen mode, get the application's theme. + theme = this.getActivity().getApplicationInfo().theme; + } - const dialog = new DialogImpl(this, this.getActivity(), this.getTheme()); + const dialog = new DialogImpl(this, this.getActivity(), theme); // do not override alignment unless fullscreen modal will be shown; // otherwise we might break component-level layout: @@ -149,6 +158,7 @@ function initializeDialogFragment() { this.owner.verticalAlignment = "stretch"; } + dialog.setCanceledOnTouchOutside(this._cancelable); return dialog; } @@ -195,8 +205,17 @@ function initializeDialogFragment() { public onDestroy(): void { super.onDestroy(); const owner = this.owner; - owner._isAddedToNativeVisualTree = false; - owner._tearDownUI(true); + + if (owner) { + // Android calls onDestroy before onDismiss. + // Make sure we unload first and then call _tearDownUI. + if (owner.isLoaded) { + owner.callUnloaded(); + } + + owner._isAddedToNativeVisualTree = false; + owner._tearDownUI(true); + } } } @@ -386,7 +405,7 @@ export class View extends ViewCommon { 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(); @@ -574,7 +593,7 @@ export class View extends ViewCommon { return result | (childMeasuredState & layout.MEASURED_STATE_MASK); } - protected _showNativeModalView(parent: View, options: ShowModalOptions) { //context: any, closeCallback: Function, fullscreen?: boolean, animated?: boolean, stretched?: boolean, iosOpts?: any) { + protected _showNativeModalView(parent: View, options: ShowModalOptions) { super._showNativeModalView(parent, options); if (!this.backgroundColor) { this.backgroundColor = new Color("White"); @@ -591,6 +610,7 @@ export class View extends ViewCommon { owner: this, fullscreen: !!options.fullscreen, stretched: !!options.stretched, + cancelable: options.android ? !!options.android.cancelable : true, shownCallback: () => this._raiseShownModallyEvent(), dismissCallback: () => this.closeModal() } diff --git a/tns-core-modules/ui/core/view/view.ios.ts b/tns-core-modules/ui/core/view/view.ios.ts index 06e62e42d..2c1c5aed0 100644 --- a/tns-core-modules/ui/core/view/view.ios.ts +++ b/tns-core-modules/ui/core/view/view.ios.ts @@ -1,7 +1,6 @@ // Definitions. import { Point, View as ViewDefinition, dip } from "."; import { ViewBase } from "../view-base"; -import { booleanConverter, Property } from "../view"; import { ViewCommon, layout, isEnabledProperty, originXProperty, originYProperty, automationTextProperty, isUserInteractionEnabledProperty, @@ -371,7 +370,7 @@ export class View extends ViewCommon { return this._suspendCATransaction || this._suspendNativeUpdatesCount; } - protected _showNativeModalView(parent: View, options: ShowModalOptions) { //context: any, closeCallback: Function, fullscreen?: boolean, animated?: boolean, stretched?: boolean, iosOpts?: any) { + protected _showNativeModalView(parent: View, options: ShowModalOptions) { const parentWithController = ios.getParentWithViewController(parent); if (!parentWithController) { traceWrite(`Could not find parent with viewController for ${parent} while showing modal view.`, diff --git a/tns-core-modules/ui/dialogs/dialogs.ios.ts b/tns-core-modules/ui/dialogs/dialogs.ios.ts index 598f49389..91a6cbc5f 100644 --- a/tns-core-modules/ui/dialogs/dialogs.ios.ts +++ b/tns-core-modules/ui/dialogs/dialogs.ios.ts @@ -1,7 +1,7 @@ /** * iOS specific dialogs functions implementation. */ -import { View, ios as iosView } from "../core/view"; +import { ios as iosView } from "../core/view"; import { ConfirmOptions, PromptOptions, PromptResult, LoginOptions, LoginResult, ActionOptions } from "."; import { getCurrentPage, getLabelColor, getButtonColors, getTextFieldColor, isDialogOptions, inputType, capitalizationType, ALERT, OK, CONFIRM, CANCEL, PROMPT, parseLoginOptions } from "./dialogs-common"; import { isString, isDefined, isFunction } from "../../utils/types"; diff --git a/tns-core-modules/ui/frame/activity.android.ts b/tns-core-modules/ui/frame/activity.android.ts index 55d9118a9..946ca63df 100644 --- a/tns-core-modules/ui/frame/activity.android.ts +++ b/tns-core-modules/ui/frame/activity.android.ts @@ -50,6 +50,10 @@ class NativeScriptActivity extends android.support.v7.app.AppCompatActivity { this._callbacks.onDestroy(this, super.onDestroy); } + public onPostResume(): void { + this._callbacks.onPostResume(this, super.onPostResume); + } + public onBackPressed(): void { this._callbacks.onBackPressed(this, super.onBackPressed); } diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index 71e39e4b7..fdde423ab 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -85,10 +85,14 @@ function getAttachListener(): android.view.View.OnAttachStateChangeListener { export function reloadPage(): void { const activity = application.android.foregroundActivity; const callbacks: AndroidActivityCallbacks = activity[CALLBACKS]; - const rootView: View = callbacks.getRootView(); + if (callbacks) { + const rootView: View = callbacks.getRootView(); - if (!rootView || !rootView._onLivesync()) { - callbacks.resetActivityContent(activity); + if (!rootView || !rootView._onLivesync()) { + callbacks.resetActivityContent(activity); + } + } else { + traceError(`${activity}[CALLBACKS] is null or undefined`); } } @@ -469,19 +473,19 @@ export class Frame extends FrameBase { switch (this.actionBarVisibility) { case "never": return false; - + case "always": return true; - + default: if (page.actionBarHidden !== undefined) { return !page.actionBarHidden; } - + if (this._android && this._android.showActionBar !== undefined) { return this._android.showActionBar; } - + return true; } } @@ -530,7 +534,7 @@ function restoreAnimatorState(entry: BackstackEntry, snapshot: AnimatorState): v if (snapshot.enterAnimator) { expandedEntry.enterAnimator = snapshot.enterAnimator; } - + if (snapshot.exitAnimator) { expandedEntry.exitAnimator = snapshot.exitAnimator; } @@ -538,7 +542,7 @@ function restoreAnimatorState(entry: BackstackEntry, snapshot: AnimatorState): v if (snapshot.popEnterAnimator) { expandedEntry.popEnterAnimator = snapshot.popEnterAnimator; } - + if (snapshot.popExitAnimator) { expandedEntry.popExitAnimator = snapshot.popExitAnimator; } @@ -858,14 +862,14 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { // parent while its supposed parent believes it properly removed its children; in order to "force" the child to // lose its parent we temporarily add it to the parent, and then remove it (addViewInLayout doesn't trigger layout pass) const nativeView = page.nativeViewProtected; - if (nativeView != null) { - const parentView = nativeView.getParent(); + if (nativeView != null) { + const parentView = nativeView.getParent(); if (parentView instanceof android.view.ViewGroup) { if (parentView.getChildCount() === 0) { parentView.addViewInLayout(nativeView, -1, new org.nativescript.widgets.CommonLayoutParams()); } - parentView.removeView(nativeView); + parentView.removeView(nativeView); } } @@ -1006,6 +1010,30 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks { } } + @profile + public onPostResume(activity: any, superFunc: Function): void { + superFunc.call(activity); + + if (traceEnabled()) { + traceWrite("NativeScriptActivity.onPostResume();", traceCategories.NativeLifecycle); + } + + // NOTE: activity.onPostResume() is called when activity resume is complete and we can + // safely raise the application resume event; + // onActivityResumed(...) lifecycle callback registered in application is called too early + // and raising the application resume event there causes issues like + // https://github.com/NativeScript/NativeScript/issues/6708 + if ((activity).isNativeScriptActivity) { + const args = { + eventName: application.resumeEvent, + object: application.android, + android: activity + }; + application.notify(args); + application.android.paused = false; + } + } + @profile public onDestroy(activity: any, superFunc: Function): void { if (traceEnabled()) { @@ -1216,4 +1244,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 dac7f0f55..a92519b69 100644 --- a/tns-core-modules/ui/frame/frame.d.ts +++ b/tns-core-modules/ui/frame/frame.d.ts @@ -415,6 +415,7 @@ export interface AndroidActivityCallbacks { onSaveInstanceState(activity: any, outState: any, superFunc: Function): void; onStart(activity: any, superFunc: Function): void; onStop(activity: any, superFunc: Function): void; + onPostResume(activity: any, superFunc: Function): void; onDestroy(activity: any, superFunc: Function): void; onBackPressed(activity: any, superFunc: Function): void; onRequestPermissionsResult(activity: any, requestCode: number, permissions: Array, grantResults: Array, superFunc: Function): void; diff --git a/tns-core-modules/ui/image-cache/image-cache-common.ts b/tns-core-modules/ui/image-cache/image-cache-common.ts index b505a390d..3dbd50809 100644 --- a/tns-core-modules/ui/image-cache/image-cache-common.ts +++ b/tns-core-modules/ui/image-cache/image-cache-common.ts @@ -6,10 +6,12 @@ export interface DownloadRequest { url: string; key: string; completed?: (image: any, key: string) => void; + error?: (key: string) => void; } export class Cache extends observable.Observable implements definition.Cache { public static downloadedEvent = "downloaded"; + public static downloadErrorEvent = "downloadError"; public placeholder: imageSource.ImageSource; public maxRequests = 5; @@ -93,6 +95,20 @@ export class Cache extends observable.Observable implements definition.Cache { else { existingRequest.completed = newRequest.completed; } + if (existingRequest.error) { + if (newRequest.error) { + var existingError = existingRequest.error; + var stackError = function (key: string) { + existingError(key); + newRequest.error(key); + } + + existingRequest.error = stackError; + } + } + else { + existingRequest.error = newRequest.error; + } } public get(key: string): any { @@ -125,10 +141,7 @@ export class Cache extends observable.Observable implements definition.Cache { public _onDownloadCompleted(key: string, image: any) { var request = this._pendingDownloads[key]; - if (request.key && image) { - this.set(request.key, image); - } - + this.set(request.key, image); this._currentDownloads--; if (request.completed) { @@ -140,7 +153,29 @@ export class Cache extends observable.Observable implements definition.Cache { eventName: Cache.downloadedEvent, object: this, key: key, - image: image + image: image + }); + } + + delete this._pendingDownloads[request.key]; + + this._updateQueue(); + } + + public _onDownloadError(key: string, err: Error) { + var request = this._pendingDownloads[key]; + this._currentDownloads--; + + if (request.error) { + request.error(request.key); + } + + if (this.hasListeners(Cache.downloadErrorEvent)) { + this.notify({ + eventName: Cache.downloadErrorEvent, + object: this, + key: key, + error: err }); } @@ -185,6 +220,7 @@ export class Cache extends observable.Observable implements definition.Cache { } } export interface Cache { - on(eventNames: string, callback: (args: observable.EventData) => void , thisArg?: any); - on(event: "downloaded", callback: (args: definition.DownloadedData) => void , thisArg?: any); + on(eventNames: string, callback: (args: observable.EventData) => void, thisArg?: any); + on(event: "downloaded", callback: (args: definition.DownloadedData) => void, thisArg?: any); + on(event: "downloadError", callback: (args: definition.DownloadError) => void, thisArg?: any); } \ No newline at end of file diff --git a/tns-core-modules/ui/image-cache/image-cache.android.ts b/tns-core-modules/ui/image-cache/image-cache.android.ts index 0a6e4e94f..96118aeed 100644 --- a/tns-core-modules/ui/image-cache/image-cache.android.ts +++ b/tns-core-modules/ui/image-cache/image-cache.android.ts @@ -1,4 +1,5 @@ import * as common from "./image-cache-common"; +import * as trace from "../../trace"; var LruBitmapCacheClass; function ensureLruBitmapCacheClass() { @@ -45,7 +46,17 @@ export class Cache extends common.Cache { onComplete: function (result: any, context: any) { var instance = that.get(); if (instance) { - instance._onDownloadCompleted(context, result) + if (result) { + instance._onDownloadCompleted(context, result); + } else { + instance._onDownloadError(context, new Error("No result in CompletionCallback")); + } + } + }, + onError: function (err: string, context: any) { + var instance = that.get(); + if (instance) { + instance._onDownloadError(context, new Error(err)); } } }); @@ -61,7 +72,13 @@ export class Cache extends common.Cache { } public set(key: string, image: any): void { - this._cache.put(key, image); + try { + if (key && image) { + this._cache.put(key, image); + } + } catch (err) { + trace.write("Cache set error: " + err, trace.categories.Error, trace.messageType.error); + } } public remove(key: string): void { diff --git a/tns-core-modules/ui/image-cache/image-cache.d.ts b/tns-core-modules/ui/image-cache/image-cache.d.ts index 8d909bf31..2a697f964 100644 --- a/tns-core-modules/ui/image-cache/image-cache.d.ts +++ b/tns-core-modules/ui/image-cache/image-cache.d.ts @@ -22,6 +22,10 @@ export interface DownloadRequest { * An optional function to be called when the download is complete. */ completed?: (image: any, key: string) => void; + /** + * An optional function to be called if the download errors. + */ + error?: (key: string) => void; } /** @@ -32,6 +36,10 @@ export class Cache extends observable.Observable { * String value used when hooking to downloaded event. */ public static downloadedEvent: string; + /** + * String value used when hooking to download error event. + */ + public static downloadErrorEvent: string; /** * The image to be used to notify for a pending download request - e.g. loading indicator. */ @@ -82,12 +90,17 @@ export class Cache extends observable.Observable { * @param callback - Callback function which will be executed when event is raised. * @param thisArg - An optional parameter which will be used as `this` context for callback execution. */ - on(eventNames: string, callback: (args: observable.EventData) => void , thisArg?: any); + on(eventNames: string, callback: (args: observable.EventData) => void, thisArg?: any); /** * Raised when the image has been downloaded. */ - on(event: "downloaded", callback: (args: DownloadedData) => void , thisArg?: any); + on(event: "downloaded", callback: (args: DownloadedData) => void, thisArg?: any); + + /** + * Raised if the image download errors. + */ + on(event: "downloadError", callback: (args: DownloadError) => void, thisArg?: any); //@private /** @@ -99,6 +112,11 @@ export class Cache extends observable.Observable { */ _onDownloadCompleted(key: string, image: any); //@endprivate + /** + * @private + */ + _onDownloadError(key: string, err: Error); + //@endprivate } /** @@ -114,3 +132,17 @@ export interface DownloadedData extends observable.EventData { */ image: imageSource.ImageSource; } + +/** + * Provides data for download error. + */ +export interface DownloadError extends observable.EventData { + /** + * A string indentifier of the cached image. + */ + key: string; + /** + * Gets the error. + */ + error: Error; +} diff --git a/tns-core-modules/ui/image-cache/image-cache.ios.ts b/tns-core-modules/ui/image-cache/image-cache.ios.ts index 41f475c67..8224fcb56 100644 --- a/tns-core-modules/ui/image-cache/image-cache.ios.ts +++ b/tns-core-modules/ui/image-cache/image-cache.ios.ts @@ -73,7 +73,7 @@ export class Cache extends common.Cache { super(); this._cache = new NSCache(); - + //this._delegate = NSCacheDelegateImpl.new(); //this._cache.delegate = this._delegate; @@ -83,11 +83,16 @@ export class Cache extends common.Cache { public _downloadCore(request: common.DownloadRequest) { ensureHttpRequest(); - var that = this; httpRequest.request({ url: request.url, method: "GET" }) - .then(response => { - var image = UIImage.alloc().initWithData(response.content.raw); - that._onDownloadCompleted(request.key, image); + .then((response) => { + try { + var image = UIImage.alloc().initWithData(response.content.raw); + this._onDownloadCompleted(request.key, image); + } catch (err) { + this._onDownloadError(request.key, err); + } + }, (err) => { + this._onDownloadError(request.key, err); }); } diff --git a/tns-core-modules/ui/list-view/list-view-common.ts b/tns-core-modules/ui/list-view/list-view-common.ts index 2c054dbcf..5b0cf65c3 100644 --- a/tns-core-modules/ui/list-view/list-view-common.ts +++ b/tns-core-modules/ui/list-view/list-view-common.ts @@ -1,5 +1,5 @@ import { ListView as ListViewDefinition, ItemsSource, ItemEventData, TemplatedItemsView } from "."; -import { CoercibleProperty, CssProperty, Style, View, ViewBase, ContainerView, Template, KeyedTemplate, Length, Property, Color, Observable, EventData, CSSType } from "../core/view"; +import { CoercibleProperty, CssProperty, Style, View, ContainerView, Template, KeyedTemplate, Length, Property, Color, Observable, EventData, CSSType } from "../core/view"; import { parse, parseMultipleTemplates } from "../builder"; import { Label } from "../label"; import { ObservableArray, ChangedData } from "../../data/observable-array"; diff --git a/tns-core-modules/ui/list-view/list-view.ios.ts b/tns-core-modules/ui/list-view/list-view.ios.ts index da0b0cb2c..a68067c5f 100644 --- a/tns-core-modules/ui/list-view/list-view.ios.ts +++ b/tns-core-modules/ui/list-view/list-view.ios.ts @@ -7,7 +7,6 @@ import { StackLayout } from "../layouts/stack-layout"; import { ProxyViewContainer } from "../proxy-view-container"; import { profile } from "../../profiling"; import * as trace from "../../trace"; -import { ios as iosUtils } from "../../utils/utils"; export * from "./list-view-common"; @@ -23,7 +22,6 @@ interface ViewItemIndex { } type ItemView = View & ViewItemIndex; -const majorVersion = iosUtils.MajorVersion; class ListViewCell extends UITableViewCell { public static initWithEmptyBackground(): ListViewCell { diff --git a/tns-core-modules/ui/page/page-common.ts b/tns-core-modules/ui/page/page-common.ts index 0d85ab1ab..28814e186 100644 --- a/tns-core-modules/ui/page/page-common.ts +++ b/tns-core-modules/ui/page/page-common.ts @@ -17,17 +17,17 @@ export class PageBase extends ContentView implements PageDefinition { public static navigatedToEvent = "navigatedTo"; public static navigatingFromEvent = "navigatingFrom"; public static navigatedFromEvent = "navigatedFrom"; - + private _navigationContext: any; private _actionBar: ActionBar; public _frame: Frame; - + public actionBarHidden: boolean; public enableSwipeBackNavigation: boolean; public backgroundSpanUnderStatusBar: boolean; public hasActionBar: boolean; - + get navigationContext(): any { return this._navigationContext; } @@ -89,7 +89,7 @@ export class PageBase extends ContentView implements PageDefinition { const frame = this.parent; return frame instanceof Frame ? frame : undefined; } - + private createNavigatedData(eventName: string, isBackNavigation: boolean): NavigatedData { return { eventName: eventName, @@ -103,6 +103,13 @@ export class PageBase extends ContentView implements PageDefinition { public onNavigatingTo(context: any, isBackNavigation: boolean, bindingContext?: any) { this._navigationContext = context; + if (isBackNavigation && this._styleScope) { + this._styleScope.ensureSelectors(); + if (!this._cssState.isSelectorsLatestVersionApplied()) { + this._onCssStateChange(); + } + } + //https://github.com/NativeScript/NativeScript/issues/731 if (!isBackNavigation && bindingContext !== undefined && bindingContext !== null) { this.bindingContext = bindingContext; diff --git a/tns-core-modules/ui/scroll-view/scroll-view.ios.ts b/tns-core-modules/ui/scroll-view/scroll-view.ios.ts index eeade71b8..55a6f4b6b 100644 --- a/tns-core-modules/ui/scroll-view/scroll-view.ios.ts +++ b/tns-core-modules/ui/scroll-view/scroll-view.ios.ts @@ -3,9 +3,6 @@ import { View, layout, ScrollViewBase, scrollBarIndicatorVisibleProperty, isScrollEnabledProperty } from "./scroll-view-common"; import { ios as iosUtils } from "../../utils/utils"; -// HACK: Webpack. Use a fully-qualified import to allow resolve.extensions(.ios.js) to -// kick in. `../utils` doesn't seem to trigger the webpack extensions mechanism. -import * as uiUtils from "tns-core-modules/ui/utils"; export * from "./scroll-view-common"; @@ -193,18 +190,4 @@ export class ScrollView extends ScrollViewBase { } } -function getTabBarHeight(scrollView: ScrollView): number { - let parent = scrollView.parent; - while (parent) { - const controller = parent.viewController; - if (controller instanceof UITabBarController) { - return uiUtils.ios.getActualHeight(controller.tabBar); - } - - parent = parent.parent; - } - - return 0; -} - ScrollView.prototype.recycleNativeView = "auto"; diff --git a/tns-core-modules/ui/styling/style-scope.d.ts b/tns-core-modules/ui/styling/style-scope.d.ts index b404ce9a7..b5bf01684 100644 --- a/tns-core-modules/ui/styling/style-scope.d.ts +++ b/tns-core-modules/ui/styling/style-scope.d.ts @@ -19,6 +19,11 @@ export class CssState { * Gets the static selectors that match the view and the dynamic selectors that may potentially match the view. */ public changeMap: ChangeMap; + + /** + * Checks whether style scope and CSS state selectors are in sync. + */ + public isSelectorsLatestVersionApplied(): boolean } export class StyleScope { @@ -29,6 +34,9 @@ export class StyleScope { public static createSelectorsFromImports(tree: SyntaxTree, keyframes: Object): RuleSet[]; public ensureSelectors(): number; + public isApplicationCssSelectorsLatestVersionApplied(): boolean; + public isLocalCssSelectorsLatestVersionApplied(): boolean; + public applySelectors(view: ViewBase): void public query(options: Node): SelectorCore[]; diff --git a/tns-core-modules/ui/styling/style-scope.ts b/tns-core-modules/ui/styling/style-scope.ts index 8f3cf607c..a915b559d 100644 --- a/tns-core-modules/ui/styling/style-scope.ts +++ b/tns-core-modules/ui/styling/style-scope.ts @@ -271,7 +271,7 @@ export function removeTaggedAdditionalCSS(tag: String | Number): Boolean { changed = true; } } - if (changed) { mergeCssSelectors(); } + if (changed) { mergeCssSelectors(); } return changed; } @@ -343,6 +343,7 @@ export class CssState { _appliedChangeMap: Readonly>; _appliedPropertyValues: Readonly<{}>; _appliedAnimations: ReadonlyArray; + _appliedSelectorsVersion: number; _match: SelectorsMatch; _matchInvalid: boolean; @@ -367,6 +368,10 @@ export class CssState { } } + public isSelectorsLatestVersionApplied(): boolean { + return this.view._styleScope._getSelectorsVersion() === this._appliedSelectorsVersion; + } + public onLoaded(): void { if (this._matchInvalid) { this.updateMatch(); @@ -381,7 +386,12 @@ export class CssState { @profile private updateMatch() { - this._match = this.view._styleScope ? this.view._styleScope.matchSelectors(this.view) : CssState.emptyMatch; + if (this.view._styleScope) { + this._appliedSelectorsVersion = this.view._styleScope._getSelectorsVersion(); + this._match = this.view._styleScope.matchSelectors(this.view); + } else { + this._match = CssState.emptyMatch; + } this._matchInvalid = false; } @@ -597,8 +607,8 @@ export class StyleScope { } public ensureSelectors(): number { - if (this._applicationCssSelectorsAppliedVersion !== applicationCssSelectorVersion || - this._localCssSelectorVersion !== this._localCssSelectorsAppliedVersion || + if (!this.isApplicationCssSelectorsLatestVersionApplied() || + !this.isLocalCssSelectorsLatestVersionApplied() || !this._mergedCssSelectors) { this._createSelectors(); @@ -607,6 +617,14 @@ export class StyleScope { return this._getSelectorsVersion(); } + public isApplicationCssSelectorsLatestVersionApplied(): boolean { + return this._applicationCssSelectorsAppliedVersion === applicationCssSelectorVersion; + } + + public isLocalCssSelectorsLatestVersionApplied(): boolean { + return this._localCssSelectorsAppliedVersion === this._localCssSelectorVersion; + } + @profile private _createSelectors() { let toMerge: RuleSet[][] = []; diff --git a/tns-core-modules/utils/utils.ios.ts b/tns-core-modules/utils/utils.ios.ts index 77da535b7..8665fb40d 100644 --- a/tns-core-modules/utils/utils.ios.ts +++ b/tns-core-modules/utils/utils.ios.ts @@ -127,7 +127,6 @@ export module ios { } if (rootViewController.isKindOfClass(UITabBarController.class())) { - let selectedTab = (rootViewController).selectedViewController; return getVisibleViewController(rootViewController); } diff --git a/tns-platform-declarations/android/org.nativescript.widgets.d.ts b/tns-platform-declarations/android/org.nativescript.widgets.d.ts index ae96b44f3..053e9c728 100644 --- a/tns-platform-declarations/android/org.nativescript.widgets.d.ts +++ b/tns-platform-declarations/android/org.nativescript.widgets.d.ts @@ -5,10 +5,12 @@ export class CompleteCallback { constructor(implementation: ICompleteCallback); onComplete(result: Object, context: Object): void; + onError(error: string, context: Object): void; } export interface ICompleteCallback { onComplete(result: Object, context: Object): void; + onError(error: string, context: Object): void; } export module Image { diff --git a/tns-platform-declarations/package.json b/tns-platform-declarations/package.json index 7bda26d49..b3d093a34 100644 --- a/tns-platform-declarations/package.json +++ b/tns-platform-declarations/package.json @@ -1,6 +1,6 @@ { "name": "tns-platform-declarations", - "version": "5.1.2", + "version": "5.2.0", "description": "Platform-specific TypeScript declarations for NativeScript for accessing native objects", "main": "", "scripts": {