diff --git a/apps/perf-tests/nav-page.ts b/apps/perf-tests/nav-page.ts index 9ee3f9b7f..6ce578038 100644 --- a/apps/perf-tests/nav-page.ts +++ b/apps/perf-tests/nav-page.ts @@ -1,6 +1,6 @@ import definition = require("controls-page"); import {View} from "ui/core/view"; -import {Page} from "ui/page"; +import {Page, NavigatedData} from "ui/page"; import {topmost as topmostFrame, NavigationTransition, Frame} from "ui/frame"; import {Orientation, AnimationCurve} from "ui/enums"; import {StackLayout} from "ui/layouts/stack-layout"; @@ -39,25 +39,25 @@ export class NavPage extends Page implements definition.ControlsPage { var that = this; that.on(View.loadedEvent, (args) => { - console.log(`${args.object}.loadedEvent`); - //if (topmostFrame().android) { - // topmostFrame().android.cachePagesOnNavigate = true; - //} + console.log(`Loaded ${args.object}`); + if (topmostFrame().android) { + topmostFrame().android.cachePagesOnNavigate = true; + } }); that.on(View.unloadedEvent, (args) => { - console.log(`${args.object}.unloadedEvent`); + console.log(`Unloaded ${args.object}`); }); - that.on(Page.navigatingFromEvent, (args) => { - console.log(`${args.object}.navigatingFromEvent`); + that.on(Page.navigatingFromEvent, (args: NavigatedData) => { + console.log(`NavigatING FROM ${args.object}, isBackNavigation: ${args.isBackNavigation}`); }); - that.on(Page.navigatedFromEvent, (args) => { - console.log(`${args.object}.navigatedFromEvent`); + that.on(Page.navigatedFromEvent, (args: NavigatedData) => { + console.log(`NaviagatED FROM ${args.object}, isBackNavigation: ${args.isBackNavigation}`); }); - that.on(Page.navigatingToEvent, (args) => { - console.log(`${args.object}.navigatingToEvent`); + that.on(Page.navigatingToEvent, (args: NavigatedData) => { + console.log(`NavigatING TO ${args.object}, isBackNavigation: ${args.isBackNavigation}`); }); - that.on(Page.navigatedToEvent, (args) => { - console.log(`${args.object}.navigatedToEvent`); + that.on(Page.navigatedToEvent, (args: NavigatedData) => { + console.log(`NavigatED TO ${args.object}, isBackNavigation: ${args.isBackNavigation}`); (topmostFrame())._printFrameBackStack(); if (topmostFrame().android) { (topmostFrame())._printNativeBackStack(); diff --git a/apps/tests/navigation/navigation-tests.ts b/apps/tests/navigation/navigation-tests.ts index ee1a7bff1..2990a5fb7 100644 --- a/apps/tests/navigation/navigation-tests.ts +++ b/apps/tests/navigation/navigation-tests.ts @@ -1,5 +1,5 @@ import * as TKUnit from "../TKUnit"; -import {Page} from "ui/page"; +import {Page, NavigatedData} from "ui/page"; import {topmost as topmostFrame, NavigationTransition} from "ui/frame"; import {Color} from "color"; @@ -174,3 +174,65 @@ export var test_ClearHistoryWithTransitionDoesNotBreakNavigation = function () { // Clean up topmost.transition = undefined; } + +function _test_NavigationEvents(transition?: NavigationTransition) { + 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"; + let actualMainPageEvents = new Array(); + mainTestPage.on(Page.navigatingFromEvent, (args: NavigatedData) => { actualMainPageEvents.push(argsToString(args)); }); + mainTestPage.on(Page.navigatedFromEvent, (args: NavigatedData) => { actualMainPageEvents.push(argsToString(args)); }); + mainTestPage.on(Page.navigatingToEvent, (args: NavigatedData) => { actualMainPageEvents.push(argsToString(args)); }); + mainTestPage.on(Page.navigatedToEvent, (args: NavigatedData) => { actualMainPageEvents.push(argsToString(args)); }); + + let actualSecondPageEvents = new Array(); + let secondPageFactory = function (): Page { + var secondPage = new Page(); + secondPage.id = "second-page" + secondPage.on(Page.navigatingFromEvent, (args: NavigatedData) => { actualSecondPageEvents.push(argsToString(args)); }); + secondPage.on(Page.navigatedFromEvent, (args: NavigatedData) => { actualSecondPageEvents.push(argsToString(args)); }); + secondPage.on(Page.navigatingToEvent, (args: NavigatedData) => { actualSecondPageEvents.push(argsToString(args)); }); + secondPage.on(Page.navigatedToEvent, (args: NavigatedData) => { actualSecondPageEvents.push(argsToString(args)); }); + secondPage.style.backgroundColor = new Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)); + return secondPage; + }; + + // Go to other page + topmost.navigate({ create: secondPageFactory, transition: transition }); + TKUnit.waitUntilReady(() => { return topmost.currentPage !== mainTestPage; }); + + // Go back to main + topmost.goBack(); + TKUnit.waitUntilReady(() => { return topmost.currentPage === mainTestPage; }); + mainTestPage.id = originalMainPageId; + + let expectedMainPageEvents = [ + "main-page navigatingFrom forward", + "main-page navigatedFrom forward", + "main-page navigatingTo back", + "main-page navigatedTo back" + ]; + TKUnit.arrayAssert(actualMainPageEvents, expectedMainPageEvents, "Actual main-page events are different from expected."); + + let expectedSecondPageEvents = [ + "second-page navigatingTo forward", + "second-page navigatedTo forward", + "second-page navigatingFrom back", + "second-page navigatedFrom back" + ]; + TKUnit.arrayAssert(actualSecondPageEvents, expectedSecondPageEvents, "Actual second-page events are different from expected."); + +} + +export var test_NavigationEvents = function () { + _test_NavigationEvents(); +} + +export var test_NavigationEvents_WithTransition = function () { + _test_NavigationEvents({ name: "slide" }); +} \ No newline at end of file diff --git a/ui/transition/transition.android.ts b/ui/transition/transition.android.ts index 0a69e9b46..2c090dfb9 100644 --- a/ui/transition/transition.android.ts +++ b/ui/transition/transition.android.ts @@ -9,11 +9,18 @@ import trace = require("trace"); var _sdkVersion = parseInt(platform.device.sdkVersion); var _defaultInterpolator = new android.view.animation.AccelerateDecelerateInterpolator(); -var ENTER_POPEXIT_TRANSITION = "ENTER_POPEXIT_TRANSITION"; -var EXIT_POPENTER_TRANSITION = "EXIT_POPENTER_TRANSITION"; -var COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS = "COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS"; -var COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS = "COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS"; -var DESTROYED = "DESTROYED"; +interface CompleteOptions { + isBack: boolean; +} + +interface ExpandedFragment { + enterPopExitTransition: definition.Transition; + exitPopEnterTransition: definition.Transition; + completePageAdditionWhenTransitionEnds: CompleteOptions; + completePageRemovalWhenTransitionEnds: CompleteOptions; + isDestroyed: boolean; +} + var enterFakeResourceId = -10; var exitFakeResourceId = -20; var popEnterFakeResourceId = -30; @@ -27,11 +34,12 @@ export module AndroidTransitionType { } export function _clearBackwardTransitions(fragment: any): void { - if (fragment[ENTER_POPEXIT_TRANSITION]) { - trace.write(`Cleared ENTER_POPEXIT_TRANSITION ${fragment[ENTER_POPEXIT_TRANSITION]} for ${fragment.getTag()}`, trace.categories.Transition); - fragment[ENTER_POPEXIT_TRANSITION] = undefined; + var expandedFragment = fragment; + if (expandedFragment.enterPopExitTransition) { + trace.write(`Cleared enterPopExitTransition ${expandedFragment.enterPopExitTransition} for ${fragment.getTag()}`, trace.categories.Transition); + expandedFragment.enterPopExitTransition = undefined; } - + if (_sdkVersion >= 21) { var enterTransition = (fragment).getEnterTransition(); if (enterTransition) { @@ -47,9 +55,10 @@ export function _clearBackwardTransitions(fragment: any): void { } export function _clearForwardTransitions(fragment: any): void { - if (fragment[EXIT_POPENTER_TRANSITION]) { - trace.write(`Cleared EXIT_POPENTER_TRANSITION ${fragment[EXIT_POPENTER_TRANSITION]} for ${fragment.getTag()}`, trace.categories.Transition); - fragment[EXIT_POPENTER_TRANSITION] = undefined; + var expandedFragment = fragment; + if (expandedFragment.exitPopEnterTransition) { + trace.write(`Cleared exitPopEnterTransition ${expandedFragment.exitPopEnterTransition} for ${fragment.getTag()}`, trace.categories.Transition); + expandedFragment.exitPopEnterTransition = undefined; } if (_sdkVersion >= 21) { @@ -199,9 +208,11 @@ export function _setAndroidFragmentTransitions(navigationTransition: frameModule } if (transition) { - newFragment[ENTER_POPEXIT_TRANSITION] = transition; + var newexpandedFragment = newFragment; + newexpandedFragment.enterPopExitTransition = transition; if (currentFragment) { - currentFragment[EXIT_POPENTER_TRANSITION] = transition; + var currentexpandedFragment = currentFragment; + currentexpandedFragment.exitPopEnterTransition = transition; } fragmentTransaction.setCustomAnimations(enterFakeResourceId, exitFakeResourceId, popEnterFakeResourceId, popExitFakeResourceId); } @@ -222,81 +233,81 @@ function _setUpNativeTransition(navigationTransition: frameModule.NavigationTran } } -export function _onFragmentShown(fragment: android.app.Fragment, isBack: boolean): void { +export function _onFragmentShown(fragment: any, isBack: boolean): void { + var expandedFragment = fragment; var transitionType = isBack ? "Pop Enter" : "Enter"; - var relevantTransition = isBack ? EXIT_POPENTER_TRANSITION : ENTER_POPEXIT_TRANSITION; - if (fragment[relevantTransition]) { - trace.write(`${fragment.getTag()} has been shown when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${fragment[relevantTransition]}. Will complete page addition when transition ends.`, trace.categories.Transition); - fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS] = { isBack: isBack }; + var relevantTransition = isBack ? expandedFragment.exitPopEnterTransition : expandedFragment.enterPopExitTransition; + if (relevantTransition) { + 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); + expandedFragment.completePageAdditionWhenTransitionEnds = { isBack: isBack }; } else if (_sdkVersion >= 21) { var nativeTransition = isBack ? (fragment).getReenterTransition() : (fragment).getEnterTransition(); if (nativeTransition) { 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); - fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS] = { isBack: isBack }; + expandedFragment.completePageAdditionWhenTransitionEnds = { isBack: isBack }; } } - if (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS] === undefined) { - _completePageAddition(fragment, isBack, true); + if (!expandedFragment.completePageAdditionWhenTransitionEnds) { + _completePageAddition(fragment, isBack); } } -export function _onFragmentHidden(fragment: android.app.Fragment, isBack: boolean, destroyed: boolean) { +export function _onFragmentHidden(fragment: any, isBack: boolean, destroyed: boolean) { + var expandedFragment = fragment; var transitionType = isBack ? "Pop Exit" : "Exit"; - var relevantTransition = isBack ? ENTER_POPEXIT_TRANSITION : EXIT_POPENTER_TRANSITION; - if (fragment[relevantTransition]) { - trace.write(`${fragment.getTag()} has been hidden when going ${isBack ? "back" : "forward"}, but there is ${transitionType} ${fragment[relevantTransition]}. Will complete page removal when transition ends.`, trace.categories.Transition); - fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS] = true; + var relevantTransition = isBack ? expandedFragment.enterPopExitTransition : expandedFragment.exitPopEnterTransition; + if (relevantTransition) { + 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); + expandedFragment.completePageRemovalWhenTransitionEnds = { isBack: isBack }; } else if (_sdkVersion >= 21) { var nativeTransition = isBack ? (fragment).getReturnTransition() : (fragment).getExitTransition(); if (nativeTransition) { 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); - fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS] = true; + expandedFragment.completePageRemovalWhenTransitionEnds = { isBack: isBack }; } } - fragment[DESTROYED] = destroyed; + expandedFragment.isDestroyed = destroyed; - if (fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS] === undefined) { + if (expandedFragment.completePageRemovalWhenTransitionEnds === undefined) { // This might be a second call if the fragment is hidden and then destroyed. - _completePageRemoval(fragment, true, isBack); + _completePageRemoval(fragment, isBack); } } -function _completePageAddition(fragment: android.app.Fragment, isBack: boolean, force?: boolean) { - if (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS] || force) { - fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS] = undefined; - var frame = (fragment).frame; - var entry: frameModule.BackstackEntry = (fragment).entry; - var page: pageModule.Page = entry.resolvedPage; - // The original code that was once in Frame onFragmentShown - frame._currentEntry = entry; - page.onNavigatedTo(isBack); - frame._processNavigationQueue(page); - trace.write(`ADDITION of ${page} completed`, trace.categories.Transition); - } +function _completePageAddition(fragment: any, isBack: boolean) { + var expandedFragment = fragment; + expandedFragment.completePageAdditionWhenTransitionEnds = undefined; + var frame = fragment.frame; + var entry: frameModule.BackstackEntry = fragment.entry; + var page: pageModule.Page = entry.resolvedPage; + // The original code that was once in Frame onFragmentShown + frame._currentEntry = entry; + page.onNavigatedTo(isBack); + frame._processNavigationQueue(page); + trace.write(`ADDITION of ${page} completed`, trace.categories.Transition); } -function _completePageRemoval(fragment: android.app.Fragment, force: boolean = false, isBack: boolean = false) { - if (fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS] || force) { - fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS] = undefined; - var frame = (fragment).frame; - var entry: frameModule.BackstackEntry = (fragment).entry; - var page: pageModule.Page = entry.resolvedPage; - if (page.frame) { - frame._removeView(page); - page.onNavigatedFrom(isBack); - trace.write(`REMOVAL of ${page} completed`, trace.categories.Transition); - } - else { - trace.write(`REMOVAL of ${page} has already been done`, trace.categories.Transition); - } +function _completePageRemoval(fragment: any, isBack: boolean) { + var expandedFragment = fragment; + expandedFragment.completePageRemovalWhenTransitionEnds = undefined; + var frame = fragment.frame; + var entry: frameModule.BackstackEntry = fragment.entry; + var page: pageModule.Page = entry.resolvedPage; + if (page.frame) { + frame._removeView(page); + page.onNavigatedFrom(isBack); + trace.write(`REMOVAL of ${page} completed`, trace.categories.Transition); + } + else { + trace.write(`REMOVAL of ${page} has already been done`, trace.categories.Transition); } - if (fragment[DESTROYED]) { - fragment[DESTROYED] = undefined; + if (expandedFragment.isDestroyed) { + expandedFragment.isDestroyed = undefined; if (page._context) { page._onDetached(true); trace.write(`DETACHMENT of ${page} completed`, trace.categories.Transition); @@ -307,24 +318,25 @@ function _completePageRemoval(fragment: android.app.Fragment, force: boolean = f } } -function _addNativeTransitionListener(fragment: android.app.Fragment, nativeTransition: any/*android.transition.Transition*/) { +function _addNativeTransitionListener(fragment: any, nativeTransition: any/*android.transition.Transition*/) { + var expandedFragment = fragment; var transitionListener = new (android).transition.Transition.TransitionListener({ onTransitionCancel: function (transition: any): void { trace.write(`CANCEL ${nativeTransition} transition for ${fragment}`, trace.categories.Transition); - if (fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS]) { - _completePageRemoval(fragment); + if (expandedFragment.completePageRemovalWhenTransitionEnds) { + _completePageRemoval(fragment, expandedFragment.completePageRemovalWhenTransitionEnds.isBack); } - if (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS]) { - _completePageAddition(fragment, fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS].isBack); + if (expandedFragment.completePageAdditionWhenTransitionEnds) { + _completePageAddition(fragment, expandedFragment.completePageAdditionWhenTransitionEnds.isBack); } }, onTransitionEnd: function (transition: any): void { trace.write(`END ${nativeTransition} transition for ${fragment}`, trace.categories.Transition); - if (fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS]) { - _completePageRemoval(fragment); + if (expandedFragment.completePageRemovalWhenTransitionEnds) { + _completePageRemoval(fragment, expandedFragment.completePageRemovalWhenTransitionEnds.isBack); } - if (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS]) { - _completePageAddition(fragment, fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS].isBack); + if (expandedFragment.completePageAdditionWhenTransitionEnds) { + _completePageAddition(fragment, expandedFragment.completePageAdditionWhenTransitionEnds.isBack); } }, onTransitionPause: function (transition: any): void { @@ -340,7 +352,8 @@ function _addNativeTransitionListener(fragment: android.app.Fragment, nativeTran nativeTransition.addListener(transitionListener); } -export function _onFragmentCreateAnimator(fragment: android.app.Fragment, nextAnim: number): android.animation.Animator { +export function _onFragmentCreateAnimator(fragment: any, nextAnim: number): android.animation.Animator { + var expandedFragment = fragment; var transitionType; switch (nextAnim) { case enterFakeResourceId: transitionType = AndroidTransitionType.enter; break; @@ -353,11 +366,11 @@ export function _onFragmentCreateAnimator(fragment: android.app.Fragment, nextAn switch (transitionType) { case AndroidTransitionType.enter: case AndroidTransitionType.popExit: - transition = fragment[ENTER_POPEXIT_TRANSITION]; + transition = expandedFragment.enterPopExitTransition; break; case AndroidTransitionType.exit: case AndroidTransitionType.popEnter: - transition = fragment[EXIT_POPENTER_TRANSITION]; + transition = expandedFragment.exitPopEnterTransition; break; } @@ -373,20 +386,20 @@ export function _onFragmentCreateAnimator(fragment: android.app.Fragment, nextAn }, onAnimationEnd: function (animator: android.animation.Animator): void { trace.write(`END ${transitionType} ${transition}`, trace.categories.Transition); - if (fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS]) { - _completePageRemoval(fragment); + if (expandedFragment.completePageRemovalWhenTransitionEnds) { + _completePageRemoval(fragment, expandedFragment.completePageRemovalWhenTransitionEnds.isBack); } - if (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS]) { - _completePageAddition(fragment, fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS].isBack); + if (expandedFragment.completePageAdditionWhenTransitionEnds) { + _completePageAddition(fragment, expandedFragment.completePageAdditionWhenTransitionEnds.isBack); } }, onAnimationCancel: function (animator: android.animation.Animator): void { trace.write(`CANCEL ${transitionType} ${transition} for ${fragment.getTag()}`, trace.categories.Transition); - if (fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS]) { - _completePageRemoval(fragment); + if (expandedFragment.completePageRemovalWhenTransitionEnds) { + _completePageRemoval(fragment, expandedFragment.completePageRemovalWhenTransitionEnds.isBack); } - if (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS]) { - _completePageAddition(fragment, fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS].isBack); + if (expandedFragment.completePageAdditionWhenTransitionEnds) { + _completePageAddition(fragment, expandedFragment.completePageAdditionWhenTransitionEnds.isBack); } } });