Fixed: Navigating with clearHistory / transition

This commit is contained in:
Rossen Hristov
2016-05-16 18:25:43 +03:00
parent e2f1fb6f9d
commit 37f13c50d4
13 changed files with 464 additions and 289 deletions

View File

@@ -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}`);
(<any>topmostFrame())._printFrameBackStack();
if (topmostFrame().android) {
(<any>topmostFrame())._printNativeBackStack();

View File

@@ -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;
}

View File

@@ -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";

View File

@@ -4,36 +4,51 @@ 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 _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 () {
@@ -45,22 +60,26 @@ export var test_backstackVisible_WithTransition = function () {
}
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,7 +100,10 @@ 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 () {
@@ -102,34 +124,37 @@ 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.
@@ -138,49 +163,104 @@ export var test_ClearHistory = function () {
}
export var test_ClearHistory_WithTransition = function () {
_test_ClearHistory({ name: "slide" });
_test_ClearHistory({ name: "fade" });
}
export var test_ClearHistory_WithTransition_WithCachePagesOnNavigate = function () {
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 () {
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 () {
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 `${(<Page>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 +273,7 @@ function _test_NavigationEvents(transition?: NavigationTransition) {
let actualSecondPageEvents = new Array<string>();
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 +284,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,6 +310,7 @@ 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 () {
@@ -234,5 +318,5 @@ export var test_NavigationEvents = function () {
}
export var test_NavigationEvents_WithTransition = function () {
_test_NavigationEvents({ name: "slide" });
_test_NavigationEvents({ name: "fade" });
}

View File

@@ -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
;
});
}

View File

@@ -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);
}
}
/**

View File

@@ -189,9 +189,8 @@ export class View extends viewCommon.View {
}
if (trace.enabled) {
trace.write("calling _onAttached on view " + this._domId, trace.categories.VisualTreeEvents);
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");
}

View File

@@ -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 = <definition.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<definition.Frame> {
return frameStack;
}

View File

@@ -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 = <FragmentClass>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;
}
let clearHistory = backstackEntry.entry.clearHistory;
// 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;
// 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 = <FragmentClass>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);
// 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);
}
}
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);
trace.write(`\tREMOVE ${currentFragment}`, trace.categories.Navigation);
}
fragmentTransaction.remove(currentFragment);
}
}
// Add newFragment
if (trace.enabled) {
trace.write(`\tADD ${newFragmentTag}<${newFragment.entry.resolvedPage}>`, trace.categories.Navigation);
}
fragmentTransaction.add(this.containerViewId, newFragment, newFragmentTag);
if (trace.enabled) {
trace.write(`fragmentTransaction.add(${newFragmentTag});`, trace.categories.NativeLifecycle);
}
}
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) {
// addToBackStack
if (this.backStack.length > 0 && currentFragment && !clearHistory) {
// 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(`\tADD TO BACK STACK ${currentFragment}`, trace.categories.Navigation);
}
fragmentTransaction.addToBackStack(this._currentEntry.fragmentTag);
}
if (!isFirstNavigation) {
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 = <any>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) {
(<any>fragment).frame = frame;
(<any>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,14 @@ 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 : ""}>`;
}
}

3
ui/frame/frame.d.ts vendored
View File

@@ -236,6 +236,9 @@ declare module "ui/frame" {
resolvedPage: pages.Page;
//@private
navDepth: number;
fragmentTag: string;
isBack: boolean;
isNavigation: boolean;
//@endprivate
}

View File

@@ -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;

View File

@@ -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 = <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 = (<any>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);
}
(<any>fragment).setEnterTransition(null);
}
var returnTransition = (<any>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);
}
(<any>fragment).setReturnTransition(null);
}
@@ -65,7 +65,7 @@ export function _clearForwardTransitions(fragment: any): void {
var expandedFragment = <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 = (<any>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);
}
(<any>fragment).setExitTransition(null);//exit
}
var reenterTransition = (<any>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);
}
(<any>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 = <ExpandedFragment>newFragment;
newexpandedFragment.enterPopExitTransition = transition;
var newExpandedFragment = <ExpandedFragment>newFragment;
newExpandedFragment.enterPopExitTransition = transition;
if (currentFragment) {
var currentexpandedFragment = <ExpandedFragment>currentFragment;
currentexpandedFragment.exitPopEnterTransition = transition;
var currentExpandedFragment = <ExpandedFragment>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 ? (<any>fragment).getReenterTransition() : (<any>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 ? (<any>fragment).getReturnTransition() : (<any>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 = <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 = <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 = <ExpandedFragment>fragment;
var transitionListener = new (<any>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 = (<any>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}`;
}
}

View File

@@ -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
}