diff --git a/.travis.yml b/.travis.yml index 391fb59dd..1f0d80b77 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ before_script: - "(cd build/platform-declarations && npm install)" - echo no | android create avd --force -n $AVD_NAME -t android-$EMULATOR_API_VER -b armeabi-v7a -c 12M - - emulator -avd $AVD_NAME -skin WXGA720 -no-audio -no-window & + - emulator -avd $AVD_NAME -skin WXGA720 -no-audio -no-window -memory 2048 & - android-wait-for-emulator script: - jdk_switcher use oraclejdk8 diff --git a/apps/perf-tests/nav-page.ts b/apps/perf-tests/nav-page.ts index 6ce578038..8422307fd 100644 --- a/apps/perf-tests/nav-page.ts +++ b/apps/perf-tests/nav-page.ts @@ -39,25 +39,26 @@ export class NavPage extends Page implements definition.ControlsPage { var that = this; that.on(View.loadedEvent, (args) => { - console.log(`Loaded ${args.object}`); + console.log(`NavPage: Loaded ${args.object}`); if (topmostFrame().android) { + console.log(`NavPage: topmostFrame().android.cachePagesOnNavigate = true;`); topmostFrame().android.cachePagesOnNavigate = true; } }); that.on(View.unloadedEvent, (args) => { - console.log(`Unloaded ${args.object}`); + console.log(`NavPage: Unloaded ${args.object}`); }); that.on(Page.navigatingFromEvent, (args: NavigatedData) => { - console.log(`NavigatING FROM ${args.object}, isBackNavigation: ${args.isBackNavigation}`); + console.log(`NavPage: NavigatING FROM ${args.object}, isBackNavigation: ${args.isBackNavigation}`); }); that.on(Page.navigatedFromEvent, (args: NavigatedData) => { - console.log(`NaviagatED FROM ${args.object}, isBackNavigation: ${args.isBackNavigation}`); + console.log(`NavPage: NaviagatED FROM ${args.object}, isBackNavigation: ${args.isBackNavigation}`); }); that.on(Page.navigatingToEvent, (args: NavigatedData) => { - console.log(`NavigatING TO ${args.object}, isBackNavigation: ${args.isBackNavigation}`); + console.log(`NavPage: NavigatING TO ${args.object}, isBackNavigation: ${args.isBackNavigation}`); }); that.on(Page.navigatedToEvent, (args: NavigatedData) => { - console.log(`NavigatED TO ${args.object}, isBackNavigation: ${args.isBackNavigation}`); + console.log(`NavPage: NavigatED TO ${args.object}, isBackNavigation: ${args.isBackNavigation}`); (topmostFrame())._printFrameBackStack(); if (topmostFrame().android) { (topmostFrame())._printNativeBackStack(); diff --git a/apps/tests/TKUnit.ts b/apps/tests/TKUnit.ts index 1ff5bf860..2103172fa 100644 --- a/apps/tests/TKUnit.ts +++ b/apps/tests/TKUnit.ts @@ -60,7 +60,7 @@ var runTest = function (testInfo: TestInfoEntry) { if (testInfo.isTest) { duration = time() - start; testInfo.duration = duration; - write("--- [" + testInfo.testName + "] OK, duration: " + duration, trace.messageType.info); + write(`--- [${testInfo.testName}] OK, duration: ${duration}`, trace.messageType.info); testInfo.isPassed = true; } } @@ -68,7 +68,7 @@ var runTest = function (testInfo: TestInfoEntry) { if (testInfo.isTest) { duration = time() - start; testInfo.duration = duration; - write("--- [" + testInfo.testName + "] FAILED: " + e.message + ", duration: " + duration, trace.messageType.error); + write(`--- [${testInfo.testName}] FAILED: ${e.message}, duration: ${duration}`, trace.messageType.error); testInfo.isPassed = false; testInfo.errorMessage = e.message; } diff --git a/apps/tests/app/mainPage.ts b/apps/tests/app/mainPage.ts index 7de1b21ba..18b64fce2 100644 --- a/apps/tests/app/mainPage.ts +++ b/apps/tests/app/mainPage.ts @@ -6,6 +6,16 @@ import {Label} from "ui/label"; trace.enable(); trace.addCategories(trace.categories.Test + "," + trace.categories.Error); +// When debugging +//trace.setCategories(trace.categories.concat( +// trace.categories.Test, +// trace.categories.Navigation, +// trace.categories.Transition, +// trace.categories.NativeLifecycle, +// trace.categories.ViewHierarchy, +// trace.categories.VisualTreeEvents +//)); + let page = new Page(); page.id = "mainPage"; diff --git a/apps/tests/navigation/navigation-tests.ts b/apps/tests/navigation/navigation-tests.ts index 2990a5fb7..965bb2120 100644 --- a/apps/tests/navigation/navigation-tests.ts +++ b/apps/tests/navigation/navigation-tests.ts @@ -4,63 +4,92 @@ import {topmost as topmostFrame, NavigationTransition} from "ui/frame"; import {Color} from "color"; // Creates a random colorful page full of meaningless stuff. -var pageFactory = function(): Page { +var id = 0; +var pageFactory = function (): Page { var page = new Page(); + page.actionBarHidden = true; + page.id = `NavTestPage${id++}`; page.style.backgroundColor = new Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)); return page; }; +function waitUntilNavigatedFrom(oldPage: Page) { + let topmost = topmostFrame(); + TKUnit.waitUntilReady(() => { + return topmost.currentPage + && topmost.currentPage !== oldPage + && topmost.currentPage.isLoaded + && !oldPage.isLoaded + ; + }); +} + +function androidGC() { + let topmost = topmostFrame(); + if (topmost.android) { + gc(); + java.lang.System.gc(); + } +} + function _test_backstackVisible(transition?: NavigationTransition) { let topmost = topmostFrame(); let mainTestPage = topmost.currentPage; - topmost.navigate({ create: pageFactory, transition: transition }); - TKUnit.waitUntilReady(() => { return topmost.currentPage !== mainTestPage; }); - + topmost.navigate({ create: pageFactory, transition: transition, animated: true }); + waitUntilNavigatedFrom(mainTestPage); + // page1 should not be added to the backstack let page0 = topmost.currentPage; - topmost.navigate({ create: pageFactory, backstackVisible: false, transition: transition }); - TKUnit.waitUntilReady(() => { return topmost.currentPage !== page0; }); + topmost.navigate({ create: pageFactory, backstackVisible: false, transition: transition, animated: true }); + waitUntilNavigatedFrom(page0); let page1 = topmost.currentPage; - topmost.navigate({ create: pageFactory, transition: transition }); - TKUnit.waitUntilReady(() => { return topmost.currentPage !== page1; }); - + topmost.navigate({ create: pageFactory, transition: transition, animated: true }); + waitUntilNavigatedFrom(page1); + let page2 = topmost.currentPage; topmost.goBack(); - TKUnit.waitUntilReady(() => { return topmost.currentPage !== page2; }); + waitUntilNavigatedFrom(page2); // From page2 we have to go directly to page0, skipping page1. TKUnit.assert(topmost.currentPage === page0, "Page 1 should be skipped when going back."); topmost.goBack(); - TKUnit.waitUntilReady(() => { return topmost.currentPage === mainTestPage; }); + waitUntilNavigatedFrom(page0); + TKUnit.assertEqual(topmost.currentPage, mainTestPage, "We should be on the main test page at the end of the test."); } export var test_backstackVisible = function () { + androidGC(); _test_backstackVisible(); } export var test_backstackVisible_WithTransition = function () { + androidGC(); _test_backstackVisible({name: "fade"}); } function _test_backToEntry(transition?: NavigationTransition) { + let topmost = topmostFrame(); let page = (tag) => () => { var p = new Page(); + p.actionBarHidden = true; + p.id = `NavTestPage${id++}`; p["tag"] = tag; return p; } - let topmost = topmostFrame(); - let wait = tag => TKUnit.waitUntilReady(() => topmost.currentPage["tag"] === tag, 1); + let mainTestPage = topmost.currentPage; + let waitFunc = tag => TKUnit.waitUntilReady(() => topmost.currentPage["tag"] === tag, 1); let navigate = tag => { - topmost.navigate({ create: page(tag), transition: transition }); - wait(tag) + topmost.navigate({ create: page(tag), transition: transition, animated: true }); + waitFunc(tag); + } let back = pages => { topmost.goBack(topmost.backStack[topmost.backStack.length - pages]); } let currentPageMustBe = tag => { - wait(tag); // TODO: Add a timeout... + waitFunc(tag); // TODO: Add a timeout... TKUnit.assert(topmost.currentPage["tag"] === tag, "Expected current page to be " + tag + " it was " + topmost.currentPage["tag"] + " instead."); } @@ -81,14 +110,19 @@ function _test_backToEntry(transition?: NavigationTransition) { currentPageMustBe("page1.1"); back(1); currentPageMustBe("page1"); + let page1 = topmost.currentPage; back(1); + waitUntilNavigatedFrom(page1); + TKUnit.assertEqual(topmost.currentPage, mainTestPage, "We should be on the main test page at the end of the test."); } export var test_backToEntry = function () { + androidGC(); _test_backToEntry(); } export var test_backToEntry_WithTransition = function () { + androidGC(); _test_backToEntry({name: "flip"}); } @@ -102,85 +136,148 @@ function _test_ClearHistory(transition?: NavigationTransition) { var currentPage: Page; currentPage = topmost.currentPage; - topmost.navigate({ create: pageFactory, clearHistory: true, transition: transition}); - TKUnit.waitUntilReady(() => { return topmost.currentPage !== currentPage; }); + topmost.navigate({ create: pageFactory, clearHistory: true, transition: transition, animated: true }); + waitUntilNavigatedFrom(currentPage); + TKUnit.assertEqual(topmost.backStack.length, 0, "1.topmost.backStack.length"); + TKUnit.assertEqual(topmost.canGoBack(), false, "1.topmost.canGoBack()."); currentPage = topmost.currentPage; - topmost.navigate({ create: pageFactory, transition: transition }); - TKUnit.waitUntilReady(() => { return topmost.currentPage !== currentPage; }); + topmost.navigate({ create: pageFactory, transition: transition, animated: true }); + waitUntilNavigatedFrom(currentPage); + TKUnit.assertEqual(topmost.backStack.length, 1, "2.topmost.backStack.length"); + TKUnit.assertEqual(topmost.canGoBack(), true, "2.topmost.canGoBack()."); currentPage = topmost.currentPage; - topmost.navigate({ create: pageFactory, transition: transition }); - TKUnit.waitUntilReady(() => { return topmost.currentPage !== currentPage; }); + topmost.navigate({ create: pageFactory, transition: transition, animated: true }); + waitUntilNavigatedFrom(currentPage); + TKUnit.assertEqual(topmost.backStack.length, 2, "3.topmost.backStack.length"); + TKUnit.assertEqual(topmost.canGoBack(), true, "3.topmost.canGoBack()."); currentPage = topmost.currentPage; - topmost.navigate({ create: pageFactory, transition: transition }); - TKUnit.waitUntilReady(() => { return topmost.currentPage !== currentPage; }); + topmost.navigate({ create: pageFactory, clearHistory: true, transition: transition, animated: true }); + waitUntilNavigatedFrom(currentPage); + TKUnit.assertEqual(topmost.backStack.length, 0, "4.topmost.backStack.length"); + TKUnit.assertEqual(topmost.canGoBack(), false, "4.topmost.canGoBack()."); - TKUnit.assert(topmost.canGoBack(), "Frame should be able to go back."); - TKUnit.assert(topmost.backStack.length === 3, "Back stack should have 3 entries."); - - // Navigate with clear history. currentPage = topmost.currentPage; - topmost.navigate({ create: pageFactory, clearHistory: true, transition: transition }); - TKUnit.waitUntilReady(() => { return topmost.currentPage !== currentPage; }); + topmost.navigate({ create: mainPageFactory, clearHistory: true, animated: false }); + waitUntilNavigatedFrom(currentPage); + TKUnit.assertEqual(topmost.backStack.length, 0, "5.topmost.backStack.length"); + TKUnit.assertEqual(topmost.canGoBack(), false, "5.topmost.canGoBack()."); - TKUnit.assert(!topmost.canGoBack(), "Frame should NOT be able to go back."); - TKUnit.assert(topmost.backStack.length === 0, "Back stack should have 0 entries."); - - topmost.navigate({ create: mainPageFactory, transition: transition }); - TKUnit.waitUntilReady(() => { return topmost.currentPage === mainTestPage; }); + TKUnit.assertEqual(topmost.currentPage, mainTestPage, "We should be on the main test page at the end of the test."); + TKUnit.assertEqual(topmost.backStack.length, 0, "Back stack should be empty at the end of the test."); } // Clearing the history messes up the tests app. export var test_ClearHistory = function () { + androidGC(); _test_ClearHistory(); } export var test_ClearHistory_WithTransition = function () { - _test_ClearHistory({ name: "slide" }); + androidGC(); + _test_ClearHistory({ name: "fade" }); +} + +export var test_ClearHistory_WithTransition_WithCachePagesOnNavigate = function () { + androidGC(); + let topmost = topmostFrame(); + if (!topmost.android) { + return; + } + + let originalCachePagesOnNavigate = topmost.android.cachePagesOnNavigate; + topmostFrame().android.cachePagesOnNavigate = true; + _test_ClearHistory({ name: "fade" }); + topmostFrame().android.cachePagesOnNavigate = originalCachePagesOnNavigate; } // Test case for https://github.com/NativeScript/NativeScript/issues/1948 export var test_ClearHistoryWithTransitionDoesNotBreakNavigation = function () { + androidGC(); let topmost = topmostFrame(); - let mainTestPage = topmost.currentPage; let mainPageFactory = function (): Page { return mainTestPage; }; // Go to details-page - topmost.navigate({ create: pageFactory, clearHistory: false }); - TKUnit.waitUntilReady(() => { return topmost.currentPage !== mainTestPage; }); - + topmost.navigate({ create: pageFactory, clearHistory: false, animated: true }); + waitUntilNavigatedFrom(mainTestPage); + // Go back to main-page with clearHistory var detailsPage: Page; detailsPage = topmost.currentPage; topmost.transition = { name: "fade" }; - topmost.navigate({ create: mainPageFactory, clearHistory: true }); - TKUnit.waitUntilReady(() => { return topmost.currentPage === mainTestPage; }); - + topmost.navigate({ create: mainPageFactory, clearHistory: true, animated: true }); + waitUntilNavigatedFrom(detailsPage); + // Go to details-page AGAIN - topmost.navigate({ create: pageFactory, clearHistory: false }); - TKUnit.waitUntilReady(() => { return topmost.currentPage !== mainTestPage; }); + topmost.navigate({ create: pageFactory, clearHistory: false, animated: true }); + waitUntilNavigatedFrom(mainTestPage); // Go back to main-page with clearHistory detailsPage = topmost.currentPage; topmost.transition = { name: "fade" }; - topmost.navigate({ create: mainPageFactory, clearHistory: true }); - TKUnit.waitUntilReady(() => { return topmost.currentPage === mainTestPage; }); - + topmost.navigate({ create: mainPageFactory, clearHistory: true, animated: true }); + waitUntilNavigatedFrom(detailsPage); + // Clean up topmost.transition = undefined; + + TKUnit.assertEqual(topmost.currentPage, mainTestPage, "We should be on the main test page at the end of the test."); + TKUnit.assertEqual(topmost.backStack.length, 0, "Back stack should be empty at the end of the test."); +} + +export var test_ClearHistoryWithTransitionDoesNotBreakNavigation_WithLocalTransition = function () { + androidGC(); + let topmost = topmostFrame(); + let originalCachePagesOnNavigate: boolean; + if (topmost.android) { + originalCachePagesOnNavigate = topmost.android.cachePagesOnNavigate; + topmostFrame().android.cachePagesOnNavigate = true; + } + + let mainTestPage = topmost.currentPage; + let mainPageFactory = function (): Page { + return mainTestPage; + }; + + // Go to 1st page + var currentPage = topmost.currentPage; + topmost.navigate({ create: pageFactory, clearHistory: false, transition: { name: "fade" }, animated: true }); + waitUntilNavigatedFrom(currentPage); + + // Go to 2nd page + currentPage = topmost.currentPage; + topmost.navigate({ create: pageFactory, clearHistory: false, transition: { name: "fade" }, animated: true }); + waitUntilNavigatedFrom(currentPage); + + // Go to 3rd page with clearHistory + currentPage = topmost.currentPage; + topmost.navigate({ create: pageFactory, clearHistory: true, transition: { name: "fade" }, animated: true }); + waitUntilNavigatedFrom(currentPage); + + // Go back to main + currentPage = topmost.currentPage; + topmost.navigate({ create: mainPageFactory, clearHistory: true, transition: { name: "fade" }, animated: true }); + waitUntilNavigatedFrom(currentPage); + + if (topmost.android) { + topmostFrame().android.cachePagesOnNavigate = originalCachePagesOnNavigate; + } + + TKUnit.assertEqual(topmost.currentPage, mainTestPage, "We should be on the main test page at the end of the test."); + TKUnit.assertEqual(topmost.backStack.length, 0, "Back stack should be empty at the end of the test."); } function _test_NavigationEvents(transition?: NavigationTransition) { + let topmost = topmostFrame(); let argsToString = (args: NavigatedData) => { return `${(args.object).id} ${args.eventName} ${(args.isBackNavigation ? "back" : "forward") }` }; - let topmost = topmostFrame(); let mainTestPage = topmost.currentPage; let originalMainPageId = mainTestPage.id; mainTestPage.id = "main-page"; @@ -193,6 +290,7 @@ function _test_NavigationEvents(transition?: NavigationTransition) { let actualSecondPageEvents = new Array(); let secondPageFactory = function (): Page { var secondPage = new Page(); + secondPage.actionBarHidden = true; secondPage.id = "second-page" secondPage.on(Page.navigatingFromEvent, (args: NavigatedData) => { actualSecondPageEvents.push(argsToString(args)); }); secondPage.on(Page.navigatedFromEvent, (args: NavigatedData) => { actualSecondPageEvents.push(argsToString(args)); }); @@ -203,12 +301,14 @@ function _test_NavigationEvents(transition?: NavigationTransition) { }; // Go to other page - topmost.navigate({ create: secondPageFactory, transition: transition }); - TKUnit.waitUntilReady(() => { return topmost.currentPage !== mainTestPage; }); - + topmost.navigate({ create: secondPageFactory, transition: transition, animated: true }); + waitUntilNavigatedFrom(mainTestPage); + // Go back to main + let currentPage = topmost.currentPage; topmost.goBack(); - TKUnit.waitUntilReady(() => { return topmost.currentPage === mainTestPage; }); + waitUntilNavigatedFrom(currentPage); + mainTestPage.id = originalMainPageId; let expectedMainPageEvents = [ @@ -227,12 +327,15 @@ function _test_NavigationEvents(transition?: NavigationTransition) { ]; TKUnit.arrayAssert(actualSecondPageEvents, expectedSecondPageEvents, "Actual second-page events are different from expected."); + TKUnit.assertEqual(topmost.currentPage, mainTestPage, "We should be on the main test page at the end of the test."); } export var test_NavigationEvents = function () { + androidGC(); _test_NavigationEvents(); } export var test_NavigationEvents_WithTransition = function () { - _test_NavigationEvents({ name: "slide" }); + androidGC(); + _test_NavigationEvents({ name: "fade" }); } \ No newline at end of file diff --git a/apps/tests/navigation/transition-tests.ts b/apps/tests/navigation/transition-tests.ts index c4aebddc4..835f5c8f8 100644 --- a/apps/tests/navigation/transition-tests.ts +++ b/apps/tests/navigation/transition-tests.ts @@ -3,7 +3,7 @@ import * as helper from "../ui/helper"; import * as platform from "platform"; import * as trace from "trace"; import {Color} from "color"; -import {NavigationEntry, NavigationTransition} from "ui/frame"; +import {NavigationEntry, NavigationTransition, topmost as topmostFrame} from "ui/frame"; import {Page} from "ui/page"; import {AnimationCurve} from "ui/enums" @@ -29,6 +29,12 @@ function _testTransition(navigationTransition: NavigationTransition) { // Extremely slow. Run only if needed. export var test_Transitions = function () { + let topmost = topmostFrame(); + let mainTestPage = topmost.currentPage; + let mainPageFactory = function (): Page { + return mainTestPage; + }; + helper.navigate(() => { var page = new Page(); page.id = "TransitionsTestPage_MAIN" @@ -77,4 +83,14 @@ export var test_Transitions = function () { var customTransition = new customTransitionModule.CustomTransition(); _testTransition({ instance: customTransition }); } + + var oldPage = topmost.currentPage; + topmost.navigate({ create: mainPageFactory, clearHistory: true, animated: false }); + TKUnit.waitUntilReady(() => { + return topmost.currentPage + && topmost.currentPage !== oldPage + && topmost.currentPage.isLoaded + && !oldPage.isLoaded + ; + }); } diff --git a/ui/core/view-common.ts b/ui/core/view-common.ts index 462c08ac1..c76a1a6cb 100644 --- a/ui/core/view-common.ts +++ b/ui/core/view-common.ts @@ -968,6 +968,10 @@ export class View extends ProxyObject implements definition.View { * // TODO: Think whether we need the base Layout routine. */ public _addView(view: View, atIndex?: number) { + if (trace.enabled) { + trace.write(`${this}._addView(${view}, ${atIndex})`, trace.categories.ViewHierarchy); + } + if (!view) { throw new Error("Expecting a valid View instance."); } @@ -979,10 +983,6 @@ export class View extends ProxyObject implements definition.View { view._parent = this; this._addViewCore(view, atIndex); view._parentChanged(null); - - if (trace.enabled) { - trace.write("called _addView on view " + this._domId + " for a child " + view._domId, trace.categories.ViewHierarchy); - } } /** @@ -1028,6 +1028,9 @@ export class View extends ProxyObject implements definition.View { * Core logic for removing a child view from this instance. Used by the framework to handle lifecycle events more centralized. Do not outside the UI Stack implementation. */ public _removeView(view: View) { + if (trace.enabled) { + trace.write(`${this}._removeView(${view})`, trace.categories.ViewHierarchy); + } if (view._parent !== this) { throw new Error("View not added to this instance. View: " + view + " CurrentParent: " + view._parent + " ExpectedParent: " + this); } @@ -1035,10 +1038,6 @@ export class View extends ProxyObject implements definition.View { this._removeViewCore(view); view._parent = undefined; view._parentChanged(this); - - if (trace.enabled) { - trace.write("called _removeView on view " + this._domId + " for a child " + view._domId, trace.categories.ViewHierarchy); - } } /** diff --git a/ui/core/view.android.ts b/ui/core/view.android.ts index a2f0e7b2a..65830d7a3 100644 --- a/ui/core/view.android.ts +++ b/ui/core/view.android.ts @@ -187,11 +187,10 @@ export class View extends viewCommon.View { if (!context) { throw new Error("Expected valid android.content.Context instance."); } - - if (trace.enabled) { - trace.write("calling _onAttached on view " + this._domId, trace.categories.VisualTreeEvents); - } + if (trace.enabled) { + trace.write(`${this}._onAttached(context)`, trace.categories.VisualTreeEvents); + } if (this._context === context) { return; } @@ -221,6 +220,9 @@ export class View extends viewCommon.View { } public _onDetached(force?: boolean) { + if (trace.enabled) { + trace.write(`${this}._onDetached(force)`, trace.categories.VisualTreeEvents); + } if (this._childrenCount > 0) { // Detach children first var that = this; @@ -236,10 +238,6 @@ export class View extends viewCommon.View { this._eachChildView(eachChild); } - if (trace.enabled) { - trace.write("calling _onDetached on view " + this._domId, trace.categories.VisualTreeEvents); - } - this._clearAndroidReference(); this._context = undefined; @@ -265,9 +263,8 @@ export class View extends viewCommon.View { public _onContextChanged() { if (trace.enabled) { - trace.write("calling _onContextChanged on view " + this._domId, trace.categories.VisualTreeEvents); + trace.write(`${this}._onContextChanged`, trace.categories.VisualTreeEvents); } - this._createUI(); // Ensure layout params if (this._nativeView && !(this._nativeView.getLayoutParams() instanceof org.nativescript.widgets.CommonLayoutParams)) { @@ -451,9 +448,15 @@ export class CustomLayoutView extends View implements viewDefinition.CustomLayou if (this._nativeView && child._nativeView) { if (types.isNullOrUndefined(atIndex) || atIndex >= this._nativeView.getChildCount()) { + if (trace.enabled) { + trace.write(`${this}._nativeView.addView(${child}._nativeView)`, trace.categories.VisualTreeEvents); + } this._nativeView.addView(child._nativeView); } else { + if (trace.enabled) { + trace.write(`${this}._nativeView.addView(${child}._nativeView, ${atIndex})`, trace.categories.VisualTreeEvents); + } this._nativeView.addView(child._nativeView, atIndex); } return true; @@ -466,6 +469,9 @@ export class CustomLayoutView extends View implements viewDefinition.CustomLayou super._removeViewFromNativeVisualTree(child); if (this._nativeView && child._nativeView) { + if (trace.enabled) { + trace.write(`${this}._nativeView.removeView(${child}._nativeView)`, trace.categories.VisualTreeEvents); + } this._nativeView.removeView(child._nativeView); trace.notifyEvent(child, "childInLayoutRemovedFromNativeVisualTree"); } diff --git a/ui/frame/frame-common.ts b/ui/frame/frame-common.ts index 661c170b0..9886985e9 100644 --- a/ui/frame/frame-common.ts +++ b/ui/frame/frame-common.ts @@ -219,6 +219,9 @@ export class Frame extends CustomLayoutView implements definition.Frame { var backstackEntry: definition.BackstackEntry = { entry: entry, resolvedPage: page, + navDepth: undefined, + fragmentTag: undefined, + isBack: undefined, isNavigation: true }; @@ -266,7 +269,7 @@ export class Frame extends CustomLayoutView implements definition.Frame { return this._navigationQueue.length === 0; } - public _isEntryBackstackVisible(entry: definition.BackstackEntry): boolean { + public static _isEntryBackstackVisible(entry: definition.BackstackEntry): boolean { if (!entry) { return false; } @@ -297,7 +300,7 @@ export class Frame extends CustomLayoutView implements definition.Frame { if (navigationContext.entry.entry.clearHistory) { this._backStack.length = 0; } - else if (this._isEntryBackstackVisible(this._currentEntry)) { + else if (Frame._isEntryBackstackVisible(this._currentEntry)) { this._backStack.push(this._currentEntry); } @@ -313,11 +316,15 @@ export class Frame extends CustomLayoutView implements definition.Frame { } public _goBackCore(backstackEntry: definition.BackstackEntry) { - // + if (trace.enabled) { + trace.write(`${this}._goBackCore(${this._backstackEntryTrace(backstackEntry) }); ${this}.currentPage: ${this.currentPage}`, trace.categories.Navigation); + } } public _navigateCore(backstackEntry: definition.BackstackEntry) { - // + if (trace.enabled) { + trace.write(`${this}._navigateCore(${this._backstackEntryTrace(backstackEntry) }); ${this}.currentPage: ${this.currentPage}`, trace.categories.Navigation); + } } public _onNavigatingTo(backstackEntry: definition.BackstackEntry, isBack: boolean) { @@ -452,13 +459,37 @@ export class Frame extends CustomLayoutView implements definition.Frame { public _printFrameBackStack() { var length = this.backStack.length; var i = length - 1; - console.log("---------------------------"); - console.log("Frame Back Stack (" + length + ")"); + console.log(`Frame Back Stack: `); while (i >= 0) { var backstackEntry = this.backStack[i--]; - console.log("[ " + backstackEntry.resolvedPage.id + " ]"); + console.log(`\t${backstackEntry.resolvedPage}`); } } + + public _backstackEntryTrace(b: definition.BackstackEntry): string { + let result = `${b.resolvedPage}`; + + let backstackVisible = Frame._isEntryBackstackVisible(b); + if (!backstackVisible) { + result += ` | INVISIBLE`; + } + + if (b.entry.clearHistory) { + result += ` | CLEAR HISTORY`; + } + + let animated = this._getIsAnimatedNavigation(b.entry); + if (!animated) { + result += ` | NOT ANIMATED`; + } + + let t = this._getNavigationTransition(b.entry); + if (t) { + result += ` | Transition[${JSON.stringify(t)}]`; + } + + return result; + } } var _topmost = function (): Frame { @@ -488,3 +519,4 @@ export function goBack(): boolean { export function stack(): Array { return frameStack; } + diff --git a/ui/frame/frame.android.ts b/ui/frame/frame.android.ts index 0d5941351..751b46296 100644 --- a/ui/frame/frame.android.ts +++ b/ui/frame/frame.android.ts @@ -10,27 +10,22 @@ import * as types from "utils/types"; global.moduleMerge(frameCommon, exports); -var TAG = "_fragmentTag"; -var HIDDEN = "_hidden"; -var INTENT_EXTRA = "com.tns.activity"; -var BACKSTACK_TAG = "_backstackTag"; -var IS_BACK = "_isBack"; -var NAV_DEPTH = "_navDepth"; -var CLEARING_HISTORY = "_clearingHistory"; -var FRAMEID = "_frameId"; -var FRAGMENT = "_FRAGMENT"; - -var navDepth = -1; - -var activityInitialized = false; +let HIDDEN = "_hidden"; +let INTENT_EXTRA = "com.tns.activity"; +let FRAMEID = "_frameId"; +let navDepth = -1; +let fragmentId = -1; +let activityInitialized = false; function onFragmentShown(fragment: FragmentClass) { if (trace.enabled) { - trace.write(`SHOWN ${fragment.getTag()}`, trace.categories.NativeLifecycle); + trace.write(`SHOWN ${fragment}`, trace.categories.NativeLifecycle); } - if (fragment[CLEARING_HISTORY]) { + if (fragment.clearHistory) { + // This is the fragment which was at the bottom of the stack (fragment0) when we cleared history and called + // manager.popBackStack(firstEntryName, android.app.FragmentManager.POP_BACK_STACK_INCLUSIVE); if (trace.enabled) { - trace.write(`${fragment.getTag()} has been shown, but we are currently clearing history. Returning.`, trace.categories.NativeLifecycle); + trace.write(`${fragment} has been shown, but it is being cleared from history. Returning.`, trace.categories.NativeLifecycle); } return null; } @@ -51,6 +46,9 @@ function onFragmentShown(fragment: FragmentClass) { } var isBack = currentNavigationContext ? currentNavigationContext.isBackNavigation : false; + + transitionModule._removePageNativeViewFromAndroidParent(page); + frame._addView(page); // onFragmentShown is called before NativeActivity.start where we call frame.onLoaded @@ -67,19 +65,11 @@ function onFragmentShown(fragment: FragmentClass) { function onFragmentHidden(fragment: FragmentClass, destroyed: boolean) { if (trace.enabled) { - trace.write(`HIDDEN ${fragment.getTag()}`, trace.categories.NativeLifecycle); + trace.write(`HIDDEN ${fragment}; destroyed: ${destroyed}`, trace.categories.NativeLifecycle); } - if (fragment[CLEARING_HISTORY]) { - if (trace.enabled) { - trace.write(`${fragment.getTag()} has been hidden, but we are currently clearing history. Clearing any existing transitions.`, trace.categories.NativeLifecycle); - } - transitionModule._clearBackwardTransitions(fragment); - transitionModule._clearForwardTransitions(fragment); - } - - var isBack = fragment.entry[IS_BACK]; - fragment.entry[IS_BACK] = undefined; + var isBack = fragment.entry.isBack; + fragment.entry.isBack = undefined; // Handle page transitions. transitionModule._onFragmentHidden(fragment, isBack, destroyed); @@ -126,9 +116,7 @@ export class Frame extends frameCommon.Frame { } public _navigateCore(backstackEntry: definition.BackstackEntry) { - if (trace.enabled) { - trace.write(`${this}._navigateCore(page: ${backstackEntry.resolvedPage}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry)}, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation); - } + super._navigateCore(backstackEntry); let activity = this._android.activity; if (!activity) { @@ -145,68 +133,61 @@ export class Frame extends frameCommon.Frame { } let manager = activity.getFragmentManager(); - let isFirstNavigation = types.isNullOrUndefined(this._currentEntry); - backstackEntry.isNavigation = true; + // Current Fragment + var currentFragment: FragmentClass; if (this._currentEntry) { this._currentEntry.isNavigation = true; + currentFragment = manager.findFragmentByTag(this._currentEntry.fragmentTag); } - // Clear history - if (backstackEntry.entry.clearHistory) { - let backStackEntryCount = manager.getBackStackEntryCount(); - let i = backStackEntryCount - 1; - let fragment: android.app.Fragment; - while (i >= 0) { - fragment = manager.findFragmentByTag(manager.getBackStackEntryAt(i--).getName()); - if (trace.enabled) { - trace.write(`${fragment.getTag()}[CLEARING_HISTORY] = true;`, trace.categories.NativeLifecycle); - } - fragment[CLEARING_HISTORY] = true; - } - - // Remember that the current fragment has never been added to the backStack, so mark it as well. - if (this.currentPage) { - fragment = manager.findFragmentByTag(this.currentPage[TAG]); - if (fragment) { - fragment[CLEARING_HISTORY] = true; - if (trace.enabled) { - trace.write(`${fragment.getTag()}[CLEARING_HISTORY] = true;`, trace.categories.NativeLifecycle); - } - } - } - - if (backStackEntryCount) { - let firstEntryName = manager.getBackStackEntryAt(0).getName(); - if (trace.enabled) { - trace.write(`manager.popBackStack(${firstEntryName}, android.app.FragmentManager.POP_BACK_STACK_INCLUSIVE);`, trace.categories.NativeLifecycle); - } - manager.popBackStack(firstEntryName, android.app.FragmentManager.POP_BACK_STACK_INCLUSIVE); - } - this._currentEntry = null; + let clearHistory = backstackEntry.entry.clearHistory; + + // New Fragment + if (clearHistory) { navDepth = -1; } - navDepth++; - - let fragmentTransaction = manager.beginTransaction(); - - var currentFragmentTag: string; - var currentFragment: android.app.Fragment; - if (this.currentPage) { - currentFragmentTag = this.currentPage[TAG]; - currentFragment = manager.findFragmentByTag(currentFragmentTag); - } - - var newFragmentTag = "fragment" + navDepth; + fragmentId++; + var newFragmentTag = `fragment${fragmentId}[${navDepth}]`; let newFragment = new FragmentClass(); - let args = new android.os.Bundle(); args.putInt(FRAMEID, this._android.frameId); newFragment.setArguments(args); + newFragment.frame = this; + newFragment.entry = backstackEntry; - var animated = this._getIsAnimatedNavigation(backstackEntry.entry); - var navigationTransition = this._getNavigationTransition(backstackEntry.entry); + // backstackEntry + backstackEntry.isNavigation = true; + backstackEntry.fragmentTag = newFragmentTag; + backstackEntry.navDepth = navDepth; + + // Clear History + let length = manager.getBackStackEntryCount(); + let emptyNativeBackStack = clearHistory && length > 0; + if (emptyNativeBackStack) { + for (let i = 0; i < length; i++) { + let fragmentToRemove = manager.findFragmentByTag(manager.getBackStackEntryAt(i).getName()); + Frame._clearHistory(fragmentToRemove); + } + if (currentFragment) { + Frame._clearHistory(currentFragment); + } + let firstEntryName = manager.getBackStackEntryAt(0).getName(); + if (trace.enabled) { + trace.write(`POP BACK STACK ${firstEntryName}`, trace.categories.Navigation); + } + manager.popBackStackImmediate(firstEntryName, android.app.FragmentManager.POP_BACK_STACK_INCLUSIVE); + } + + let fragmentTransaction = manager.beginTransaction(); + if (trace.enabled) { + trace.write(`BEGIN TRANSACTION ${fragmentTransaction}`, trace.categories.Navigation); + } + + // Transitions + let animated = this._getIsAnimatedNavigation(backstackEntry.entry); + let navigationTransition = this._getNavigationTransition(backstackEntry.entry); if (currentFragment) { // There might be transitions left over from previous forward navigations from the current page. transitionModule._clearForwardTransitions(currentFragment); @@ -216,101 +197,89 @@ export class Frame extends frameCommon.Frame { } } - newFragment.frame = this; - newFragment.entry = backstackEntry; - - // Cahce newFragment at backstackEntry instance so that it cannot die while backstackEntry is alive. - backstackEntry[FRAGMENT] = newFragment; - - backstackEntry[BACKSTACK_TAG] = newFragmentTag; - backstackEntry[NAV_DEPTH] = navDepth; - - // remember the fragment tag at page level so that we can retrieve the fragment associated with a Page instance - backstackEntry.resolvedPage[TAG] = newFragmentTag; - - if (isFirstNavigation) { - fragmentTransaction.add(this.containerViewId, newFragment, newFragmentTag); - if (trace.enabled) { - trace.write(`fragmentTransaction.add(${newFragmentTag});`, trace.categories.NativeLifecycle); - } - } - else { - if (this.android.cachePagesOnNavigate && !backstackEntry.entry.clearHistory) { - if (currentFragment) { - fragmentTransaction.hide(currentFragment); - if (trace.enabled) { - trace.write(`fragmentTransaction.hide(${currentFragmentTag});`, trace.categories.NativeLifecycle); - } - } - else { - if (trace.enabled) { - trace.write(`Could not find ${currentFragmentTag} to hide.`, trace.categories.NativeLifecycle); - } - } - - fragmentTransaction.add(this.containerViewId, newFragment, newFragmentTag); + // Hide/remove current fragment if it exists and was not popped + if (currentFragment && !emptyNativeBackStack) { + if (this.android.cachePagesOnNavigate && !clearHistory) { if (trace.enabled) { - trace.write(`fragmentTransaction.add(${newFragmentTag});`, trace.categories.NativeLifecycle); + trace.write(`\tHIDE ${currentFragment}`, trace.categories.Navigation); } + fragmentTransaction.hide(currentFragment); } else { - fragmentTransaction.replace(this.containerViewId, newFragment, newFragmentTag); if (trace.enabled) { - trace.write(`fragmentTransaction.replace(${newFragmentTag});`, trace.categories.NativeLifecycle); - } - } - - // Add to backStack if needed. - if (this.backStack.length > 0 && this._currentEntry) { - // We add each entry in the backstack to avoid the "Stack corrupted" mismatch - let backstackTag = this._currentEntry[BACKSTACK_TAG]; - fragmentTransaction.addToBackStack(backstackTag); - if (trace.enabled) { - trace.write(`fragmentTransaction.addToBackStack(${backstackTag});`, trace.categories.NativeLifecycle); + trace.write(`\tREMOVE ${currentFragment}`, trace.categories.Navigation); } + fragmentTransaction.remove(currentFragment); } } - if (!isFirstNavigation) { + // Add newFragment + if (trace.enabled) { + trace.write(`\tADD ${newFragmentTag}<${newFragment.entry.resolvedPage}>`, trace.categories.Navigation); + } + fragmentTransaction.add(this.containerViewId, newFragment, newFragmentTag); + + // addToBackStack + if (this.backStack.length > 0 && currentFragment && !clearHistory) { + // We add each entry in the backstack to avoid the "Stack corrupted" mismatch + if (trace.enabled) { + trace.write(`\tADD TO BACK STACK ${currentFragment}`, trace.categories.Navigation); + } + fragmentTransaction.addToBackStack(this._currentEntry.fragmentTag); + } + + if (currentFragment) { // This bug is fixed on API19+ ensureAnimationFixed(); + let trans: number; if (this.android.cachePagesOnNavigate && animationFixed < 0) { // Apparently, there is an Android bug with when hiding fragments with animation. // https://code.google.com/p/android/issues/detail?id=32405 // When bug is fixed use animated variable. - fragmentTransaction.setTransition(android.app.FragmentTransaction.TRANSIT_NONE); + trans = android.app.FragmentTransaction.TRANSIT_NONE; } else { - var transit = animated ? android.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN : android.app.FragmentTransaction.TRANSIT_NONE; - fragmentTransaction.setTransition(transit); + trans = animated ? android.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN : android.app.FragmentTransaction.TRANSIT_NONE; } + if (trace.enabled) { + trace.write(`\tSET TRANSITION ${trans === 0 ? "NONE" : "OPEN"}`, trace.categories.Navigation); + } + fragmentTransaction.setTransition(trans); } fragmentTransaction.commit(); if (trace.enabled) { - trace.write(`fragmentTransaction.commit();`, trace.categories.NativeLifecycle); + trace.write(`END TRANSACTION ${fragmentTransaction}`, trace.categories.Navigation); } } + private static _clearHistory(fragment: FragmentClass) { + if (trace.enabled) { + trace.write(`CLEAR HISTORY FOR ${fragment}`, trace.categories.Navigation); + } + fragment.clearHistory = true; + transitionModule._clearBackwardTransitions(fragment); + transitionModule._clearForwardTransitions(fragment); + transitionModule._removePageNativeViewFromAndroidParent(fragment.entry.resolvedPage); + } + public _goBackCore(backstackEntry: definition.BackstackEntry) { - navDepth = backstackEntry[NAV_DEPTH]; + super._goBackCore(backstackEntry); + + navDepth = backstackEntry.navDepth; backstackEntry.isNavigation = true; if (this._currentEntry) { // We need this information inside onFragmentHidden - this._currentEntry[IS_BACK] = true; + this._currentEntry.isBack = true; this._currentEntry.isNavigation = true; } - if (trace.enabled) { - trace.write(`${this}._goBackCore(pageId: ${backstackEntry.resolvedPage.id}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry)}, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation); - } - var manager = this._android.activity.getFragmentManager(); if (manager.getBackStackEntryCount() > 0) { // pop all other fragments up until the named one // this handles cases where user may navigate to an inner page without adding it on the backstack - manager.popBackStack(backstackEntry[BACKSTACK_TAG], android.app.FragmentManager.POP_BACK_STACK_INCLUSIVE); + manager.popBackStack(backstackEntry.fragmentTag, android.app.FragmentManager.POP_BACK_STACK_INCLUSIVE); } } @@ -360,11 +329,10 @@ export class Frame extends frameCommon.Frame { var manager = this._android.activity.getFragmentManager(); var length = manager.getBackStackEntryCount(); var i = length - 1; - console.log("---------------------------"); - console.log("Fragment Manager Back Stack (" + length + ")"); + console.log(`Fragment Manager Back Stack: `); while (i >= 0) { - var fragment = manager.findFragmentByTag(manager.getBackStackEntryAt(i--).getName()); - console.log("[ " + fragment.getTag() + " ]"); + var fragment = manager.findFragmentByTag(manager.getBackStackEntryAt(i--).getName()); + console.log(`\t${fragment}`); } } @@ -558,7 +526,7 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) { return; } - if (frame.currentPage && frame.currentPage[TAG] === fragmentTag) { + if (frame._currentEntry && frame._currentEntry.fragmentTag === fragmentTag) { page = frame.currentPage; entry = frame._currentEntry; if (trace.enabled) { @@ -568,7 +536,7 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) { else { var backStack = frame.backStack; for (var i = 0; i < backStack.length; i++) { - if (backStack[i].resolvedPage[TAG] === fragmentTag) { + if (backStack[i].fragmentTag === fragmentTag) { entry = backStack[i]; break; } @@ -584,8 +552,6 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) { if (page) { (fragment).frame = frame; (fragment).entry = entry; - - page[TAG] = fragmentTag; } else { //throw new Error(`Could not find a page for ${fragmentTag}.`); @@ -625,6 +591,7 @@ function ensureAnimationFixed() { class FragmentClass extends android.app.Fragment { public frame: Frame; public entry: definition.BackstackEntry; + public clearHistory: boolean; constructor() { super(); @@ -633,7 +600,7 @@ class FragmentClass extends android.app.Fragment { public onHiddenChanged(hidden: boolean): void { if (trace.enabled) { - trace.write(`${this.getTag()}.onHiddenChanged(${hidden})`, trace.categories.NativeLifecycle); + trace.write(`${this}.onHiddenChanged(${hidden})`, trace.categories.NativeLifecycle); } super.onHiddenChanged(hidden); if (hidden) { @@ -645,20 +612,29 @@ class FragmentClass extends android.app.Fragment { } public onCreateAnimator(transit: number, enter: boolean, nextAnim: number): android.animation.Animator { + var nextAnimString: string; + switch (nextAnim) { + case -10: nextAnimString = "enter"; break; + case -20: nextAnimString = "exit"; break; + case -30: nextAnimString = "popEnter"; break; + case -40: nextAnimString = "popExit"; break; + } + var animator = transitionModule._onFragmentCreateAnimator(this, nextAnim); + if (!animator) { animator = super.onCreateAnimator(transit, enter, nextAnim); } if (trace.enabled) { - trace.write(`${this.getTag()}.onCreateAnimator(${transit}, ${enter}, ${nextAnim}): ${animator}`, trace.categories.NativeLifecycle); + trace.write(`${this}.onCreateAnimator(${transit}, ${enter ? "enter" : "exit"}, ${nextAnimString}): ${animator}`, trace.categories.NativeLifecycle); } return animator; } public onCreate(savedInstanceState: android.os.Bundle): void { if (trace.enabled) { - trace.write(`${this.getTag()}.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle); + trace.write(`${this}.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle); } super.onCreate(savedInstanceState); super.setHasOptionsMenu(true); @@ -681,7 +657,7 @@ class FragmentClass extends android.app.Fragment { public onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View { if (trace.enabled) { - trace.write(`${this.getTag()}.onCreateView(inflater, container, ${savedInstanceState})`, trace.categories.NativeLifecycle); + trace.write(`${this}.onCreateView(inflater, container, ${savedInstanceState})`, trace.categories.NativeLifecycle); } var entry = this.entry; var page = entry.resolvedPage; @@ -698,7 +674,7 @@ class FragmentClass extends android.app.Fragment { public onSaveInstanceState(outState: android.os.Bundle): void { if (trace.enabled) { - trace.write(`${this.getTag()}.onSaveInstanceState(${outState})`, trace.categories.NativeLifecycle); + trace.write(`${this}.onSaveInstanceState(${outState})`, trace.categories.NativeLifecycle); } super.onSaveInstanceState(outState); if (this.isHidden()) { @@ -708,7 +684,7 @@ class FragmentClass extends android.app.Fragment { public onDestroyView(): void { if (trace.enabled) { - trace.write(`${this.getTag()}.onDestroyView()`, trace.categories.NativeLifecycle); + trace.write(`${this}.onDestroyView()`, trace.categories.NativeLifecycle); } super.onDestroyView(); // Detaching the page has been move in onFragmentHidden due to transitions. @@ -717,10 +693,15 @@ class FragmentClass extends android.app.Fragment { public onDestroy(): void { if (trace.enabled) { - trace.write(`${this.getTag()}.onDestroy()`, trace.categories.NativeLifecycle); + trace.write(`${this}.onDestroy()`, trace.categories.NativeLifecycle); } super.onDestroy(); - this.entry[FRAGMENT] = undefined; + + this.entry.fragmentTag = undefined; + } + + public toString(): string { + return `${this.getTag()}<${this.entry ? this.entry.resolvedPage : ""}>`; } } diff --git a/ui/frame/frame.d.ts b/ui/frame/frame.d.ts index fb66b53c1..3047af545 100644 --- a/ui/frame/frame.d.ts +++ b/ui/frame/frame.d.ts @@ -236,6 +236,9 @@ declare module "ui/frame" { resolvedPage: pages.Page; //@private + navDepth: number; + fragmentTag: string; + isBack: boolean; isNavigation: boolean; //@endprivate } diff --git a/ui/frame/frame.ios.ts b/ui/frame/frame.ios.ts index fd004534c..54525988b 100644 --- a/ui/frame/frame.ios.ts +++ b/ui/frame/frame.ios.ts @@ -70,9 +70,8 @@ export class Frame extends frameCommon.Frame { } public _navigateCore(backstackEntry: definition.BackstackEntry) { - if (trace.enabled) { - trace.write(`${this}._navigateCore(page: ${backstackEntry.resolvedPage}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry)}, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation); - } + super._navigateCore(backstackEntry); + let viewController: UIViewController = backstackEntry.resolvedPage.ios; if (!viewController) { throw new Error("Required page does not have a viewController created."); @@ -131,7 +130,7 @@ export class Frame extends frameCommon.Frame { } // We should hide the current entry from the back stack. - if (!this._isEntryBackstackVisible(this._currentEntry)) { + if (!Frame._isEntryBackstackVisible(this._currentEntry)) { var newControllers = NSMutableArray.alloc().initWithArray(this._ios.controller.viewControllers); if (newControllers.count === 0) { throw new Error("Wrong controllers count."); @@ -161,10 +160,9 @@ export class Frame extends frameCommon.Frame { } public _goBackCore(backstackEntry: definition.BackstackEntry) { + super._goBackCore(backstackEntry); + navDepth = backstackEntry[NAV_DEPTH]; - if (trace.enabled) { - trace.write(`${this}._goBackCore(page: ${backstackEntry.resolvedPage}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry)}, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation); - } if (!this._shouldSkipNativePop) { var controller = backstackEntry.resolvedPage.ios; diff --git a/ui/transition/transition.android.ts b/ui/transition/transition.android.ts index 97029ddc1..b8ba8fb5c 100644 --- a/ui/transition/transition.android.ts +++ b/ui/transition/transition.android.ts @@ -1,13 +1,13 @@ -import definition = require("ui/transition"); -import platform = require("platform"); -import frameModule = require("ui/frame"); -import pageModule = require("ui/page"); +import { Transition as definitionTransition } from "ui/transition"; +import { NavigationTransition, BackstackEntry } from "ui/frame"; +import { Page} from "ui/page"; +import { getClass } from "utils/types"; +import { device } from "platform"; import * as animationModule from "ui/animation"; -import types = require("utils/types"); -import trace = require("trace"); import lazy from "utils/lazy"; +import trace = require("trace"); +var _sdkVersion = lazy(() => parseInt(device.sdkVersion)); -var _sdkVersion = lazy(() => parseInt(platform.device.sdkVersion)); var _defaultInterpolator = lazy(() => new android.view.animation.AccelerateDecelerateInterpolator()); interface CompleteOptions { @@ -15,8 +15,8 @@ interface CompleteOptions { } interface ExpandedFragment { - enterPopExitTransition: definition.Transition; - exitPopEnterTransition: definition.Transition; + enterPopExitTransition: definitionTransition; + exitPopEnterTransition: definitionTransition; completePageAdditionWhenTransitionEnds: CompleteOptions; completePageRemovalWhenTransitionEnds: CompleteOptions; isDestroyed: boolean; @@ -38,7 +38,7 @@ export function _clearBackwardTransitions(fragment: any): void { var expandedFragment = fragment; if (expandedFragment.enterPopExitTransition) { if (trace.enabled) { - trace.write(`Cleared enterPopExitTransition ${expandedFragment.enterPopExitTransition} for ${fragment.getTag()}`, trace.categories.Transition); + trace.write(`Cleared enterPopExitTransition ${expandedFragment.enterPopExitTransition} for ${fragment}`, trace.categories.Transition); } expandedFragment.enterPopExitTransition = undefined; } @@ -47,14 +47,14 @@ export function _clearBackwardTransitions(fragment: any): void { var enterTransition = (fragment).getEnterTransition(); if (enterTransition) { if (trace.enabled) { - trace.write(`Cleared Enter ${enterTransition.getClass().getSimpleName()} transition for ${fragment.getTag()}`, trace.categories.Transition); + trace.write(`Cleared Enter ${enterTransition.getClass().getSimpleName() } transition for ${fragment}`, trace.categories.Transition); } (fragment).setEnterTransition(null); } var returnTransition = (fragment).getReturnTransition(); if (returnTransition) { if (trace.enabled) { - trace.write(`Cleared Pop Exit ${returnTransition.getClass().getSimpleName()} transition for ${fragment.getTag()}`, trace.categories.Transition); + trace.write(`Cleared Pop Exit ${returnTransition.getClass().getSimpleName() } transition for ${fragment}`, trace.categories.Transition); } (fragment).setReturnTransition(null); } @@ -65,7 +65,7 @@ export function _clearForwardTransitions(fragment: any): void { var expandedFragment = fragment; if (expandedFragment.exitPopEnterTransition) { if (trace.enabled) { - trace.write(`Cleared exitPopEnterTransition ${expandedFragment.exitPopEnterTransition} for ${fragment.getTag()}`, trace.categories.Transition); + trace.write(`Cleared exitPopEnterTransition ${expandedFragment.exitPopEnterTransition} for ${fragment}`, trace.categories.Transition); } expandedFragment.exitPopEnterTransition = undefined; } @@ -74,21 +74,21 @@ export function _clearForwardTransitions(fragment: any): void { var exitTransition = (fragment).getExitTransition(); if (exitTransition) { if (trace.enabled) { - trace.write(`Cleared Exit ${exitTransition.getClass().getSimpleName()} transition for ${fragment.getTag()}`, trace.categories.Transition); + trace.write(`Cleared Exit ${exitTransition.getClass().getSimpleName() } transition for ${fragment}`, trace.categories.Transition); } (fragment).setExitTransition(null);//exit } var reenterTransition = (fragment).getReenterTransition(); if (reenterTransition) { if (trace.enabled) { - trace.write(`Cleared Pop Enter ${reenterTransition.getClass().getSimpleName()} transition for ${fragment.getTag()}`, trace.categories.Transition); + trace.write(`Cleared Pop Enter ${reenterTransition.getClass().getSimpleName() } transition for ${fragment}`, trace.categories.Transition); } (fragment).setReenterTransition(null);//popEnter } } } -export function _setAndroidFragmentTransitions(navigationTransition: frameModule.NavigationTransition, currentFragment: any, newFragment: any, fragmentTransaction: any): void { +export function _setAndroidFragmentTransitions(navigationTransition: NavigationTransition, currentFragment: any, newFragment: any, fragmentTransaction: any): void { var name; if (navigationTransition.name) { name = navigationTransition.name.toLowerCase(); @@ -196,7 +196,7 @@ export function _setAndroidFragmentTransitions(navigationTransition: frameModule return; } - var transition: definition.Transition; + var transition: definitionTransition; if (name) { if (name.indexOf("slide") === 0) { //HACK: Use an absolute import to work around a webpack issue that doesn't resolve relatively-imported "xxx.android/ios" modules @@ -221,17 +221,17 @@ export function _setAndroidFragmentTransitions(navigationTransition: frameModule } if (transition) { - var newexpandedFragment = newFragment; - newexpandedFragment.enterPopExitTransition = transition; + var newExpandedFragment = newFragment; + newExpandedFragment.enterPopExitTransition = transition; if (currentFragment) { - var currentexpandedFragment = currentFragment; - currentexpandedFragment.exitPopEnterTransition = transition; + var currentExpandedFragment = currentFragment; + currentExpandedFragment.exitPopEnterTransition = transition; } fragmentTransaction.setCustomAnimations(enterFakeResourceId, exitFakeResourceId, popEnterFakeResourceId, popExitFakeResourceId); } } -function _setUpNativeTransition(navigationTransition: frameModule.NavigationTransition, nativeTransition: any/*android.transition.Transition*/) { +function _setUpNativeTransition(navigationTransition: NavigationTransition, nativeTransition: any/*android.transition.Transition*/) { if (navigationTransition.duration) { nativeTransition.setDuration(navigationTransition.duration); } @@ -252,7 +252,7 @@ export function _onFragmentShown(fragment: any, isBack: boolean): void { var relevantTransition = isBack ? expandedFragment.exitPopEnterTransition : expandedFragment.enterPopExitTransition; if (relevantTransition) { if (trace.enabled) { - trace.write(`${fragment.getTag() } has been shown when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${relevantTransition}. Will complete page addition when transition ends.`, trace.categories.Transition); + trace.write(`${fragment } has been shown when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${relevantTransition}. Will complete page addition when transition ends.`, trace.categories.Transition); } expandedFragment.completePageAdditionWhenTransitionEnds = { isBack: isBack }; } @@ -260,7 +260,7 @@ export function _onFragmentShown(fragment: any, isBack: boolean): void { var nativeTransition = isBack ? (fragment).getReenterTransition() : (fragment).getEnterTransition(); if (nativeTransition) { if (trace.enabled) { - trace.write(`${fragment.getTag() } has been shown when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${nativeTransition.getClass().getSimpleName()} transition. Will complete page addition when transition ends.`, trace.categories.Transition); + trace.write(`${fragment } has been shown when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${nativeTransition.getClass().getSimpleName() } transition. Will complete page addition when transition ends.`, trace.categories.Transition); } expandedFragment.completePageAdditionWhenTransitionEnds = { isBack: isBack }; } @@ -277,7 +277,7 @@ export function _onFragmentHidden(fragment: any, isBack: boolean, destroyed: boo var relevantTransition = isBack ? expandedFragment.enterPopExitTransition : expandedFragment.exitPopEnterTransition; if (relevantTransition) { if (trace.enabled) { - trace.write(`${fragment.getTag()} has been hidden when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${relevantTransition}. Will complete page removal when transition ends.`, trace.categories.Transition); + trace.write(`${fragment} has been hidden when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${relevantTransition}. Will complete page removal when transition ends.`, trace.categories.Transition); } expandedFragment.completePageRemovalWhenTransitionEnds = { isBack: isBack }; } @@ -285,7 +285,7 @@ export function _onFragmentHidden(fragment: any, isBack: boolean, destroyed: boo var nativeTransition = isBack ? (fragment).getReturnTransition() : (fragment).getExitTransition(); if (nativeTransition) { if (trace.enabled) { - trace.write(`${fragment.getTag()} has been hidden when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${nativeTransition.getClass().getSimpleName()} transition. Will complete page removal when transition ends.`, trace.categories.Transition); + trace.write(`${fragment} has been hidden when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${nativeTransition.getClass().getSimpleName() } transition. Will complete page removal when transition ends.`, trace.categories.Transition); } expandedFragment.completePageRemovalWhenTransitionEnds = { isBack: isBack }; } @@ -300,11 +300,14 @@ export function _onFragmentHidden(fragment: any, isBack: boolean, destroyed: boo } function _completePageAddition(fragment: any, isBack: boolean) { + if (trace.enabled) { + trace.write(`STARTING ADDITION of ${page}...`, trace.categories.Transition); + } var expandedFragment = fragment; expandedFragment.completePageAdditionWhenTransitionEnds = undefined; var frame = fragment.frame; - var entry: frameModule.BackstackEntry = fragment.entry; - var page: pageModule.Page = entry.resolvedPage; + var entry: BackstackEntry = fragment.entry; + var page: Page = entry.resolvedPage; // The original code that was once in Frame onFragmentShown frame._currentEntry = entry; page.onNavigatedTo(isBack); @@ -316,11 +319,14 @@ function _completePageAddition(fragment: any, isBack: boolean) { } function _completePageRemoval(fragment: any, isBack: boolean) { + if (trace.enabled) { + trace.write(`STARTING REMOVAL of ${page}...`, trace.categories.Transition); + } var expandedFragment = fragment; expandedFragment.completePageRemovalWhenTransitionEnds = undefined; var frame = fragment.frame; - var entry: frameModule.BackstackEntry = fragment.entry; - var page: pageModule.Page = entry.resolvedPage; + var entry: BackstackEntry = fragment.entry; + var page: Page = entry.resolvedPage; if (page.frame) { frame._removeView(page); // This could be undefined if activity is destroyed (e.g. without actual navigation). @@ -349,12 +355,25 @@ function _completePageRemoval(fragment: any, isBack: boolean) { if (trace.enabled) { trace.write(`DETACHMENT of ${page} has already been done`, trace.categories.Transition); } + _removePageNativeViewFromAndroidParent(page); } } entry.isNavigation = undefined; } +export function _removePageNativeViewFromAndroidParent(page: Page): void { + if (page._nativeView && page._nativeView.getParent) { + var androidParent = page._nativeView.getParent(); + if (androidParent && androidParent.removeView) { + if (trace.enabled) { + trace.write(`REMOVED ${page}._nativeView from its Android parent`, trace.categories.Transition); + } + androidParent.removeView(page._nativeView); + } + } +} + function _addNativeTransitionListener(fragment: any, nativeTransition: any/*android.transition.Transition*/) { var expandedFragment = fragment; var transitionListener = new (android).transition.Transition.TransitionListener({ @@ -427,17 +446,17 @@ export function _onFragmentCreateAnimator(fragment: any, nextAnim: number): andr var transitionListener = new android.animation.Animator.AnimatorListener({ onAnimationStart: function (animator: android.animation.Animator): void { if (trace.enabled) { - trace.write(`START ${transitionType} ${transition} for ${fragment.getTag()}`, trace.categories.Transition); + trace.write(`START ${transitionType} ${transition} for ${fragment}`, trace.categories.Transition); } }, onAnimationRepeat: function (animator: android.animation.Animator): void { if (trace.enabled) { - trace.write(`REPEAT ${transitionType} ${transition} for ${fragment.getTag()}`, trace.categories.Transition); + trace.write(`REPEAT ${transitionType} ${transition} for ${fragment}`, trace.categories.Transition); } }, onAnimationEnd: function (animator: android.animation.Animator): void { if (trace.enabled) { - trace.write(`END ${transitionType} ${transition}`, trace.categories.Transition); + trace.write(`END ${transitionType} ${transition} for ${fragment}`, trace.categories.Transition); } if (expandedFragment.completePageRemovalWhenTransitionEnds) { _completePageRemoval(fragment, expandedFragment.completePageRemovalWhenTransitionEnds.isBack); @@ -448,7 +467,7 @@ export function _onFragmentCreateAnimator(fragment: any, nextAnim: number): andr }, onAnimationCancel: function (animator: android.animation.Animator): void { if (trace.enabled) { - trace.write(`CANCEL ${transitionType} ${transition} for ${fragment.getTag()}`, trace.categories.Transition); + trace.write(`CANCEL ${transitionType} ${transition} for ${fragment}`, trace.categories.Transition); } if (expandedFragment.completePageRemovalWhenTransitionEnds) { _completePageRemoval(fragment, expandedFragment.completePageRemovalWhenTransitionEnds.isBack); @@ -461,14 +480,39 @@ export function _onFragmentCreateAnimator(fragment: any, nextAnim: number): andr animator.addListener(transitionListener); } + if (transitionType && !animator) { + // Happens when the transaction has setCustomAnimations, but we have cleared the transitions because of CLEARING_HISTORY + animator = _createDummyZeroDurationAnimator(); + } + return animator; } -var transitionId = 0; -export class Transition implements definition.Transition { +let intEvaluator: android.animation.IntEvaluator; +function ensureIntEvaluator() { + if (!intEvaluator) { + intEvaluator = new android.animation.IntEvaluator(); + } +} + +function _createDummyZeroDurationAnimator(): android.animation.Animator { + if (trace.enabled) { + trace.write(`_createDummyZeroDurationAnimator()`, trace.categories.Transition); + } + ensureIntEvaluator(); + let nativeArray = (Array).create(java.lang.Object, 2); + nativeArray[0] = java.lang.Integer.valueOf(0); + nativeArray[1] = java.lang.Integer.valueOf(1); + var animator = android.animation.ValueAnimator.ofObject(intEvaluator, nativeArray); + animator.setDuration(0); + return animator; +} + +export class Transition implements definitionTransition { private _duration: number; private _interpolator: android.view.animation.Interpolator; private _id: number; + private static transitionId = 0; constructor(duration: number, curve: any) { this._duration = duration; @@ -479,7 +523,7 @@ export class Transition implements definition.Transition { else { this._interpolator = _defaultInterpolator(); } - this._id = transitionId++; + this._id = Transition.transitionId++; } public getDuration(): number { @@ -499,6 +543,6 @@ export class Transition implements definition.Transition { } public toString(): string { - return `${types.getClass(this)}@${this._id}`; + return `${getClass(this)}@${this._id}`; } } diff --git a/ui/transition/transition.d.ts b/ui/transition/transition.d.ts index 0ea3b20f7..b8a51ef3a 100644 --- a/ui/transition/transition.d.ts +++ b/ui/transition/transition.d.ts @@ -1,5 +1,6 @@ declare module "ui/transition" { - import frame = require("ui/frame"); + import { NavigationTransition } from "ui/frame"; + import { Page } from "ui/page"; export module AndroidTransitionType { export var enter: string; @@ -20,11 +21,12 @@ //@private export function _clearBackwardTransitions(fragment: any): void; export function _clearForwardTransitions(fragment: any): void; - export function _setAndroidFragmentTransitions(navigationTransition: frame.NavigationTransition, currentFragment: any, newFragment: any, fragmentTransaction: any): void; + export function _setAndroidFragmentTransitions(navigationTransition: NavigationTransition, currentFragment: any, newFragment: any, fragmentTransaction: any): void; export function _onFragmentCreateAnimator(fragment: any, nextAnim: number): any; export function _onFragmentShown(fragment: any, isBack: boolean): void; export function _onFragmentHidden(fragment: any, isBack: boolean, destroyed: boolean): void; + export function _removePageNativeViewFromAndroidParent(page: Page): void; - export function _createIOSAnimatedTransitioning(navigationTransition: frame.NavigationTransition, nativeCurve: any, operation: number, fromVC: any, toVC: any): any; + export function _createIOSAnimatedTransitioning(navigationTransition: NavigationTransition, nativeCurve: any, operation: number, fromVC: any, toVC: any): any; //@endprivate } \ No newline at end of file