diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj
index 3c8aa80a5..54b01ab88 100644
--- a/CrossPlatformModules.csproj
+++ b/CrossPlatformModules.csproj
@@ -83,7 +83,12 @@
data-binding.xml
+
+
+
+
+
modal-page.xml
@@ -130,6 +135,7 @@
+
@@ -175,7 +181,7 @@
-
+
page21.xml
@@ -737,6 +743,14 @@
+
+
+
+
+
+
+
+
@@ -2093,6 +2107,9 @@
+
+ PreserveNewest
+
diff --git a/apps/perf-tests/NavigationTest/app.ts b/apps/perf-tests/NavigationTest/app.ts
index 91949d903..00c39550d 100644
--- a/apps/perf-tests/NavigationTest/app.ts
+++ b/apps/perf-tests/NavigationTest/app.ts
@@ -4,13 +4,23 @@ import navPageModule = require("../nav-page");
import trace = require("trace");
trace.enable();
trace.setCategories(trace.categories.concat(
- trace.categories.NativeLifecycle
- , trace.categories.Navigation
+ trace.categories.NativeLifecycle,
+ trace.categories.Navigation,
+ //trace.categories.Animation,
+ trace.categories.Transition
));
application.mainEntry = {
create: function () {
- return new navPageModule.NavPage(0);
+ return new navPageModule.NavPage({
+ index: 0,
+ backStackVisible: true,
+ clearHistory: false,
+ animated: true,
+ transition: 0,
+ curve: 0,
+ duration: 0,
+ });
}
//backstackVisible: false,
//clearHistory: true
diff --git a/apps/perf-tests/NavigationTest/list-picker-page.ts b/apps/perf-tests/NavigationTest/list-picker-page.ts
new file mode 100644
index 000000000..0c93c1bf6
--- /dev/null
+++ b/apps/perf-tests/NavigationTest/list-picker-page.ts
@@ -0,0 +1,21 @@
+import {Page, ShownModallyData, ListPicker} from "ui";
+
+var closeCallback: Function;
+var page: Page;
+var listPicker: ListPicker;
+
+export function onLoaded(args) {
+ page = args.object;
+ listPicker = page.getViewById("listPicker");
+}
+
+export function onShownModally(args) {
+ closeCallback = args.closeCallback;
+
+ listPicker.items = args.context.items;
+ listPicker.selectedIndex = args.context.selectedIndex || 0;
+}
+
+export function onButtonTap() {
+ closeCallback(listPicker.selectedIndex);
+}
\ No newline at end of file
diff --git a/apps/perf-tests/NavigationTest/list-picker-page.xml b/apps/perf-tests/NavigationTest/list-picker-page.xml
new file mode 100644
index 000000000..27cdc8b69
--- /dev/null
+++ b/apps/perf-tests/NavigationTest/list-picker-page.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/perf-tests/custom-transition.android.ts b/apps/perf-tests/custom-transition.android.ts
new file mode 100644
index 000000000..a9be59345
--- /dev/null
+++ b/apps/perf-tests/custom-transition.android.ts
@@ -0,0 +1,35 @@
+import transition = require("ui/transition");
+import platform = require("platform");
+
+var floatType = java.lang.Float.class.getField("TYPE").get(null);
+
+export class CustomTransition extends transition.Transition {
+ public createAndroidAnimator(transitionType: string): android.animation.Animator {
+ var scaleValues = java.lang.reflect.Array.newInstance(floatType, 2);
+ switch (transitionType) {
+ case transition.AndroidTransitionType.enter:
+ case transition.AndroidTransitionType.popEnter:
+ scaleValues[0] = 0;
+ scaleValues[1] = 1;
+ break;
+ case transition.AndroidTransitionType.exit:
+ case transition.AndroidTransitionType.popExit:
+ scaleValues[0] = 1;
+ scaleValues[1] = 0;
+ break;
+ }
+ var objectAnimators = java.lang.reflect.Array.newInstance(android.animation.Animator.class, 2);
+ objectAnimators[0] = android.animation.ObjectAnimator.ofFloat(null, "scaleX", scaleValues);
+ objectAnimators[1] = android.animation.ObjectAnimator.ofFloat(null, "scaleY", scaleValues);
+ var animatorSet = new android.animation.AnimatorSet();
+ animatorSet.playTogether(objectAnimators);
+
+ var duration = this.getDuration();
+ if (duration !== undefined) {
+ animatorSet.setDuration(duration);
+ }
+ animatorSet.setInterpolator(this.getCurve());
+
+ return animatorSet;
+ }
+}
\ No newline at end of file
diff --git a/apps/perf-tests/custom-transition.ios.ts b/apps/perf-tests/custom-transition.ios.ts
new file mode 100644
index 000000000..4dfff7e9b
--- /dev/null
+++ b/apps/perf-tests/custom-transition.ios.ts
@@ -0,0 +1,26 @@
+import transition = require("ui/transition");
+import platform = require("platform");
+
+export class CustomTransition extends transition.Transition {
+ public animateIOSTransition(containerView: UIView, fromView: UIView, toView: UIView, operation: UINavigationControllerOperation, completion: (finished: boolean) => void): void {
+ toView.transform = CGAffineTransformMakeScale(0, 0);
+ fromView.transform = CGAffineTransformIdentity;
+
+ switch (operation) {
+ case UINavigationControllerOperation.UINavigationControllerOperationPush:
+ containerView.insertSubviewAboveSubview(toView, fromView);
+ break;
+ case UINavigationControllerOperation.UINavigationControllerOperationPop:
+ containerView.insertSubviewBelowSubview(toView, fromView);
+ break;
+ }
+
+ var duration = this.getDuration();
+ var curve = this.getCurve();
+ UIView.animateWithDurationAnimationsCompletion(duration, () => {
+ UIView.setAnimationCurve(curve);
+ toView.transform = CGAffineTransformIdentity;
+ fromView.transform = CGAffineTransformMakeScale(0, 0);
+ }, completion);
+ }
+}
\ No newline at end of file
diff --git a/apps/perf-tests/nav-page.ts b/apps/perf-tests/nav-page.ts
index 118196462..2e4affa51 100644
--- a/apps/perf-tests/nav-page.ts
+++ b/apps/perf-tests/nav-page.ts
@@ -1,82 +1,194 @@
import definition = require("controls-page");
-import frameModule = require("ui/frame");
-import pagesModule = require("ui/page");
-import stackLayoutModule = require("ui/layouts/stack-layout");
-import labelModule = require("ui/label");
-import buttonModule = require("ui/button");
-import textFieldModule = require("ui/text-field");
-import enums = require("ui/enums");
-import switchModule = require("ui/switch");
+import {View, Page, topmost as topmostFrame, NavigationTransition, Orientation, AnimationCurve, StackLayout, Button, Label, TextField, Switch, ListPicker, Slider} from "ui";
+import {Color} from "color";
+import platform = require("platform");
-export class NavPage extends pagesModule.Page implements definition.ControlsPage {
- constructor(id: number) {
+var availableTransitions = ["default", "custom", "flip", "flipRight", "flipLeft", "slide", "slideLeft", "slideRight", "slideTop", "slideBottom", "fade"];
+if (platform.device.os === platform.platformNames.ios) {
+ availableTransitions = availableTransitions.concat(["curl", "curlUp", "curlDown"]);
+}
+else {
+ availableTransitions = availableTransitions.concat(["explode"]);
+}
+
+var availableCurves = [AnimationCurve.easeInOut, AnimationCurve.easeIn, AnimationCurve.easeOut, AnimationCurve.linear];
+
+export interface Context {
+ index: number;
+ backStackVisible: boolean;
+ clearHistory: boolean;
+ animated: boolean;
+ transition: number;
+ curve: number;
+ duration: number;
+}
+
+export class NavPage extends Page implements definition.ControlsPage {
+ constructor(context: Context) {
super();
- this.id = "NavPage " + id;
+ var that = this;
+ that.on(View.loadedEvent, (args) => {
+ console.log(`${args.object}.loadedEvent`);
+ if (topmostFrame().android) {
+ topmostFrame().android.cachePagesOnNavigate = true;
+ }
+ });
+ that.on(View.unloadedEvent, (args) => {
+ console.log(`${args.object}.unloadedEvent`);
+ });
+ that.on(Page.navigatingFromEvent, (args) => {
+ console.log(`${args.object}.navigatingFromEvent`);
+ });
+ that.on(Page.navigatedFromEvent, (args) => {
+ console.log(`${args.object}.navigatedFromEvent`);
+ });
+ that.on(Page.navigatingToEvent, (args) => {
+ console.log(`${args.object}.navigatingToEvent`);
+ });
+ that.on(Page.navigatedToEvent, (args) => {
+ console.log(`${args.object}.navigatedToEvent`);
+ });
- var stackLayout = new stackLayoutModule.StackLayout();
- stackLayout.orientation = enums.Orientation.vertical;
+ this.id = "" + context.index;
- var goBackButton = new buttonModule.Button();
- goBackButton.text = "<-";
- goBackButton.on(buttonModule.Button.tapEvent, function () {
- frameModule.topmost().goBack();
+ var bg = new Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255));
+ this.style.backgroundColor = bg;
+
+ var stackLayout = new StackLayout();
+ stackLayout.orientation = Orientation.vertical;
+
+ var goBackButton = new Button();
+ goBackButton.text = "<=";
+ goBackButton.style.fontSize = 18;
+ goBackButton.on(Button.tapEvent, () => {
+ topmostFrame().goBack();
});
stackLayout.addChild(goBackButton);
- this.on(pagesModule.Page.navigatedToEvent, function () {
+ this.on(Page.navigatedToEvent, function () {
//console.log("Navigated to NavPage " + id + "; backStack.length: " + frameModule.topmost().backStack.length);
- goBackButton.isEnabled = frameModule.topmost().canGoBack();
+ goBackButton.isEnabled = topmostFrame().canGoBack();
});
- var stateLabel = new labelModule.Label();
- stateLabel.text = "NavPage " + id;
+ var stateLabel = new Label();
+ stateLabel.text = `${this.id} (${(bg)._hex})`;
stackLayout.addChild(stateLabel);
- var textField = new textFieldModule.TextField();
+ var textField = new TextField();
textField.text = "";
stackLayout.addChild(textField);
- var changeStateButton = new buttonModule.Button();
+ var changeStateButton = new Button();
changeStateButton.text = "Click me!"
var clickCount = 0;
- changeStateButton.on(buttonModule.Button.tapEvent, () => {
- //stateLabel.text = "<<>>";
- //textField.text = "<<>>";
+ changeStateButton.on(Button.tapEvent, () => {
changeStateButton.text = (clickCount++).toString();
});
stackLayout.addChild(changeStateButton);
- var optionsLayout = new stackLayoutModule.StackLayout();
+ var optionsLayout = new StackLayout();
- var addToBackStackLabel = new labelModule.Label();
+ var addToBackStackLabel = new Label();
addToBackStackLabel.text = "backStackVisible";
optionsLayout.addChild(addToBackStackLabel);
- var addToBackStackSwitch = new switchModule.Switch();
- addToBackStackSwitch.checked = true;
+ var addToBackStackSwitch = new Switch();
+ addToBackStackSwitch.checked = context.backStackVisible;
optionsLayout.addChild(addToBackStackSwitch);
- var clearHistoryLabel = new labelModule.Label();
+ var clearHistoryLabel = new Label();
clearHistoryLabel.text = "clearHistory";
optionsLayout.addChild(clearHistoryLabel);
- var clearHistorySwitch = new switchModule.Switch();
- clearHistorySwitch.checked = false;
+ var clearHistorySwitch = new Switch();
+ clearHistorySwitch.checked = context.clearHistory;
optionsLayout.addChild(clearHistorySwitch);
+ var animatedLabel = new Label();
+ animatedLabel.text = "animated";
+ optionsLayout.addChild(animatedLabel);
+
+ var animatedSwitch = new Switch();
+ animatedSwitch.checked = context.animated;
+ optionsLayout.addChild(animatedSwitch);
+
+ var transitionButton = new Button();
+ transitionButton.text = availableTransitions[context.transition];
+ transitionButton.on("tap", () => {
+ that.showModal("perf-tests/NavigationTest/list-picker-page", { items: availableTransitions, selectedIndex: context.transition }, (selectedIndex: number) => {
+ context.transition = selectedIndex;
+ transitionButton.text = availableTransitions[context.transition];
+ }, true);
+ });
+ optionsLayout.addChild(transitionButton);
+
+ var curveButton = new Button();
+ curveButton.text = availableCurves[context.curve];
+ curveButton.on(Button.tapEvent, () => {
+ that.showModal("perf-tests/NavigationTest/list-picker-page", { items: availableCurves, selectedIndex: context.curve }, (selectedIndex: number) => {
+ context.curve = selectedIndex;
+ curveButton.text = availableCurves[context.curve]
+ }, true);
+ });
+ optionsLayout.addChild(curveButton);
+
+ var durationLabel = new Label();
+ durationLabel.text = "Duration";
+ optionsLayout.addChild(durationLabel);
+
+ var durationSlider = new Slider();
+ durationSlider.minValue = 0;
+ durationSlider.maxValue = 10000;
+ durationSlider.value = context.duration;
+ optionsLayout.addChild(durationSlider);
+
stackLayout.addChild(optionsLayout);
- var forwardButton = new buttonModule.Button();
- forwardButton.text = "->";
- forwardButton.on(buttonModule.Button.tapEvent, function () {
+ var forwardButton = new Button();
+ forwardButton.text = "=>";
+ forwardButton.style.fontSize = 18;
+ forwardButton.on(Button.tapEvent, () => {
var pageFactory = function () {
- return new NavPage(id + 1);
+ return new NavPage({
+ index: context.index + 1,
+ backStackVisible: addToBackStackSwitch.checked,
+ clearHistory: clearHistorySwitch.checked,
+ animated: animatedSwitch.checked,
+ transition: context.transition,
+ curve: context.curve,
+ duration: durationSlider.value,
+ });
};
- frameModule.topmost().navigate({
+
+ var navigationTransition: NavigationTransition;
+ if (context.transition) {// Different from default
+ var transitionName = availableTransitions[context.transition];
+ var duration = durationSlider.value !== 0 ? durationSlider.value : undefined;
+ var curve = context.curve ? availableCurves[context.curve] : undefined;
+
+ if (transitionName === "custom") {
+ var customTransitionModule = require("./custom-transition");
+ var customTransition = new customTransitionModule.CustomTransition(duration, curve);
+ navigationTransition = {
+ transition: customTransition
+ };
+ }
+ else {
+ navigationTransition = {
+ transition: transitionName,
+ duration: duration,
+ curve: curve
+ };
+ }
+ }
+
+ topmostFrame().navigate({
create: pageFactory,
backstackVisible: addToBackStackSwitch.checked,
- clearHistory: clearHistorySwitch.checked
+ clearHistory: clearHistorySwitch.checked,
+ animated: animatedSwitch.checked,
+ navigationTransition: navigationTransition,
});
});
stackLayout.addChild(forwardButton);
diff --git a/apps/tab-view-demo/mainPage.ts b/apps/tab-view-demo/mainPage.ts
index ea205f6ba..f811abf5f 100644
--- a/apps/tab-view-demo/mainPage.ts
+++ b/apps/tab-view-demo/mainPage.ts
@@ -46,7 +46,15 @@ export function createPage() {
stackLayout.addChild(navigateToAnotherPageButton);
navigateToAnotherPageButton.on(buttonModule.Button.tapEvent, function () {
var pageFactory = function () {
- return new navPageModule.NavPage(0);
+ return new navPageModule.NavPage({
+ index: 0,
+ backStackVisible: true,
+ clearHistory: false,
+ animated: true,
+ transition: 0,
+ curve: 0,
+ duration: 0,
+ });
};
frameModule.topmost().navigate(pageFactory);
});
diff --git a/apps/tests/app/mainPage.ts b/apps/tests/app/mainPage.ts
index d204b2b47..f0d76ab97 100644
--- a/apps/tests/app/mainPage.ts
+++ b/apps/tests/app/mainPage.ts
@@ -7,6 +7,7 @@ trace.addCategories(trace.categories.Test + "," + trace.categories.Error);
let started = false;
let page = new Page();
+page.id = "mainPage";
page.on(Page.navigatedToEvent, function () {
if (!started) {
diff --git a/apps/tests/navigation-tests.ts b/apps/tests/navigation-tests.ts
deleted file mode 100644
index cc2a28594..000000000
--- a/apps/tests/navigation-tests.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import TKUnit = require("./TKUnit");
-import pageModule = require("ui/page");
-import frame = require("ui/frame");
-import { Page } from "ui/page";
-
-export var test_backstackVisible = function() {
- var pageFactory = function(): pageModule.Page {
- return new pageModule.Page();
- };
-
- var mainTestPage = frame.topmost().currentPage;
- frame.topmost().navigate({ create: pageFactory });
- TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== mainTestPage; });
-
- // page1 should not be added to the backstack
- var page0 = frame.topmost().currentPage;
- frame.topmost().navigate({ create: pageFactory, backstackVisible: false });
- TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== page0; });
-
- var page1 = frame.topmost().currentPage;
- frame.topmost().navigate({ create: pageFactory });
- TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== page1; });
-
- var page2 = frame.topmost().currentPage;
- frame.topmost().goBack();
- TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== page2; });
-
- // From page2 we have to go directly to page0, skipping page1.
- TKUnit.assert(frame.topmost().currentPage === page0, "Page 1 should be skipped when going back.");
-
- frame.topmost().goBack();
- TKUnit.waitUntilReady(() => { return frame.topmost().currentPage === mainTestPage; });
-}
-
-export var test_backToEntry = function() {
- let page = (tag) => () => {
- var p = new Page();
- p["tag"] = tag;
- return p;
- }
- let topmost = frame.topmost();
- let wait = tag => TKUnit.waitUntilReady(() => topmost.currentPage["tag"] === tag, 1);
- let navigate = tag => {
- topmost.navigate({ create: page(tag) });
- wait(tag)
- }
- let back = pages => {
- topmost.goBack(topmost.backStack[topmost.backStack.length - pages]);
- }
- let currentPageMustBe = tag => {
- wait(tag); // TODO: Add a timeout...
- TKUnit.assert(topmost.currentPage["tag"] === tag, "Expected current page to be " + tag + " it was " + topmost.currentPage["tag"] + " instead.");
- }
-
- navigate("page1");
- navigate("page2");
- navigate("page3");
- navigate("page4");
-
- currentPageMustBe("page4");
- back(2);
- currentPageMustBe("page2");
- back(1);
- currentPageMustBe("page1");
- navigate("page1.1");
- navigate("page1.2");
- currentPageMustBe("page1.2");
- back(1);
- currentPageMustBe("page1.1");
- back(1);
- currentPageMustBe("page1");
- back(1);
-}
-
-// Clearing the history messes up the tests app.
-export var test_ClearHistory = function () {
- var pageFactory = function(): pageModule.Page {
- return new pageModule.Page();
- };
-
- var mainTestPage = frame.topmost().currentPage;
- var mainPageFactory = function(): pageModule.Page {
- return mainTestPage;
- };
-
- var currentPage: pageModule.Page;
-
- currentPage = frame.topmost().currentPage;
- frame.topmost().navigate({ create: pageFactory });
- TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== currentPage; });
-
- currentPage = frame.topmost().currentPage;
- frame.topmost().navigate({ create: pageFactory });
- TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== currentPage; });
-
- currentPage = frame.topmost().currentPage;
- frame.topmost().navigate({ create: pageFactory });
- TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== currentPage; });
-
- TKUnit.assert(frame.topmost().canGoBack(), "Frame should be able to go back.");
- TKUnit.assert(frame.topmost().backStack.length === 3, "Back stack should have 3 entries.");
-
- // Navigate with clear history.
- currentPage = frame.topmost().currentPage;
- frame.topmost().navigate({ create: pageFactory, clearHistory: true });
- TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== currentPage; });
-
- TKUnit.assert(!frame.topmost().canGoBack(), "Frame should NOT be able to go back.");
- TKUnit.assert(frame.topmost().backStack.length === 0, "Back stack should have 0 entries.");
-
- frame.topmost().navigate({ create: mainPageFactory });
- TKUnit.waitUntilReady(() => { return frame.topmost().currentPage === mainTestPage; });
-}
\ No newline at end of file
diff --git a/apps/tests/navigation/custom-transition.android.ts b/apps/tests/navigation/custom-transition.android.ts
new file mode 100644
index 000000000..9c8bae0a7
--- /dev/null
+++ b/apps/tests/navigation/custom-transition.android.ts
@@ -0,0 +1,39 @@
+import transition = require("ui/transition");
+import platform = require("platform");
+
+var floatType = java.lang.Float.class.getField("TYPE").get(null);
+
+export class CustomTransition extends transition.Transition {
+ constructor(duration: number, curve: any) {
+ super(duration, curve);
+ }
+
+ public createAndroidAnimator(transitionType: string): android.animation.Animator {
+ var scaleValues = java.lang.reflect.Array.newInstance(floatType, 2);
+ switch (transitionType) {
+ case transition.AndroidTransitionType.enter:
+ case transition.AndroidTransitionType.popEnter:
+ scaleValues[0] = 0;
+ scaleValues[1] = 1;
+ break;
+ case transition.AndroidTransitionType.exit:
+ case transition.AndroidTransitionType.popExit:
+ scaleValues[0] = 1;
+ scaleValues[1] = 0;
+ break;
+ }
+ var objectAnimators = java.lang.reflect.Array.newInstance(android.animation.Animator.class, 2);
+ objectAnimators[0] = android.animation.ObjectAnimator.ofFloat(null, "scaleX", scaleValues);
+ objectAnimators[1] = android.animation.ObjectAnimator.ofFloat(null, "scaleY", scaleValues);
+ var animatorSet = new android.animation.AnimatorSet();
+ animatorSet.playTogether(objectAnimators);
+
+ var duration = this.getDuration();
+ if (duration !== undefined) {
+ animatorSet.setDuration(duration);
+ }
+ animatorSet.setInterpolator(this.getCurve());
+
+ return animatorSet;
+ }
+}
\ No newline at end of file
diff --git a/apps/tests/navigation/custom-transition.ios.ts b/apps/tests/navigation/custom-transition.ios.ts
new file mode 100644
index 000000000..82a52ba87
--- /dev/null
+++ b/apps/tests/navigation/custom-transition.ios.ts
@@ -0,0 +1,30 @@
+import transition = require("ui/transition");
+import platform = require("platform");
+
+export class CustomTransition extends transition.Transition {
+ constructor(duration: number, curve: any) {
+ super(duration, curve);
+ }
+
+ public animateIOSTransition(containerView: UIView, fromView: UIView, toView: UIView, operation: UINavigationControllerOperation, completion: (finished: boolean) => void): void {
+ toView.transform = CGAffineTransformMakeScale(0, 0);
+ fromView.transform = CGAffineTransformIdentity;
+
+ switch (operation) {
+ case UINavigationControllerOperation.UINavigationControllerOperationPush:
+ containerView.insertSubviewAboveSubview(toView, fromView);
+ break;
+ case UINavigationControllerOperation.UINavigationControllerOperationPop:
+ containerView.insertSubviewBelowSubview(toView, fromView);
+ break;
+ }
+
+ var duration = this.getDuration();
+ var curve = this.getCurve();
+ UIView.animateWithDurationAnimationsCompletion(duration, () => {
+ UIView.setAnimationCurve(curve);
+ toView.transform = CGAffineTransformIdentity;
+ fromView.transform = CGAffineTransformMakeScale(0, 0);
+ }, completion);
+ }
+}
\ No newline at end of file
diff --git a/apps/tests/navigation/navigation-tests.ts b/apps/tests/navigation/navigation-tests.ts
new file mode 100644
index 000000000..fa366b5d0
--- /dev/null
+++ b/apps/tests/navigation/navigation-tests.ts
@@ -0,0 +1,183 @@
+import TKUnit = require("../TKUnit");
+import platform = require("platform");
+import transitionModule = require("ui/transition");
+import {Frame, Page, topmost as topmostFrame, NavigationEntry, NavigationTransition, AnimationCurve, WrapLayout, Button} from "ui";
+import color = require("color");
+import helper = require("../ui/helper");
+import utils = require("utils/utils");
+import trace = require("trace");
+
+// Creates a random colorful page full of meaningless stuff.
+var pageFactory = function(): Page {
+ var page = new Page();
+ page.style.backgroundColor = new color.Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255));
+ return page;
+};
+
+function _testTransition(navigationTransition: NavigationTransition) {
+ var testId = `Transition[${JSON.stringify(navigationTransition)}]`;
+ trace.write(`Testing ${testId}`, trace.categories.Test);
+ var navigationEntry: NavigationEntry = {
+ create: function (): Page {
+ var page = new Page();
+ page.id = testId;
+ page.style.backgroundColor = new color.Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255));
+ return page;
+ },
+ animated: true,
+ navigationTransition: navigationTransition
+ }
+
+ helper.navigateWithEntry(navigationEntry);
+ TKUnit.wait(0.100);
+ helper.goBack();
+ TKUnit.wait(0.100);
+ utils.GC();
+}
+
+// Extremely slow. Run only if needed.
+export var test_Transitions = function () {
+ helper.navigate(() => {
+ var page = new Page();
+ page.id = "TransitionsTestPage_MAIN"
+ page.style.backgroundColor = new color.Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255));
+ return page;
+ });
+
+ var transitions;
+ if (platform.device.os === platform.platformNames.ios) {
+ transitions = ["curl"];
+ }
+ else {
+ var _sdkVersion = parseInt(platform.device.sdkVersion);
+ transitions = _sdkVersion >= 21 ? ["explode"] : [];
+ }
+ transitions = transitions.concat(["fade", "flip", "slide"]);
+ var durations = [undefined, 500];
+ var curves = [undefined, AnimationCurve.easeIn];
+
+ // Built-in transitions
+ var t, d, c;
+ var tlen = transitions.length;
+ var dlen = durations.length;
+ var clen = curves.length;
+ for (t = 0; t < tlen; t++) {
+ for (d = 0; d < dlen; d++) {
+ for (c = 0; c < clen; c++) {
+ _testTransition({
+ transition: transitions[t],
+ duration: durations[d],
+ curve: curves[c]
+ });
+ }
+ }
+ }
+
+ // Custom transition
+ var customTransitionModule = require("./custom-transition");
+ var customTransition = new customTransitionModule.CustomTransition();
+ _testTransition({transition: customTransition});
+
+ helper.goBack();
+}
+
+export var test_backstackVisible = function () {
+ var mainTestPage = topmostFrame().currentPage;
+ topmostFrame().navigate({ create: pageFactory });
+ TKUnit.waitUntilReady(() => { return topmostFrame().currentPage !== mainTestPage; });
+
+ // page1 should not be added to the backstack
+ var page0 = topmostFrame().currentPage;
+ topmostFrame().navigate({ create: pageFactory, backstackVisible: false });
+ TKUnit.waitUntilReady(() => { return topmostFrame().currentPage !== page0; });
+
+ var page1 = topmostFrame().currentPage;
+ topmostFrame().navigate({ create: pageFactory });
+ TKUnit.waitUntilReady(() => { return topmostFrame().currentPage !== page1; });
+
+ var page2 = topmostFrame().currentPage;
+ topmostFrame().goBack();
+ TKUnit.waitUntilReady(() => { return topmostFrame().currentPage !== page2; });
+
+ // From page2 we have to go directly to page0, skipping page1.
+ TKUnit.assert(topmostFrame().currentPage === page0, "Page 1 should be skipped when going back.");
+
+ topmostFrame().goBack();
+ TKUnit.waitUntilReady(() => { return topmostFrame().currentPage === mainTestPage; });
+}
+
+export var test_backToEntry = function () {
+ let page = (tag) => () => {
+ var p = new Page();
+ p["tag"] = tag;
+ return p;
+ }
+ let topmost = topmostFrame();
+ let wait = tag => TKUnit.waitUntilReady(() => topmost.currentPage["tag"] === tag, 1);
+ let navigate = tag => {
+ topmost.navigate({ create: page(tag) });
+ wait(tag)
+ }
+ let back = pages => {
+ topmost.goBack(topmost.backStack[topmost.backStack.length - pages]);
+ }
+ let currentPageMustBe = tag => {
+ wait(tag); // TODO: Add a timeout...
+ TKUnit.assert(topmost.currentPage["tag"] === tag, "Expected current page to be " + tag + " it was " + topmost.currentPage["tag"] + " instead.");
+ }
+
+ navigate("page1");
+ navigate("page2");
+ navigate("page3");
+ navigate("page4");
+
+ currentPageMustBe("page4");
+ back(2);
+ currentPageMustBe("page2");
+ back(1);
+ currentPageMustBe("page1");
+ navigate("page1.1");
+ navigate("page1.2");
+ currentPageMustBe("page1.2");
+ back(1);
+ currentPageMustBe("page1.1");
+ back(1);
+ currentPageMustBe("page1");
+ back(1);
+}
+
+// Clearing the history messes up the tests app.
+export var test_ClearHistory = function () {
+ var mainTestPage = topmostFrame().currentPage;
+ var mainPageFactory = function (): Page {
+ return mainTestPage;
+ };
+
+ var currentPage: Page;
+
+ currentPage = topmostFrame().currentPage;
+ topmostFrame().navigate({ create: pageFactory });
+ TKUnit.waitUntilReady(() => { return topmostFrame().currentPage !== currentPage; });
+
+ currentPage = topmostFrame().currentPage;
+ topmostFrame().navigate({ create: pageFactory });
+ TKUnit.waitUntilReady(() => { return topmostFrame().currentPage !== currentPage; });
+
+ currentPage = topmostFrame().currentPage;
+ topmostFrame().navigate({ create: pageFactory });
+ TKUnit.waitUntilReady(() => { return topmostFrame().currentPage !== currentPage; });
+
+ TKUnit.assert(topmostFrame().canGoBack(), "Frame should be able to go back.");
+ TKUnit.assert(topmostFrame().backStack.length === 3, "Back stack should have 3 entries.");
+
+ // Navigate with clear history.
+ currentPage = topmostFrame().currentPage;
+ topmostFrame().navigate({ create: pageFactory, clearHistory: true });
+ TKUnit.waitUntilReady(() => { return topmostFrame().currentPage !== currentPage; });
+
+ TKUnit.assert(!topmostFrame().canGoBack(), "Frame should NOT be able to go back.");
+ TKUnit.assert(topmostFrame().backStack.length === 0, "Back stack should have 0 entries.");
+
+ topmostFrame().navigate({ create: mainPageFactory });
+ TKUnit.waitUntilReady(() => { return topmostFrame().currentPage === mainTestPage; });
+}
\ No newline at end of file
diff --git a/apps/tests/testRunner.ts b/apps/tests/testRunner.ts
index 4d9da1006..fa0f8299b 100644
--- a/apps/tests/testRunner.ts
+++ b/apps/tests/testRunner.ts
@@ -92,15 +92,16 @@ if (!isRunningOnEmulator()) {
}
// Navigation tests should always be last.
-allTests["NAVIGATION"] = require("./navigation-tests");
+allTests["NAVIGATION"] = require("./navigation/navigation-tests");
var testsWithLongDelay = {
+ test_Transitions: 3 * 60 * 1000,
testLocation: 10000,
testLocationOnce: 10000,
testLocationOnceMaximumAge: 10000,
//web-view-tests
testLoadExistingUrl: 10000,
- testLoadInvalidUrl: 10000,
+ testLoadInvalidUrl: 10000
}
var running = false;
diff --git a/apps/tests/ui/helper.ts b/apps/tests/ui/helper.ts
index 306ca5da1..efdbd9e8d 100644
--- a/apps/tests/ui/helper.ts
+++ b/apps/tests/ui/helper.ts
@@ -207,22 +207,25 @@ export function buildUIWithWeakRefAndInteract(createFunc: (
export function navigate(pageFactory: () => page.Page, navigationContext?: any) {
var currentPage = frame.topmost().currentPage;
frame.topmost().navigate({ create: pageFactory, animated: false, context: navigationContext });
- TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== currentPage; });
+ TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== currentPage && frame.topmost().currentPage.isLoaded && !currentPage.isLoaded; });
}
export function navigateToModule(moduleName: string, context?: any) {
var currentPage = frame.topmost().currentPage;
frame.topmost().navigate({ moduleName: moduleName, context: context, animated: false });
- TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== currentPage; });
- TKUnit.assert(frame.topmost().currentPage.isLoaded, "Current page should be loaded!");
+ TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== currentPage && frame.topmost().currentPage.isLoaded && !currentPage.isLoaded; });
+}
+
+export function navigateWithEntry(navigationEntry: frame.NavigationEntry) {
+ var currentPage = frame.topmost().currentPage;
+ frame.topmost().navigate(navigationEntry);
+ TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== currentPage && frame.topmost().currentPage.isLoaded && !currentPage.isLoaded; });
}
export function goBack(): void {
var currentPage = frame.topmost().currentPage;
frame.topmost().goBack();
- TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== currentPage; });
- TKUnit.assert(frame.topmost().currentPage.isLoaded, "Current page should be loaded!");
- TKUnit.assert(!currentPage.isLoaded, "Previous page should be unloaded!");
+ TKUnit.waitUntilReady(() => { return frame.topmost().currentPage !== currentPage && frame.topmost().currentPage.isLoaded && !currentPage.isLoaded; });
}
export function assertAreClose(actual: number, expected: number, message: string): void {
diff --git a/apps/tests/ui/page/page-tests-common.ts b/apps/tests/ui/page/page-tests-common.ts
index fa53e1ffe..7984c515b 100644
--- a/apps/tests/ui/page/page-tests-common.ts
+++ b/apps/tests/ui/page/page-tests-common.ts
@@ -28,6 +28,7 @@ import stackLayoutModule = require("ui/layouts/stack-layout");
import helper = require("../helper");
import view = require("ui/core/view");
import platform = require("platform");
+import observable = require("data/observable");
export function addLabelToPage(page: PageModule.Page, text?: string) {
var label = new LabelModule.Label();
@@ -59,13 +60,9 @@ export function test_AfterPageLoaded_is_called_NativeInstance_is_created() {
helper.navigate(pageFactory);
- try {
- TKUnit.assert(nativeInstanceCreated, "Expected: true, Actual: " + nativeInstanceCreated);
- }
- finally {
- page.off(view.View.loadedEvent, handler);
- helper.goBack();
- }
+ TKUnit.assert(nativeInstanceCreated, "Expected: true, Actual: " + nativeInstanceCreated);
+ page.off(view.View.loadedEvent, handler);
+ helper.goBack();
}
export function test_PageLoaded_is_called_once() {
@@ -96,14 +93,10 @@ export function test_PageLoaded_is_called_once() {
helper.navigate(pageFactory2);
- try {
- TKUnit.assert(loaded === 1, "Expected: 1, Actual: " + loaded);
- }
- finally {
- page2.off(view.View.loadedEvent, handler);
- helper.goBack();
- helper.goBack();
- }
+ TKUnit.assert(loaded === 1, "Expected: 1, Actual: " + loaded);
+ page2.off(view.View.loadedEvent, handler);
+ helper.goBack();
+ helper.goBack();
}
export function test_NavigateToNewPage() {
@@ -147,7 +140,15 @@ export function test_NavigateToNewPage() {
TKUnit.assert(testPage._isAddedToNativeVisualTree === false, "Page._isAddedToNativeVisualTree should become false after navigating back");
}
-export function test_PageNavigation_EventSequence() {
+export function test_PageNavigation_EventSequence_WithTransition() {
+ _test_PageNavigation_EventSequence(true);
+}
+
+export function test_PageNavigation_EventSequence_WithoutTransition() {
+ _test_PageNavigation_EventSequence(false);
+}
+
+function _test_PageNavigation_EventSequence(withTransition: boolean) {
var testPage: PageModule.Page;
var context = { property: "this is the context" };
var eventSequence = [];
@@ -160,13 +161,15 @@ export function test_PageNavigation_EventSequence() {
TKUnit.assertEqual(data.context, context, "navigatingTo: navigationContext");
});
- testPage.on(PageModule.Page.loadedEvent, function (data) {
+ testPage.on(PageModule.Page.loadedEvent, function (data: observable.EventData) {
eventSequence.push("loaded");
+ TKUnit.assertNotEqual(FrameModule.topmost().currentPage, data.object);
});
testPage.on(PageModule.Page.navigatedToEvent, function (data: PageModule.NavigatedData) {
eventSequence.push("navigatedTo");
TKUnit.assertEqual(data.context, context, "navigatedTo : navigationContext");
+ TKUnit.assertEqual(FrameModule.topmost().currentPage, data.object);
});
testPage.on(PageModule.Page.navigatingFromEvent, function (data: PageModule.NavigatedData) {
@@ -186,7 +189,23 @@ export function test_PageNavigation_EventSequence() {
return testPage;
};
- helper.navigate(pageFactory, context);
+ if (withTransition) {
+ var navigationTransition: FrameModule.NavigationTransition = {
+ transition: "slide",
+ duration: 1000,
+ };
+ var navigationEntry: FrameModule.NavigationEntry = {
+ create: pageFactory,
+ context: context,
+ animated: true,
+ navigationTransition: navigationTransition
+ }
+ helper.navigateWithEntry(navigationEntry);
+ }
+ else {
+ helper.navigate(pageFactory, context);
+ }
+
helper.goBack();
var expectedEventSequence = ["navigatingTo", "loaded", "navigatedTo", "navigatingFrom", "navigatedFrom", "unloaded"];
@@ -218,13 +237,9 @@ export function test_NavigateTo_WithContext() {
//
TKUnit.waitUntilReady(() => { return topFrame.currentPage !== currentPage });
- try {
- var actualContextValue = testPage.navigationContext;
- TKUnit.assert(actualContextValue === "myContext", "Expected: myContext" + ", Actual: " + actualContextValue);
- }
- finally {
- helper.goBack();
- }
+ var actualContextValue = testPage.navigationContext;
+ TKUnit.assert(actualContextValue === "myContext", "Expected: myContext" + ", Actual: " + actualContextValue);
+ helper.goBack();
TKUnit.assert(testPage.navigationContext === undefined, "Navigation context should be cleared on navigating back");
}
@@ -239,13 +254,9 @@ export function test_FrameBackStack_WhenNavigatingForwardAndBack() {
helper.navigate(pageFactory);
var topFrame = FrameModule.topmost();
- try {
- TKUnit.assert(topFrame.backStack.length === 1, "Expected: 1, Actual: " + topFrame.backStack.length);
- TKUnit.assert(topFrame.canGoBack(), "We should can go back.");
- }
- finally {
- helper.goBack();
- }
+ TKUnit.assert(topFrame.backStack.length === 1, "Expected: 1, Actual: " + topFrame.backStack.length);
+ TKUnit.assert(topFrame.canGoBack(), "We should can go back.");
+ helper.goBack();
TKUnit.assert(topFrame.backStack.length === 0, "Expected: 0, Actual: " + topFrame.backStack.length);
TKUnit.assert(topFrame.canGoBack() === false, "canGoBack should return false.");
@@ -253,44 +264,31 @@ export function test_FrameBackStack_WhenNavigatingForwardAndBack() {
export function test_LoadPageFromModule() {
helper.navigateToModule("ui/page/test-page-module");
- try {
- var topFrame = FrameModule.topmost();
- TKUnit.assert(topFrame.currentPage.content instanceof LabelModule.Label, "Content of the test page should be a Label created within test-page-module.");
- var testLabel = topFrame.currentPage.content;
- TKUnit.assert(testLabel.text === "Label created within a page module.");
- }
- finally {
- helper.goBack();
- }
+ var topFrame = FrameModule.topmost();
+ TKUnit.assert(topFrame.currentPage.content instanceof LabelModule.Label, "Content of the test page should be a Label created within test-page-module.");
+ var testLabel = topFrame.currentPage.content;
+ TKUnit.assert(testLabel.text === "Label created within a page module.");
+ helper.goBack();
}
export function test_LoadPageFromDeclarativeWithCSS() {
helper.navigateToModule("ui/page/test-page-declarative-css");
- try {
- var topFrame = FrameModule.topmost();
- TKUnit.assert(topFrame.currentPage.content instanceof LabelModule.Label, "Content of the test page should be a Label created within test-page-module-css.");
- var testLabel = topFrame.currentPage.content;
- TKUnit.assert(testLabel.text === "Label created within a page declarative file with css.");
- TKUnit.assert(testLabel.style.backgroundColor.hex === "#ff00ff00", "Expected: #ff00ff00, Actual: " + testLabel.style.backgroundColor.hex);
- }
- finally {
- helper.goBack();
- }
-
+ var topFrame = FrameModule.topmost();
+ TKUnit.assert(topFrame.currentPage.content instanceof LabelModule.Label, "Content of the test page should be a Label created within test-page-module-css.");
+ var testLabel = topFrame.currentPage.content;
+ TKUnit.assert(testLabel.text === "Label created within a page declarative file with css.");
+ TKUnit.assert(testLabel.style.backgroundColor.hex === "#ff00ff00", "Expected: #ff00ff00, Actual: " + testLabel.style.backgroundColor.hex);
+ helper.goBack();
}
export function test_LoadPageFromModuleWithCSS() {
helper.navigateToModule("ui/page/test-page-module-css");
- try {
- var topFrame = FrameModule.topmost();
- TKUnit.assert(topFrame.currentPage.content instanceof LabelModule.Label, "Content of the test page should be a Label created within test-page-module-css.");
- var testLabel = topFrame.currentPage.content;
- TKUnit.assert(testLabel.text === "Label created within a page module css.");
- TKUnit.assert(testLabel.style.backgroundColor.hex === "#ff00ff00", "Expected: #ff00ff00, Actual: " + testLabel.style.backgroundColor.hex);
- }
- finally {
- helper.goBack();
- }
+ var topFrame = FrameModule.topmost();
+ TKUnit.assert(topFrame.currentPage.content instanceof LabelModule.Label, "Content of the test page should be a Label created within test-page-module-css.");
+ var testLabel = topFrame.currentPage.content;
+ TKUnit.assert(testLabel.text === "Label created within a page module css.");
+ TKUnit.assert(testLabel.style.backgroundColor.hex === "#ff00ff00", "Expected: #ff00ff00, Actual: " + testLabel.style.backgroundColor.hex);
+ helper.goBack();
}
export function test_NavigateToPageCreatedWithNavigationEntry() {
@@ -305,12 +303,8 @@ export function test_NavigateToPageCreatedWithNavigationEntry() {
helper.navigate(pageFactory);
var actualContent = testPage.content;
- try {
- TKUnit.assert(actualContent.text === expectedText, "Expected: " + expectedText + ", Actual: " + actualContent.text);
- }
- finally {
- helper.goBack();
- }
+ TKUnit.assert(actualContent.text === expectedText, "Expected: " + expectedText + ", Actual: " + actualContent.text);
+ helper.goBack();
}
export function test_cssShouldBeAppliedToAllNestedElements() {
@@ -330,13 +324,9 @@ export function test_cssShouldBeAppliedToAllNestedElements() {
helper.navigate(pageFactory);
var expectedText = "Some text";
- try {
- TKUnit.assert(label.style.backgroundColor.hex === "#ff00ff00", "Expected: #ff00ff00, Actual: " + label.style.backgroundColor.hex);
- TKUnit.assert(StackLayout.style.backgroundColor.hex === "#ffff0000", "Expected: #ffff0000, Actual: " + StackLayout.style.backgroundColor.hex);
- }
- finally {
- helper.goBack();
- }
+ TKUnit.assert(label.style.backgroundColor.hex === "#ff00ff00", "Expected: #ff00ff00, Actual: " + label.style.backgroundColor.hex);
+ TKUnit.assert(StackLayout.style.backgroundColor.hex === "#ffff0000", "Expected: #ffff0000, Actual: " + StackLayout.style.backgroundColor.hex);
+ helper.goBack();
}
export function test_cssShouldBeAppliedAfterChangeToAllNestedElements() {
@@ -357,17 +347,13 @@ export function test_cssShouldBeAppliedAfterChangeToAllNestedElements() {
helper.navigate(pageFactory);
var expectedText = "Some text";
- try {
- TKUnit.assert(label.style.backgroundColor.hex === "#ff00ff00", "Expected: #ff00ff00, Actual: " + label.style.backgroundColor.hex);
- TKUnit.assert(StackLayout.style.backgroundColor.hex === "#ffff0000", "Expected: #ffff0000, Actual: " + StackLayout.style.backgroundColor.hex);
+ TKUnit.assert(label.style.backgroundColor.hex === "#ff00ff00", "Expected: #ff00ff00, Actual: " + label.style.backgroundColor.hex);
+ TKUnit.assert(StackLayout.style.backgroundColor.hex === "#ffff0000", "Expected: #ffff0000, Actual: " + StackLayout.style.backgroundColor.hex);
- testPage.css = "stackLayout {background-color: #ff0000ff;} label {background-color: #ffff0000;}";
- TKUnit.assert(label.style.backgroundColor.hex === "#ffff0000", "Expected: #ffff0000, Actual: " + label.style.backgroundColor.hex);
- TKUnit.assert(StackLayout.style.backgroundColor.hex === "#ff0000ff", "Expected: #ff0000ff, Actual: " + StackLayout.style.backgroundColor.hex);
- }
- finally {
- helper.goBack();
- }
+ testPage.css = "stackLayout {background-color: #ff0000ff;} label {background-color: #ffff0000;}";
+ TKUnit.assert(label.style.backgroundColor.hex === "#ffff0000", "Expected: #ffff0000, Actual: " + label.style.backgroundColor.hex);
+ TKUnit.assert(StackLayout.style.backgroundColor.hex === "#ff0000ff", "Expected: #ff0000ff, Actual: " + StackLayout.style.backgroundColor.hex);
+ helper.goBack();
}
export function test_page_backgroundColor_is_white() {
@@ -377,10 +363,10 @@ export function test_page_backgroundColor_is_white() {
});
}
-export function test_WhenPageIsLoadedFrameCurrentPageIsTheSameInstance() {
+export function test_WhenPageIsLoadedFrameCurrentPageIsNotYetTheSameAsThePage() {
var page;
var loadedEventHandler = function (args) {
- TKUnit.assert(FrameModule.topmost().currentPage === args.object, `frame.topmost().currentPage should be equal to args.object page instance in the page.loaded event handler. Expected: ${args.object.id}; Actual: ${FrameModule.topmost().currentPage.id};`);
+ TKUnit.assert(FrameModule.topmost().currentPage !== args.object, `When a page is loaded it should not yet be the current page. Loaded: ${args.object.id}; Current: ${FrameModule.topmost().currentPage.id};`);
}
var pageFactory = function (): PageModule.Page {
@@ -393,13 +379,30 @@ export function test_WhenPageIsLoadedFrameCurrentPageIsTheSameInstance() {
return page;
};
- try {
- helper.navigate(pageFactory);
- page.off(view.View.loadedEvent, loadedEventHandler);
- }
- finally {
- helper.goBack();
+ helper.navigate(pageFactory);
+ page.off(view.View.loadedEvent, loadedEventHandler);
+ helper.goBack();
+}
+
+export function test_WhenPageIsNavigatedToFrameCurrentPageIsNowTheSameAsThePage() {
+ var page;
+ var navigatedEventHandler = function (args) {
+ TKUnit.assert(FrameModule.topmost().currentPage === args.object, `frame.topmost().currentPage should be equal to args.object page instance in the page.navigatedTo event handler. Expected: ${args.object.id}; Actual: ${FrameModule.topmost().currentPage.id};`);
}
+
+ var pageFactory = function (): PageModule.Page {
+ page = new PageModule.Page();
+ page.id = "newPage";
+ page.on(PageModule.Page.navigatedToEvent, navigatedEventHandler);
+ var label = new LabelModule.Label();
+ label.text = "Text";
+ page.content = label;
+ return page;
+ };
+
+ helper.navigate(pageFactory);
+ page.off(view.View.loadedEvent, navigatedEventHandler);
+ helper.goBack();
}
export function test_WhenNavigatingForwardAndBack_IsBackNavigationIsCorrect() {
@@ -407,7 +410,7 @@ export function test_WhenNavigatingForwardAndBack_IsBackNavigationIsCorrect() {
var page2;
var forwardCounter = 0;
var backCounter = 0;
- var loadedEventHandler = function (args: PageModule.NavigatedData) {
+ var navigatedEventHandler = function (args: PageModule.NavigatedData) {
if (args.isBackNavigation) {
backCounter++;
}
@@ -418,28 +421,24 @@ export function test_WhenNavigatingForwardAndBack_IsBackNavigationIsCorrect() {
var pageFactory1 = function (): PageModule.Page {
page1 = new PageModule.Page();
- page1.on(PageModule.Page.navigatedToEvent, loadedEventHandler);
+ page1.on(PageModule.Page.navigatedToEvent, navigatedEventHandler);
return page1;
};
var pageFactory2 = function (): PageModule.Page {
page2 = new PageModule.Page();
- page2.on(PageModule.Page.navigatedToEvent, loadedEventHandler);
+ page2.on(PageModule.Page.navigatedToEvent, navigatedEventHandler);
return page2;
};
- try {
- helper.navigate(pageFactory1);
- helper.navigate(pageFactory2);
- helper.goBack();
- TKUnit.assertEqual(forwardCounter, 2, "Forward navigation counter should be 1");
- TKUnit.assertEqual(backCounter, 1, "Backward navigation counter should be 1");
- page1.off(PageModule.Page.navigatedToEvent, loadedEventHandler);
- page2.off(PageModule.Page.navigatedToEvent, loadedEventHandler);
- }
- finally {
- helper.goBack();
- }
+ helper.navigate(pageFactory1);
+ helper.navigate(pageFactory2);
+ helper.goBack();
+ TKUnit.assertEqual(forwardCounter, 2, "Forward navigation counter should be 1");
+ TKUnit.assertEqual(backCounter, 1, "Backward navigation counter should be 1");
+ page1.off(PageModule.Page.navigatedToEvent, navigatedEventHandler);
+ page2.off(PageModule.Page.navigatedToEvent, navigatedEventHandler);
+ helper.goBack();
}
//export function test_ModalPage_Layout_is_Correct() {
diff --git a/apps/tests/ui/page/page-tests.ios.ts b/apps/tests/ui/page/page-tests.ios.ts
index cff5727ce..acd1dc23b 100644
--- a/apps/tests/ui/page/page-tests.ios.ts
+++ b/apps/tests/ui/page/page-tests.ios.ts
@@ -27,7 +27,7 @@ export function test_NavigateToNewPage_InnerControl() {
TKUnit.assert(label.isLoaded === false, "InnerControl.isLoaded should become false after navigating back");
}
-export function test_WhenPageIsLoadedItCanShowAnotherPageAsModal() {
+export function test_WhenPageIsNavigatedToItCanShowAnotherPageAsModal() {
var masterPage;
var ctx = {
shownModally: false
@@ -42,7 +42,7 @@ export function test_WhenPageIsLoadedItCanShowAnotherPageAsModal() {
modalClosed = true;
}
- var loadedEventHandler = function (args) {
+ var navigatedToEventHandler = function (args) {
TKUnit.assert(!frame.topmost().currentPage.modal, "frame.topmost().currentPage.modal should be undefined when no modal page is shown!");
var basePath = "ui/page/";
args.object.showModal(basePath + "modal-page", ctx, modalCloseCallback, false);
@@ -51,7 +51,7 @@ export function test_WhenPageIsLoadedItCanShowAnotherPageAsModal() {
var masterPageFactory = function (): PageModule.Page {
masterPage = new PageModule.Page();
masterPage.id = "newPage";
- masterPage.on(view.View.loadedEvent, loadedEventHandler);
+ masterPage.on(PageModule.Page.navigatedToEvent, navigatedToEventHandler);
var label = new LabelModule.Label();
label.text = "Text";
masterPage.content = label;
@@ -61,7 +61,7 @@ export function test_WhenPageIsLoadedItCanShowAnotherPageAsModal() {
try {
helper.navigate(masterPageFactory);
TKUnit.waitUntilReady(() => { return modalClosed; });
- masterPage.off(view.View.loadedEvent, loadedEventHandler);
+ masterPage.off(view.View.loadedEvent, navigatedToEventHandler);
}
finally {
helper.goBack();
@@ -77,7 +77,7 @@ export function test_WhenShowingModalPageUnloadedIsNotFiredForTheMasterPage() {
modalClosed = true;
}
- var loadedEventHandler = function (args) {
+ var navigatedToEventHandler = function (args) {
var basePath = "ui/page/";
args.object.showModal(basePath + "modal-page", null, modalCloseCallback, false);
};
@@ -89,7 +89,7 @@ export function test_WhenShowingModalPageUnloadedIsNotFiredForTheMasterPage() {
var masterPageFactory = function (): PageModule.Page {
masterPage = new PageModule.Page();
masterPage.id = "master-page";
- masterPage.on(view.View.loadedEvent, loadedEventHandler);
+ masterPage.on(PageModule.Page.navigatedToEvent, navigatedToEventHandler);
masterPage.on(view.View.unloadedEvent, unloadedEventHandler);
var label = new LabelModule.Label();
label.text = "Modal Page";
@@ -101,8 +101,8 @@ export function test_WhenShowingModalPageUnloadedIsNotFiredForTheMasterPage() {
helper.navigate(masterPageFactory);
TKUnit.waitUntilReady(() => { return modalClosed; });
TKUnit.assert(!masterPageUnloaded, "Master page should not raise 'unloaded' when showing modal!");
- masterPage.off(view.View.loadedEvent, loadedEventHandler);
- masterPage.off(view.View.unloadedEvent, loadedEventHandler);
+ masterPage.off(view.View.loadedEvent, navigatedToEventHandler);
+ masterPage.off(view.View.unloadedEvent, unloadedEventHandler);
}
finally {
helper.goBack();
diff --git a/trace/trace.d.ts b/trace/trace.d.ts
index 2dff00a98..dcc83817a 100644
--- a/trace/trace.d.ts
+++ b/trace/trace.d.ts
@@ -76,6 +76,7 @@ declare module "trace" {
export var Binding: string;
export var Error: string;
export var Animation: string;
+ export var Transition: string;
export var All: string;
diff --git a/trace/trace.ts b/trace/trace.ts
index 4288eb4f0..98390ad7b 100644
--- a/trace/trace.ts
+++ b/trace/trace.ts
@@ -111,7 +111,8 @@ export module categories {
export var Binding = "Binding";
export var Error = "Error";
export var Animation = "Animation";
- export var All = VisualTreeEvents + "," + Layout + "," + Style + "," + ViewHierarchy + "," + NativeLifecycle + "," + Debug + "," + Navigation + "," + Test + "," + Binding + "," + Error + "," + Animation;
+ export var Transition = "Transition";
+ export var All = VisualTreeEvents + "," + Layout + "," + Style + "," + ViewHierarchy + "," + NativeLifecycle + "," + Debug + "," + Navigation + "," + Test + "," + Binding + "," + Error + "," + Animation + "," + Transition;
export var separator = ",";
diff --git a/tsconfig.json b/tsconfig.json
index db6ff7be7..cba672883 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -104,6 +104,7 @@
"apps/perf-tests/NavigationMemoryLeakTest/mainPage.ts",
"apps/perf-tests/NavigationTest/app.ts",
"apps/perf-tests/NavigationTest/details-page.ts",
+ "apps/perf-tests/NavigationTest/list-picker-page.ts",
"apps/perf-tests/NavigationTest/master-page.ts",
"apps/perf-tests/NavigationTest/page1.ts",
"apps/perf-tests/NavigationTest/page2.ts",
@@ -120,6 +121,8 @@
"apps/perf-tests/common.ts",
"apps/perf-tests/controls-page.d.ts",
"apps/perf-tests/controls-page.ts",
+ "apps/perf-tests/custom-transition.android.ts",
+ "apps/perf-tests/custom-transition.ios.ts",
"apps/perf-tests/nav-page.d.ts",
"apps/perf-tests/nav-page.ts",
"apps/pickers-demo/app.ts",
@@ -179,7 +182,9 @@
"apps/tests/layouts/stack-layout-tests.ts",
"apps/tests/layouts/wrap-layout-tests.ts",
"apps/tests/location-tests.ts",
- "apps/tests/navigation-tests.ts",
+ "apps/tests/navigation/custom-transition.android.ts",
+ "apps/tests/navigation/custom-transition.ios.ts",
+ "apps/tests/navigation/navigation-tests.ts",
"apps/tests/observable-array-tests.ts",
"apps/tests/observable-tests.ts",
"apps/tests/pages/app.ts",
@@ -663,6 +668,14 @@
"ui/time-picker/time-picker.android.ts",
"ui/time-picker/time-picker.d.ts",
"ui/time-picker/time-picker.ios.ts",
+ "ui/transition/fade-transition.android.ts",
+ "ui/transition/fade-transition.ios.ts",
+ "ui/transition/flip-transition.android.ts",
+ "ui/transition/slide-transition.android.ts",
+ "ui/transition/slide-transition.ios.ts",
+ "ui/transition/transition.android.ts",
+ "ui/transition/transition.d.ts",
+ "ui/transition/transition.ios.ts",
"ui/ui.d.ts",
"ui/ui.ts",
"ui/utils.d.ts",
diff --git a/ui/animation/animation-common.ts b/ui/animation/animation-common.ts
index e41930e28..dd58ce806 100644
--- a/ui/animation/animation-common.ts
+++ b/ui/animation/animation-common.ts
@@ -70,7 +70,7 @@ export class Animation implements definition.Animation {
var i = 0;
var length = animationDefinitions.length;
for (; i < length; i++) {
- animationDefinitions[i].curve = this._resolveAnimationCurve(animationDefinitions[i].curve);
+ animationDefinitions[i].curve = definition._resolveAnimationCurve(animationDefinitions[i].curve);
this._propertyAnimations = this._propertyAnimations.concat(Animation._createPropertyAnimations(animationDefinitions[i]));
}
@@ -92,10 +92,6 @@ export class Animation implements definition.Animation {
this._reject(new Error("Animation cancelled."));
}
- _resolveAnimationCurve(curve: any): any {
- //
- }
-
private static _createPropertyAnimations(animationDefinition: definition.AnimationDefinition): Array {
if (!animationDefinition.target) {
throw new Error("No animation target specified.");
diff --git a/ui/animation/animation.android.ts b/ui/animation/animation.android.ts
index 434e060b3..cf5c4648a 100644
--- a/ui/animation/animation.android.ts
+++ b/ui/animation/animation.android.ts
@@ -298,27 +298,31 @@ export class Animation extends common.Animation implements definition.Animation
this._propertyResetCallbacks = this._propertyResetCallbacks.concat(propertyResetCallbacks);
}
- _resolveAnimationCurve(curve: any): any {
- switch (curve) {
- case enums.AnimationCurve.easeIn:
- trace.write("Animation curve resolved to android.view.animation.AccelerateInterpolator(1).", trace.categories.Animation);
- return new android.view.animation.AccelerateInterpolator(1);
- case enums.AnimationCurve.easeOut:
- trace.write("Animation curve resolved to android.view.animation.DecelerateInterpolator(1).", trace.categories.Animation);
- return new android.view.animation.DecelerateInterpolator(1);
- case enums.AnimationCurve.easeInOut:
- trace.write("Animation curve resolved to android.view.animation.AccelerateDecelerateInterpolator().", trace.categories.Animation);
- return new android.view.animation.AccelerateDecelerateInterpolator();
- case enums.AnimationCurve.linear:
- trace.write("Animation curve resolved to android.view.animation.LinearInterpolator().", trace.categories.Animation);
- return new android.view.animation.LinearInterpolator();
- default:
- trace.write("Animation curve resolved to original: " + curve, trace.categories.Animation);
- return curve;
- }
- }
-
private static _getAndroidRepeatCount(iterations: number): number {
return (iterations === Number.POSITIVE_INFINITY) ? android.view.animation.Animation.INFINITE : iterations - 1;
}
-}
\ No newline at end of file
+}
+
+var easeIn = new android.view.animation.AccelerateInterpolator(1);
+var easeOut = new android.view.animation.DecelerateInterpolator(1);
+var easeInOut = new android.view.animation.AccelerateDecelerateInterpolator();
+var linear = new android.view.animation.LinearInterpolator();
+export function _resolveAnimationCurve(curve: any): any {
+ switch (curve) {
+ case enums.AnimationCurve.easeIn:
+ trace.write("Animation curve resolved to android.view.animation.AccelerateInterpolator(1).", trace.categories.Animation);
+ return easeIn;
+ case enums.AnimationCurve.easeOut:
+ trace.write("Animation curve resolved to android.view.animation.DecelerateInterpolator(1).", trace.categories.Animation);
+ return easeOut;
+ case enums.AnimationCurve.easeInOut:
+ trace.write("Animation curve resolved to android.view.animation.AccelerateDecelerateInterpolator().", trace.categories.Animation);
+ return easeInOut;
+ case enums.AnimationCurve.linear:
+ trace.write("Animation curve resolved to android.view.animation.LinearInterpolator().", trace.categories.Animation);
+ return linear;
+ default:
+ trace.write("Animation curve resolved to original: " + curve, trace.categories.Animation);
+ return curve;
+ }
+}
diff --git a/ui/animation/animation.d.ts b/ui/animation/animation.d.ts
index a888c8a9a..028ece8b8 100644
--- a/ui/animation/animation.d.ts
+++ b/ui/animation/animation.d.ts
@@ -76,8 +76,9 @@
public play: () => Promise;
public cancel: () => void;
public isPlaying: boolean;
- //@private
- _resolveAnimationCurve(curve: any): any
- //@endprivate
}
+
+ //@private
+ export function _resolveAnimationCurve(curve: any): any;
+ //@endprivate
}
\ No newline at end of file
diff --git a/ui/animation/animation.ios.ts b/ui/animation/animation.ios.ts
index e70f9c5cd..e32b3d656 100644
--- a/ui/animation/animation.ios.ts
+++ b/ui/animation/animation.ios.ts
@@ -144,26 +144,6 @@ export class Animation extends common.Animation implements definition.Animation
this._iOSAnimationFunction = Animation._createiOSAnimationFunction(this._mergedPropertyAnimations, 0, this._playSequentially, animationFinishedCallback);
}
- _resolveAnimationCurve(curve: any): any {
- switch (curve) {
- case enums.AnimationCurve.easeIn:
- trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveEaseIn.", trace.categories.Animation);
- return UIViewAnimationCurve.UIViewAnimationCurveEaseIn;
- case enums.AnimationCurve.easeOut:
- trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveEaseOut.", trace.categories.Animation);
- return UIViewAnimationCurve.UIViewAnimationCurveEaseOut;
- case enums.AnimationCurve.easeInOut:
- trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveEaseInOut.", trace.categories.Animation);
- return UIViewAnimationCurve.UIViewAnimationCurveEaseInOut;
- case enums.AnimationCurve.linear:
- trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveLinear.", trace.categories.Animation);
- return UIViewAnimationCurve.UIViewAnimationCurveLinear;
- default:
- trace.write("Animation curve resolved to original: " + curve, trace.categories.Animation);
- return curve;
- }
- }
-
private static _createiOSAnimationFunction(propertyAnimations: Array, index: number, playSequentially: boolean, finishedCallback: (cancelled?: boolean) => void): Function {
return (cancelled?: boolean) => {
if (cancelled && finishedCallback) {
@@ -382,3 +362,23 @@ export function _getTransformMismatchErrorMessage(view: viewModule.View): string
return undefined;
}
+
+export function _resolveAnimationCurve(curve: any): any {
+ switch (curve) {
+ case enums.AnimationCurve.easeIn:
+ trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveEaseIn.", trace.categories.Animation);
+ return UIViewAnimationCurve.UIViewAnimationCurveEaseIn;
+ case enums.AnimationCurve.easeOut:
+ trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveEaseOut.", trace.categories.Animation);
+ return UIViewAnimationCurve.UIViewAnimationCurveEaseOut;
+ case enums.AnimationCurve.easeInOut:
+ trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveEaseInOut.", trace.categories.Animation);
+ return UIViewAnimationCurve.UIViewAnimationCurveEaseInOut;
+ case enums.AnimationCurve.linear:
+ trace.write("Animation curve resolved to UIViewAnimationCurve.UIViewAnimationCurveLinear.", trace.categories.Animation);
+ return UIViewAnimationCurve.UIViewAnimationCurveLinear;
+ default:
+ trace.write("Animation curve resolved to original: " + curve, trace.categories.Animation);
+ return curve;
+ }
+}
\ No newline at end of file
diff --git a/ui/core/view-common.ts b/ui/core/view-common.ts
index 5e63bf662..493e90459 100644
--- a/ui/core/view-common.ts
+++ b/ui/core/view-common.ts
@@ -8,7 +8,6 @@ import styleScope = require("../styling/style-scope");
import enums = require("ui/enums");
import utils = require("utils/utils");
import color = require("color");
-import animationModule = require("ui/animation");
import observable = require("data/observable");
import {PropertyMetadata, ProxyObject} from "ui/core/proxy";
import {PropertyMetadataSettings, PropertyChangeData, Property, ValueSource, PropertyMetadata as doPropertyMetadata} from "ui/core/dependency-observable";
@@ -17,6 +16,7 @@ import {CommonLayoutParams, nativeLayoutParamsProperty} from "ui/styling/style";
import * as visualStateConstants from "ui/styling/visual-state-constants";
import * as bindableModule from "ui/core/bindable";
import * as visualStateModule from "../styling/visual-state";
+import * as animModule from "ui/animation";
var bindable: typeof bindableModule;
function ensureBindable() {
@@ -1139,11 +1139,12 @@ export class View extends ProxyObject implements definition.View {
return undefined;
}
- public animate(animation: animationModule.AnimationDefinition): Promise {
+ public animate(animation: any): Promise {
return this.createAnimation(animation).play();
}
- public createAnimation(animation: animationModule.AnimationDefinition): animationModule.Animation {
+ public createAnimation(animation: any): any {
+ var animationModule: typeof animModule = require("ui/animation");
var that = this;
animation.target = that;
return new animationModule.Animation([animation]);
diff --git a/ui/frame/frame-common.ts b/ui/frame/frame-common.ts
index 024370582..fc66b933c 100644
--- a/ui/frame/frame-common.ts
+++ b/ui/frame/frame-common.ts
@@ -138,9 +138,11 @@ export class Frame extends CustomLayoutView implements definition.Frame {
private _backStack: Array;
public _currentEntry: definition.BackstackEntry;
private _animated: boolean;
+ private _navigationTransition: definition.NavigationTransition;
public _isInFrameStack = false;
public static defaultAnimatedNavigation = true;
+ public static defaultNavigationTransition: definition.NavigationTransition;
// TODO: Currently our navigation will not be synchronized in case users directly call native navigation methods like Activity.startActivity.
@@ -160,7 +162,7 @@ export class Frame extends CustomLayoutView implements definition.Frame {
* @param to The backstack entry to navigate back to.
*/
public goBack(backstackEntry?: definition.BackstackEntry) {
- trace.write(this._getTraceId() + ".goBack();", trace.categories.Navigation);
+ trace.write(`GO BACK`, trace.categories.Navigation);
if (!this.canGoBack()) {
// TODO: Do we need to throw an error?
return;
@@ -187,12 +189,12 @@ export class Frame extends CustomLayoutView implements definition.Frame {
this._processNavigationContext(navigationContext);
}
else {
- trace.write(this._getTraceId() + ".goBack scheduled;", trace.categories.Navigation);
+ trace.write(`Going back scheduled`, trace.categories.Navigation);
}
}
public navigate(param: any) {
- trace.write(this._getTraceId() + ".navigate();", trace.categories.Navigation);
+ trace.write(`NAVIGATE`, trace.categories.Navigation);
var entry = buildEntryFromArgs(param);
var page = resolvePageFromEntry(entry);
@@ -215,7 +217,7 @@ export class Frame extends CustomLayoutView implements definition.Frame {
this._processNavigationContext(navigationContext);
}
else {
- trace.write(this._getTraceId() + ".navigation scheduled;", trace.categories.Navigation);
+ trace.write(`Navigation scheduled`, trace.categories.Navigation);
}
}
@@ -258,7 +260,7 @@ export class Frame extends CustomLayoutView implements definition.Frame {
}
public _updateActionBar(page?: Page) {
- trace.write("calling _updateActionBar on Frame", trace.categories.Navigation);
+ //trace.write("calling _updateActionBar on Frame", trace.categories.Navigation);
}
private _processNavigationContext(navigationContext: NavigationContext) {
@@ -318,10 +320,19 @@ export class Frame extends CustomLayoutView implements definition.Frame {
public get animated(): boolean {
return this._animated;
}
+
public set animated(value: boolean) {
this._animated = value;
}
+ public get navigationTransition(): definition.NavigationTransition {
+ return this._navigationTransition;
+ }
+
+ public set navigationTransition(value: definition.NavigationTransition) {
+ this._navigationTransition = value;
+ }
+
get backStack(): Array {
return this._backStack.slice();
}
@@ -375,7 +386,7 @@ export class Frame extends CustomLayoutView implements definition.Frame {
}
}
- public _getIsAnimatedNavigation(entry: definition.NavigationEntry) {
+ public _getIsAnimatedNavigation(entry: definition.NavigationEntry): boolean {
if (entry && isDefined(entry.animated)) {
return entry.animated;
}
@@ -387,8 +398,16 @@ export class Frame extends CustomLayoutView implements definition.Frame {
return Frame.defaultAnimatedNavigation;
}
- private _getTraceId(): string {
- return "Frame<" + this._domId + ">";
+ public _getNavigationTransition(entry: definition.NavigationEntry): definition.NavigationTransition {
+ if (entry && isDefined(entry.navigationTransition)) {
+ return entry.navigationTransition;
+ }
+
+ if (isDefined(this.navigationTransition)) {
+ return this.navigationTransition;
+ }
+
+ return Frame.defaultNavigationTransition;
}
public get navigationBarHeight(): number {
diff --git a/ui/frame/frame.android.ts b/ui/frame/frame.android.ts
index 0983cff88..1161f3e4e 100644
--- a/ui/frame/frame.android.ts
+++ b/ui/frame/frame.android.ts
@@ -6,6 +6,7 @@ import observable = require("data/observable");
import application = require("application");
import * as types from "utils/types";
import * as utilsModule from "utils/utils";
+import transitionModule = require("ui/transition");
global.moduleMerge(frameCommon, exports);
@@ -15,6 +16,7 @@ var HIDDEN = "_hidden";
var INTENT_EXTRA = "com.tns.activity";
var ANDROID_FRAME = "android_frame";
var BACKSTACK_TAG = "_backstackTag";
+var IS_BACK = "_isBack";
var NAV_DEPTH = "_navDepth";
var CLEARING_HISTORY = "_clearingHistory";
var activityInitialized = false;
@@ -28,67 +30,78 @@ function ensureFragmentClass() {
}
FragmentClass = (android.app.Fragment).extend({
+
+ onCreate: function (savedInstanceState: android.os.Bundle) {
+ trace.write(`${this.getTag()}.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle);
+ this.super.onCreate(savedInstanceState);
+ this.super.setHasOptionsMenu(true);
+ },
- onCreate: function (savedInstanceState: android.os.Bundle) {
- trace.write(`PageFragmentBody.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle);
- this.super.onCreate(savedInstanceState);
- this.super.setHasOptionsMenu(true);
- },
-
- onCreateView: function (inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View {
- var entry = this.entry;
- var page = entry.resolvedPage;
- trace.write(`PageFragmentBody.onCreateView(${inflater}, ${page}, ${savedInstanceState})`, trace.categories.NativeLifecycle);
- if (savedInstanceState && savedInstanceState.getBoolean(HIDDEN, false)) {
- this.super.getFragmentManager().beginTransaction().hide(this).commit();
- page._onAttached(this.getActivity());
- }
- else {
- onFragmentShown(this);
- }
- return page._nativeView;
- },
-
- onHiddenChanged: function (hidden: boolean) {
- trace.write(`PageFragmentBody.onHiddenChanged(${hidden})`, trace.categories.NativeLifecycle);
- this.super.onHiddenChanged(hidden);
- if (hidden) {
- onFragmentHidden(this);
- }
- else {
- onFragmentShown(this);
- }
- },
-
- onSaveInstanceState: function (outState: android.os.Bundle) {
- trace.write(`PageFragmentBody.onSaveInstanceState(${outState})`, trace.categories.NativeLifecycle);
- this.super.onSaveInstanceState(outState);
- if (this.isHidden()) {
- outState.putBoolean(HIDDEN, true);
- }
- },
-
- onDestroyView: function () {
- trace.write(`PageFragmentBody.onDestroyView()`, trace.categories.NativeLifecycle);
- this.super.onDestroyView();
- onFragmentHidden(this);
- },
-
- onDestroy: function () {
- trace.write(`PageFragmentBody.onDestroy()`, trace.categories.NativeLifecycle);
- this.super.onDestroy();
-
- var utils: typeof utilsModule = require("utils/utils");
-
- utils.GC();
+ onCreateView: function (inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View {
+ trace.write(`${this.getTag()}.onCreateView(inflater, container, ${savedInstanceState})`, trace.categories.NativeLifecycle);
+ var entry = this.entry;
+ var page = entry.resolvedPage;
+ if (savedInstanceState && savedInstanceState.getBoolean(HIDDEN, false)) {
+ this.super.getFragmentManager().beginTransaction().hide(this).commit();
+ page._onAttached(this.getActivity());
}
- });
+ else {
+ onFragmentShown(this);
+ }
+ return page._nativeView;
+ },
+
+ onHiddenChanged: function (hidden: boolean) {
+ trace.write(`${this.getTag()}.onHiddenChanged(${hidden})`, trace.categories.NativeLifecycle);
+ this.super.onHiddenChanged(hidden);
+ if (hidden) {
+ onFragmentHidden(this);
+ }
+ else {
+ onFragmentShown(this);
+ }
+ },
+
+ onSaveInstanceState: function (outState: android.os.Bundle) {
+ trace.write(`${this.getTag()}.onSaveInstanceState(${outState})`, trace.categories.NativeLifecycle);
+ this.super.onSaveInstanceState(outState);
+ if (this.isHidden()) {
+ outState.putBoolean(HIDDEN, true);
+ }
+ },
+
+ onDestroyView: function () {
+ trace.write(`${this.getTag()}.onDestroyView()`, trace.categories.NativeLifecycle);
+ this.super.onDestroyView();
+ onFragmentHidden(this);
+ },
+
+ onDestroy: function () {
+ trace.write(`${this.getTag()}.onDestroy()`, trace.categories.NativeLifecycle);
+ this.super.onDestroy();
+
+ var utils: typeof utilsModule = require("utils/utils");
+
+ utils.GC();
+ },
+
+ onCreateAnimator: function (transit: number, enter: boolean, nextAnim: number): android.animation.Animator {
+ var animator = transitionModule._onFragmentCreateAnimator(this, nextAnim);
+
+ if (!animator) {
+ animator = this.super.onCreateAnimator(transit, enter, nextAnim);
+ }
+
+ trace.write(`${this.getTag() }.onCreateAnimator(${transit}, ${enter}, ${nextAnim}): ${animator}`, trace.categories.NativeLifecycle);
+ return animator;
+ }
+});
}
function onFragmentShown(fragment) {
- trace.write(`onFragmentShown(${fragment.toString()})`, trace.categories.NativeLifecycle);
+ trace.write(`SHOWN ${fragment.getTag()}`, trace.categories.NativeLifecycle);
if (fragment[CLEARING_HISTORY]) {
- trace.write(`${fragment.toString() } has been shown, but we are currently clearing history. Returning.`, trace.categories.NativeLifecycle);
+ trace.write(`${fragment.getTag()} has been shown, but we are currently clearing history. Returning.`, trace.categories.NativeLifecycle);
return null;
}
@@ -99,7 +112,7 @@ function onFragmentShown(fragment) {
var page: pages.Page = entry.resolvedPage;
let currentNavigationContext;
- let navigationQueue = (frame)._navigationQueue;
+ let navigationQueue = frame._navigationQueue;
for (let i = 0; i < navigationQueue.length; i++) {
if (navigationQueue[i].entry === entry) {
currentNavigationContext = navigationQueue[i];
@@ -108,34 +121,33 @@ function onFragmentShown(fragment) {
}
var isBack = currentNavigationContext ? currentNavigationContext.isBackNavigation : false;
-
- frame._currentEntry = entry;
-
frame._addView(page);
+
// onFragmentShown is called before NativeActivity.start where we call frame.onLoaded
// We need to call frame.onLoaded() here so that the call to frame._addView(page) will emit the page.loaded event
// before the page.navigatedTo event making the two platforms identical.
if (!frame.isLoaded) {
+ frame._currentEntry = entry;
frame.onLoaded();
}
- page.onNavigatedTo(isBack);
- frame._processNavigationQueue(page);
+
+ // Handle page transitions.
+ transitionModule._onFragmentShown(fragment, isBack);
}
function onFragmentHidden(fragment) {
- trace.write(`onFragmentHidden(${fragment.toString()})`, trace.categories.NativeLifecycle);
+ trace.write(`HIDDEN ${fragment.getTag()}`, trace.categories.NativeLifecycle);
+
if (fragment[CLEARING_HISTORY]) {
- trace.write(`${fragment.toString() } has been hidden, but we are currently clearing history. Returning.`, trace.categories.NativeLifecycle);
+ trace.write(`${fragment.getTag()} has been hidden, but we are currently clearing history. Returning.`, trace.categories.NativeLifecycle);
return null;
}
- var entry: definition.BackstackEntry = fragment.entry;
- var page: pages.Page = entry.resolvedPage;
- // This might be a second call if the fragment is hidden and then destroyed.
- if (page && page.frame) {
- var frame = page.frame;
- frame._removeView(page);
- }
+ var isBack = fragment.entry[IS_BACK];
+ fragment.entry[IS_BACK] = undefined;
+
+ // Handle page transitions.
+ transitionModule._onFragmentHidden(fragment, isBack);
}
export class Frame extends frameCommon.Frame {
@@ -157,6 +169,13 @@ export class Frame extends frameCommon.Frame {
frameCommon.Frame.defaultAnimatedNavigation = value;
}
+ public static get defaultNavigationTransition(): definition.NavigationTransition {
+ return frameCommon.Frame.defaultNavigationTransition;
+ }
+ public static set defaultNavigationTransition(value: definition.NavigationTransition) {
+ frameCommon.Frame.defaultNavigationTransition = value;
+ }
+
get containerViewId(): number {
return this._containerViewId;
}
@@ -170,7 +189,7 @@ export class Frame extends frameCommon.Frame {
}
public _navigateCore(backstackEntry: definition.BackstackEntry) {
- trace.write(`_navigateCore; id: ${backstackEntry.resolvedPage.id}; backstackVisible: ${this._isEntryBackstackVisible(backstackEntry)}; clearHistory: ${backstackEntry.entry.clearHistory};`, trace.categories.Navigation);
+ trace.write(`${this}._navigateCore(page: ${backstackEntry.resolvedPage}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry)}, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation);
var activity = this._android.activity;
if (!activity) {
@@ -195,7 +214,7 @@ export class Frame extends frameCommon.Frame {
var fragment: android.app.Fragment;
while (i >= 0) {
fragment = manager.findFragmentByTag(manager.getBackStackEntryAt(i--).getName());
- trace.write(`${fragment.toString()}[CLEARING_HISTORY] = true;`, trace.categories.NativeLifecycle);
+ trace.write(`${fragment.getTag()}[CLEARING_HISTORY] = true;`, trace.categories.NativeLifecycle);
fragment[CLEARING_HISTORY] = true;
}
@@ -204,7 +223,7 @@ export class Frame extends frameCommon.Frame {
fragment = manager.findFragmentByTag(this.currentPage[TAG]);
if (fragment) {
fragment[CLEARING_HISTORY] = true;
- trace.write(`${fragment.toString() }[CLEARING_HISTORY] = true;`, trace.categories.NativeLifecycle);
+ trace.write(`${fragment.getTag()}[CLEARING_HISTORY] = true;`, trace.categories.NativeLifecycle);
}
}
@@ -221,10 +240,27 @@ export class Frame extends frameCommon.Frame {
var 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;
ensureFragmentClass();
var newFragment = new FragmentClass();
+ var animated = this._getIsAnimatedNavigation(backstackEntry.entry);
+ var navigationTransition = this._getNavigationTransition(backstackEntry.entry);
+ if (currentFragment) {
+ // There might be transitions left over from previous forward navigations from the current page.
+ transitionModule._clearForwardTransitions(currentFragment);
+ }
+ if (animated && navigationTransition) {
+ transitionModule._setAndroidFragmentTransitions(navigationTransition, currentFragment, newFragment, fragmentTransaction);
+ }
+
newFragment.frame = this;
newFragment.entry = backstackEntry;
@@ -233,31 +269,27 @@ export class Frame extends frameCommon.Frame {
// remember the fragment tag at page level so that we can retrieve the fragment associated with a Page instance
backstackEntry.resolvedPage[TAG] = newFragmentTag;
-
- trace.write("Frame<" + this._domId + ">.fragmentTransaction PUSH depth = " + navDepth, trace.categories.Navigation);
if (this._isFirstNavigation) {
fragmentTransaction.add(this.containerViewId, newFragment, newFragmentTag);
- trace.write("fragmentTransaction.add(" + this.containerViewId + ", " + newFragment + ", " + newFragmentTag + ");", trace.categories.NativeLifecycle);
+ trace.write(`fragmentTransaction.add(${newFragmentTag});`, trace.categories.NativeLifecycle);
}
else {
if (this.android.cachePagesOnNavigate && !backstackEntry.entry.clearHistory) {
- var currentFragmentTag = this.currentPage[TAG];
- var currentFragment = manager.findFragmentByTag(currentFragmentTag);
if (currentFragment) {
fragmentTransaction.hide(currentFragment);
- trace.write("fragmentTransaction.hide(" + currentFragment + ");", trace.categories.NativeLifecycle);
+ trace.write(`fragmentTransaction.hide(${currentFragmentTag});`, trace.categories.NativeLifecycle);
}
else {
- trace.write("Could not find " + currentFragmentTag + " to hide", trace.categories.NativeLifecycle);
+ trace.write(`Could not find ${currentFragmentTag} to hide.`, trace.categories.NativeLifecycle);
}
fragmentTransaction.add(this.containerViewId, newFragment, newFragmentTag);
- trace.write("fragmentTransaction.add(" + this.containerViewId + ", " + newFragment + ", " + newFragmentTag + ");", trace.categories.NativeLifecycle);
+ trace.write(`fragmentTransaction.add(${newFragmentTag});`, trace.categories.NativeLifecycle);
}
else {
fragmentTransaction.replace(this.containerViewId, newFragment, newFragmentTag);
- trace.write("fragmentTransaction.replace(" + this.containerViewId + ", " + newFragment + ", " + newFragmentTag + ");", trace.categories.NativeLifecycle);
+ trace.write(`fragmentTransaction.replace(${newFragmentTag});`, trace.categories.NativeLifecycle);
}
// Add to backStack if needed.
@@ -265,13 +297,11 @@ export class Frame extends frameCommon.Frame {
// We add each entry in the backstack to avoid the "Stack corrupted" mismatch
var backstackTag = this._currentEntry[BACKSTACK_TAG];
fragmentTransaction.addToBackStack(backstackTag);
- trace.write("fragmentTransaction.addToBackStack(" + backstackTag + ");", trace.categories.NativeLifecycle);
+ trace.write(`fragmentTransaction.addToBackStack(${backstackTag});`, trace.categories.NativeLifecycle);
}
}
if (!this._isFirstNavigation) {
- var animated = this._getIsAnimatedNavigation(backstackEntry.entry);
-
if (this.android.cachePagesOnNavigate) {
// Apparently, there is an Android bug with when hiding fragments with animation.
// https://code.google.com/p/android/issues/detail?id=32405
@@ -279,18 +309,24 @@ export class Frame extends frameCommon.Frame {
fragmentTransaction.setTransition(android.app.FragmentTransaction.TRANSIT_NONE);
}
else {
- var transition = animated ? android.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN : android.app.FragmentTransaction.TRANSIT_NONE;
- fragmentTransaction.setTransition(transition);
+ var transit = animated ? android.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN : android.app.FragmentTransaction.TRANSIT_NONE;
+ fragmentTransaction.setTransition(transit);
}
}
fragmentTransaction.commit();
- trace.write("fragmentTransaction.commit();", trace.categories.NativeLifecycle);
+ trace.write(`fragmentTransaction.commit();`, trace.categories.NativeLifecycle);
}
public _goBackCore(backstackEntry: definition.BackstackEntry) {
navDepth = backstackEntry[NAV_DEPTH];
- trace.write("Frame<" + this._domId + ">.fragmentTransaction POP depth = " + navDepth, trace.categories.Navigation);
+
+ if (this._currentEntry) {
+ // We need this information inside onFragmentHidden
+ this._currentEntry[IS_BACK] = true;
+ }
+
+ 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) {
@@ -350,7 +386,7 @@ export class Frame extends frameCommon.Frame {
console.log("Fragment Manager Back Stack (" + length + ")");
while (i >= 0) {
var fragment = manager.findFragmentByTag(manager.getBackStackEntryAt(i--).getName());
- console.log("[ " + fragment.toString() + " ]");
+ console.log("[ " + fragment.getTag() + " ]");
}
}
@@ -392,7 +428,7 @@ var NativeActivity = {
},
onCreate: function (savedInstanceState: android.os.Bundle) {
- trace.write("NativeScriptActivity.onCreate(); savedInstanceState: " + savedInstanceState, trace.categories.NativeLifecycle);
+ trace.write(`NativeActivity.onCreate(${savedInstanceState})`, trace.categories.NativeLifecycle);
// Find the frame for this activity.
var frameId = this.getIntent().getExtras().getInt(INTENT_EXTRA);
@@ -433,7 +469,7 @@ var NativeActivity = {
onActivityResult: function (requestCode: number, resultCode: number, data: android.content.Intent) {
this.super.onActivityResult(requestCode, resultCode, data);
- trace.write("NativeScriptActivity.onActivityResult();", trace.categories.NativeLifecycle);
+ trace.write(`NativeActivity.onActivityResult(${requestCode}, ${resultCode}, ${data})`, trace.categories.NativeLifecycle);
var result = application.android.onActivityResult;
if (result) {
@@ -451,7 +487,7 @@ var NativeActivity = {
},
onAttachFragment: function (fragment: android.app.Fragment) {
- trace.write("NativeScriptActivity.onAttachFragment() : " + fragment.getTag(), trace.categories.NativeLifecycle);
+ trace.write(`NativeActivity.onAttachFragment(${fragment.getTag()})`, trace.categories.NativeLifecycle);
this.super.onAttachFragment(fragment);
if (!(fragment).entry) {
@@ -463,7 +499,7 @@ var NativeActivity = {
onStart: function () {
this.super.onStart();
- trace.write("NativeScriptActivity.onStart();", trace.categories.NativeLifecycle);
+ trace.write("NativeActivity.onStart()", trace.categories.NativeLifecycle);
if (!this.frame.isLoaded) {
this.frame.onLoaded();
}
@@ -471,11 +507,12 @@ var NativeActivity = {
onStop: function () {
this.super.onStop();
- trace.write("NativeScriptActivity.onStop();", trace.categories.NativeLifecycle);
+ trace.write("NativeActivity.onStop()", trace.categories.NativeLifecycle);
this.frame.onUnloaded();
},
onDestroy: function () {
+ trace.write("NativeActivity.onDestroy()", trace.categories.NativeLifecycle);
// TODO: Implement uninitialized(detached) routine
var frame = this.frame;
frame._onDetached(true);
@@ -488,10 +525,10 @@ var NativeActivity = {
this.androidFrame.reset();
this.super.onDestroy();
- trace.write("NativeScriptActivity.onDestroy();", trace.categories.NativeLifecycle);
},
onOptionsItemSelected: function (menuItem: android.view.IMenuItem) {
+ trace.write(`NativeActivity.onOptionsItemSelected(${menuItem})`, trace.categories.NativeLifecycle);
if (!this.androidFrame.hasListeners(frameCommon.Frame.androidOptionSelectedEvent)) {
return false;
}
@@ -508,7 +545,7 @@ var NativeActivity = {
},
onBackPressed: function () {
- trace.write("NativeScriptActivity.onBackPressed;", trace.categories.NativeLifecycle);
+ trace.write("NativeActivity.onBackPressed()", trace.categories.NativeLifecycle);
var args = {
eventName: "activityBackPressed",
@@ -528,6 +565,7 @@ var NativeActivity = {
},
onLowMemory: function () {
+ trace.write("NativeActivity.onLowMemory()", trace.categories.NativeLifecycle);
gc();
java.lang.System.gc();
this.super.onLowMemory();
@@ -536,6 +574,7 @@ var NativeActivity = {
},
onTrimMemory: function (level: number) {
+ trace.write(`NativeActivity.onTrimMemory(${level})`, trace.categories.NativeLifecycle);
gc();
java.lang.System.gc();
this.super.onTrimMemory(level);
@@ -688,26 +727,31 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) {
var page: pages.Page;
var entry: definition.BackstackEntry;
- trace.write("Attached fragment with no page: " + fragmentTag, trace.categories.NativeLifecycle);
+ trace.write(`Finding page for ${fragmentTag}.`, trace.categories.NativeLifecycle);
+ if (fragmentTag === (pages).DIALOG_FRAGMENT_TAG) {
+ trace.write(`No need to find page for dialog fragment.`, trace.categories.NativeLifecycle);
+ return;
+ }
+
if (frame.currentPage && frame.currentPage[TAG] === fragmentTag) {
page = frame.currentPage;
entry = frame._currentEntry;
- trace.write("Current page matches fragment: " + fragmentTag, trace.categories.NativeLifecycle);
+ trace.write(`Current page matches fragment ${fragmentTag}.`, trace.categories.NativeLifecycle);
}
else {
var backStack = frame.backStack;
for (var i = 0; i < backStack.length; i++) {
- entry = backStack[i];
if (backStack[i].resolvedPage[TAG] === fragmentTag) {
entry = backStack[i];
break;
}
}
if (entry) {
- trace.write("Found entry:" + entry + " for fragment: " + fragmentTag, trace.categories.NativeLifecycle);
page = entry.resolvedPage;
+ trace.write(`Found ${page} for ${fragmentTag}`, trace.categories.NativeLifecycle);
}
}
+
if (page) {
(fragment).frame = frame;
(fragment).entry = entry;
@@ -715,7 +759,7 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) {
page[TAG] = fragmentTag;
}
else {
- //throw new Error("Could not find Page for Fragment.");
+ //throw new Error(`Could not find a page for ${fragmentTag}.`);
}
}
diff --git a/ui/frame/frame.d.ts b/ui/frame/frame.d.ts
index 7c706800e..2b4a28490 100644
--- a/ui/frame/frame.d.ts
+++ b/ui/frame/frame.d.ts
@@ -73,11 +73,21 @@ declare module "ui/frame" {
*/
animated: boolean;
+ /**
+ * Gets or sets the default navigation transition for this frame.
+ */
+ navigationTransition: NavigationTransition;
+
/**
* Gets or sets if navigation transitions should be animated globally.
*/
static defaultAnimatedNavigation: boolean;
+ /**
+ * Gets or sets the default NavigationTransition for all frames across the app.
+ */
+ static defaultNavigationTransition: NavigationTransition;
+
/**
* Gets the AndroidFrame object that represents the Android-specific APIs for this Frame. Valid when running on Android OS.
*/
@@ -149,6 +159,11 @@ declare module "ui/frame" {
*/
animated?: boolean;
+ /**
+ * Specifies an optional navigation transition. If not specified, the default platform transition will be used.
+ */
+ navigationTransition?: NavigationTransition;
+
/**
* True to record the navigation in the backstack, false otherwise.
* If the parameter is set to false then the Page will be displayed but once navigated from it will not be able to be navigated back to.
@@ -161,6 +176,27 @@ declare module "ui/frame" {
clearHistory?: boolean;
}
+ /**
+ * Represents an object specifying a page navigation transition.
+ */
+ export interface NavigationTransition {
+ /**
+ * Either a string specifying one of the built-in transitions or an user-defined instance of the "ui/transition".Transition class.
+ */
+ transition: any;
+
+ /**
+ * The length of the transition in milliseconds. If you do not specify this, the default platform transition duration will be used.
+ */
+ duration?: number;
+
+ /**
+ * An optional transition animation curve. Possible values are contained in the [AnimationCurve enumeration](../enums/AnimationCurve/README.md).
+ * Alternatively, you can pass an instance of type UIViewAnimationCurve for iOS or android.animation.TimeInterpolator for Android.
+ */
+ curve?: any;
+ }
+
/**
* Represents an entry in the back stack of a Frame object.
*/
diff --git a/ui/frame/frame.ios.ts b/ui/frame/frame.ios.ts
index 0c7709a07..7215487b6 100644
--- a/ui/frame/frame.ios.ts
+++ b/ui/frame/frame.ios.ts
@@ -7,12 +7,14 @@ import utils = require("utils/utils");
import view = require("ui/core/view");
import uiUtils = require("ui/utils");
import * as types from "utils/types";
+import * as animationModule from "ui/animation";
+import * as transitionModule from "ui/transition";
global.moduleMerge(frameCommon, exports);
var ENTRY = "_entry";
var NAV_DEPTH = "_navDepth";
-
+var TRANSITION = "_transition";
var navDepth = -1;
export class Frame extends frameCommon.Frame {
@@ -50,6 +52,7 @@ export class Frame extends frameCommon.Frame {
}
public _navigateCore(backstackEntry: definition.BackstackEntry) {
+ trace.write(`${this}._navigateCore(pageId: ${backstackEntry.resolvedPage.id}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry) }, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation);
var viewController: UIViewController = backstackEntry.resolvedPage.ios;
if (!viewController) {
throw new Error("Required page does not have a viewController created.");
@@ -57,9 +60,10 @@ export class Frame extends frameCommon.Frame {
navDepth++;
- var animated = false;
- if (this.currentPage) {
- animated = this._getIsAnimatedNavigation(backstackEntry.entry);
+ var animated = this.currentPage ? this._getIsAnimatedNavigation(backstackEntry.entry) : false;
+ var navigationTransition = this._getNavigationTransition(backstackEntry.entry);
+ if (animated && navigationTransition) {
+ viewController[TRANSITION] = navigationTransition;
}
backstackEntry[NAV_DEPTH] = navDepth;
@@ -70,7 +74,7 @@ export class Frame extends frameCommon.Frame {
// First navigation.
if (!this._currentEntry) {
this._ios.controller.pushViewControllerAnimated(viewController, animated);
- trace.write("Frame<" + this._domId + ">.pushViewControllerAnimated(newController) depth = " + navDepth, trace.categories.Navigation);
+ trace.write(`${this}.pushViewControllerAnimated(${viewController}, ${animated}); depth = ${navDepth}`, trace.categories.Navigation);
return;
}
@@ -80,7 +84,7 @@ export class Frame extends frameCommon.Frame {
var newControllers = NSMutableArray.alloc().initWithCapacity(1);
newControllers.addObject(viewController);
this._ios.controller.setViewControllersAnimated(newControllers, animated);
- trace.write("Frame<" + this._domId + ">.setViewControllersAnimated([newController]) depth = " + navDepth, trace.categories.Navigation);
+ trace.write(`${this}.setViewControllersAnimated([${viewController}], ${animated}); depth = ${navDepth}`, trace.categories.Navigation);
return;
}
@@ -102,24 +106,25 @@ export class Frame extends frameCommon.Frame {
// replace the controllers instead of pushing directly
this._ios.controller.setViewControllersAnimated(newControllers, animated);
- trace.write("Frame<" + this._domId + ">.setViewControllersAnimated([originalControllers - lastController + newController]) depth = " + navDepth, trace.categories.Navigation);
+ trace.write(`${this}.setViewControllersAnimated([originalControllers - lastController + ${viewController}], ${animated}); depth = ${navDepth}`, trace.categories.Navigation);
return;
}
// General case.
this._ios.controller.pushViewControllerAnimated(viewController, animated);
- trace.write("Frame<" + this._domId + ">.pushViewControllerAnimated(newController) depth = " + navDepth, trace.categories.Navigation);
+ trace.write(`${this}.pushViewControllerAnimated(${viewController}, ${animated}); depth = ${navDepth}`, trace.categories.Navigation);
}
public _goBackCore(backstackEntry: definition.BackstackEntry) {
navDepth = backstackEntry[NAV_DEPTH];
- trace.write("Frame<" + this._domId + ">.popViewControllerAnimated depth = " + navDepth, trace.categories.Navigation);
+ trace.write(`${this}._goBackCore(pageId: ${backstackEntry.resolvedPage.id}, backstackVisible: ${this._isEntryBackstackVisible(backstackEntry) }, clearHistory: ${backstackEntry.entry.clearHistory}), navDepth: ${navDepth}`, trace.categories.Navigation);
if (!this._shouldSkipNativePop) {
var controller = backstackEntry.resolvedPage.ios;
- var animated = this._getIsAnimatedNavigation(backstackEntry.entry);
+ var animated = this._currentEntry ? this._getIsAnimatedNavigation(this._currentEntry.entry) : false;
this._updateActionBar(backstackEntry.resolvedPage);
+ trace.write(`${this}.popToViewControllerAnimated(${controller}, ${animated}); depth = ${navDepth}`, trace.categories.Navigation);
this._ios.controller.popToViewControllerAnimated(controller, animated);
}
}
@@ -174,6 +179,13 @@ export class Frame extends frameCommon.Frame {
frameCommon.Frame.defaultAnimatedNavigation = value;
}
+ public static get defaultNavigationTransition(): definition.NavigationTransition {
+ return frameCommon.Frame.defaultNavigationTransition;
+ }
+ public static set defaultNavigationTransition(value: definition.NavigationTransition) {
+ frameCommon.Frame.defaultNavigationTransition = value;
+ }
+
public requestLayout(): void {
super.requestLayout();
// Invalidate our Window so that layout is triggered again.
@@ -265,14 +277,58 @@ export class Frame extends frameCommon.Frame {
}
}
+class TransitionDelegate extends NSObject {
+ static new(): TransitionDelegate {
+ return super.new();
+ }
+
+ private _owner: UINavigationControllerImpl;
+ private _id: string;
+
+ public initWithOwnerId(owner: UINavigationControllerImpl, id: string): TransitionDelegate {
+ this._owner = owner;
+ this._owner.transitionDelegates.push(this);
+ this._id = id;
+ return this;
+ }
+
+ public animationWillStart(animationID: string, context: any): void {
+ trace.write(`START ${this._id}`, trace.categories.Transition);
+ }
+
+ public animationDidStop(animationID: string, finished: boolean, context: any): void {
+ if (finished) {
+ trace.write(`END ${this._id}`, trace.categories.Transition);
+ }
+ else {
+ trace.write(`CANCEL ${this._id}`, trace.categories.Transition);
+ }
+
+ if (this._owner) {
+ var index = this._owner.transitionDelegates.indexOf(this);
+ if (index > -1) {
+ this._owner.transitionDelegates.splice(index, 1);
+ }
+ }
+ }
+
+ public static ObjCExposedMethods = {
+ "animationWillStart": { returns: interop.types.void, params: [NSString, NSObject] },
+ "animationDidStop": { returns: interop.types.void, params: [NSString, NSNumber, NSObject] }
+ };
+}
+
+var _defaultTransitionDuration = 0.35;
class UINavigationControllerImpl extends UINavigationController implements UINavigationControllerDelegate {
public static ObjCProtocols = [UINavigationControllerDelegate];
private _owner: WeakRef;
+ private _transitionDelegates: Array;
public static initWithOwner(owner: WeakRef): UINavigationControllerImpl {
var controller = UINavigationControllerImpl.new();
controller._owner = owner;
+ controller._transitionDelegates = new Array();
return controller;
}
@@ -280,6 +336,10 @@ class UINavigationControllerImpl extends UINavigationController implements UINav
return this._owner.get();
}
+ get transitionDelegates(): Array {
+ return this._transitionDelegates;
+ }
+
public viewDidLoad(): void {
let owner = this._owner.get();
if (owner) {
@@ -332,12 +392,6 @@ class UINavigationControllerImpl extends UINavigationController implements UINav
let newEntry: definition.BackstackEntry = viewController[ENTRY];
let newPage = newEntry.resolvedPage;
- // For some reason iOS calls navigationControllerDidShowViewControllerAnimated twice for the
- // main-page resulting in double 'loaded' and 'navigatedTo' events being fired.
- if (!(newPage)._delayLoadedEvent) {
- return;
- }
-
let backStack = frame.backStack;
let currentEntry = backStack.length > 0 ? backStack[backStack.length - 1] : null;
@@ -374,14 +428,6 @@ class UINavigationControllerImpl extends UINavigationController implements UINav
frame._navigateToEntry = null;
frame._currentEntry = newEntry;
frame.remeasureFrame();
-
- // In iOS we intentionally delay the raising of the 'loaded' event so both platforms behave identically.
- // The loaded event must be raised AFTER the page is part of the windows hierarchy and
- // frame.topmost().currentPage is set to the page instance.
- // https://github.com/NativeScript/NativeScript/issues/779
- (newPage)._delayLoadedEvent = false;
- newPage._emit(view.View.loadedEvent);
-
frame._updateActionBar(newPage);
// notify the page
@@ -392,6 +438,187 @@ class UINavigationControllerImpl extends UINavigationController implements UINav
public supportedInterfaceOrientation(): number {
return UIInterfaceOrientationMask.UIInterfaceOrientationMaskAll;
}
+
+ public pushViewControllerAnimated(viewController: UIViewController, animated: boolean): void {
+ var navigationTransition = viewController[TRANSITION];
+ trace.write(`UINavigationControllerImpl.pushViewControllerAnimated(${viewController}, ${animated}); transition: ${JSON.stringify(navigationTransition)}`, trace.categories.NativeLifecycle);
+
+ if (!animated || !navigationTransition) {
+ super.pushViewControllerAnimated(viewController, animated);
+ return;
+ }
+
+ var nativeTransition = _getNativeTransition(navigationTransition, true);
+ if (!nativeTransition) {
+ super.pushViewControllerAnimated(viewController, animated);
+ return;
+ }
+
+ var duration = navigationTransition.duration ? navigationTransition.duration / 1000 : _defaultTransitionDuration;
+ var curve = _getNativeCurve(navigationTransition);
+ var id = _getTransitionId(nativeTransition, "push");
+ var transitionDelegate = TransitionDelegate.new().initWithOwnerId(this, id);
+ UIView.animateWithDurationAnimations(duration, () => {
+ UIView.setAnimationDelegate(transitionDelegate);
+ UIView.setAnimationWillStartSelector("animationWillStart");
+ UIView.setAnimationDidStopSelector("animationDidStop");
+ UIView.setAnimationCurve(curve);
+ super.pushViewControllerAnimated(viewController, false);
+ UIView.setAnimationTransitionForViewCache(nativeTransition, this.view, true);
+ });
+ }
+
+ public setViewControllersAnimated(viewControllers: NSArray, animated: boolean): void {
+ var viewController = viewControllers.lastObject;
+ var navigationTransition = viewController[TRANSITION];
+ trace.write(`UINavigationControllerImpl.setViewControllersAnimated(${viewControllers}, ${animated}); transition: ${JSON.stringify(navigationTransition)}`, trace.categories.NativeLifecycle);
+
+ if (!animated || !navigationTransition) {
+ super.setViewControllersAnimated(viewControllers, animated);
+ return;
+ }
+
+ var nativeTransition = _getNativeTransition(navigationTransition, true);
+ if (!nativeTransition) {
+ super.setViewControllersAnimated(viewControllers, animated);
+ return;
+ }
+
+ var duration = navigationTransition.duration ? navigationTransition.duration / 1000 : _defaultTransitionDuration;
+ var curve = _getNativeCurve(navigationTransition);
+ var id = _getTransitionId(nativeTransition, "set");
+ var transitionDelegate = TransitionDelegate.new().initWithOwnerId(this, id);
+ UIView.animateWithDurationAnimations(duration, () => {
+ UIView.setAnimationDelegate(transitionDelegate);
+ UIView.setAnimationWillStartSelector("animationWillStart");
+ UIView.setAnimationDidStopSelector("animationDidStop");
+ UIView.setAnimationCurve(curve);
+ super.setViewControllersAnimated(viewControllers, false);
+ UIView.setAnimationTransitionForViewCache(nativeTransition, this.view, true);
+ });
+ }
+
+ public popViewControllerAnimated(animated: boolean): UIViewController {
+ var lastViewController = this.viewControllers.lastObject;
+ var navigationTransition = lastViewController[TRANSITION];
+ trace.write(`UINavigationControllerImpl.popViewControllerAnimated(${animated}); transition: ${JSON.stringify(navigationTransition)}`, trace.categories.NativeLifecycle);
+
+ if (!animated || !navigationTransition) {
+ return super.popViewControllerAnimated(animated);
+ }
+
+ var nativeTransition = _getNativeTransition(navigationTransition, false);
+ if (!nativeTransition) {
+ return super.popViewControllerAnimated(animated);
+ }
+
+ var duration = navigationTransition.duration ? navigationTransition.duration / 1000 : _defaultTransitionDuration;
+ var curve = _getNativeCurve(navigationTransition);
+ var id = _getTransitionId(nativeTransition, "pop");
+ var transitionDelegate = TransitionDelegate.new().initWithOwnerId(this, id);
+ UIView.animateWithDurationAnimations(duration, () => {
+ UIView.setAnimationDelegate(transitionDelegate);
+ UIView.setAnimationWillStartSelector("animationWillStart");
+ UIView.setAnimationDidStopSelector("animationDidStop");
+ UIView.setAnimationCurve(curve);
+ super.popViewControllerAnimated(false);
+ UIView.setAnimationTransitionForViewCache(nativeTransition, this.view, true);
+ });
+ return null;
+ }
+
+ public popToViewControllerAnimated(viewController: UIViewController, animated: boolean): NSArray {
+ var lastViewController = this.viewControllers.lastObject;
+ var navigationTransition = lastViewController[TRANSITION];
+ trace.write(`UINavigationControllerImpl.popToViewControllerAnimated(${viewController}, ${animated}); transition: ${JSON.stringify(navigationTransition)}`, trace.categories.NativeLifecycle);
+ if (!animated || !navigationTransition) {
+ return super.popToViewControllerAnimated(viewController, animated);
+ }
+
+ var nativeTransition = _getNativeTransition(navigationTransition, false);
+ if (!nativeTransition) {
+ return super.popToViewControllerAnimated(viewController, animated);
+ }
+
+ var duration = navigationTransition.duration ? navigationTransition.duration / 1000 : _defaultTransitionDuration;
+ var curve = _getNativeCurve(navigationTransition);
+ var id = _getTransitionId(nativeTransition, "popTo");
+ var transitionDelegate = TransitionDelegate.new().initWithOwnerId(this, id);
+ UIView.animateWithDurationAnimations(duration, () => {
+ UIView.setAnimationDelegate(transitionDelegate);
+ UIView.setAnimationWillStartSelector("animationWillStart");
+ UIView.setAnimationDidStopSelector("animationDidStop");
+ UIView.setAnimationCurve(curve);
+ super.popToViewControllerAnimated(viewController, false);
+ UIView.setAnimationTransitionForViewCache(nativeTransition, this.view, true);
+ });
+ return null;
+ }
+
+ public navigationControllerAnimationControllerForOperationFromViewControllerToViewController(navigationController: UINavigationController, operation: number, fromVC: UIViewController, toVC: UIViewController): UIViewControllerAnimatedTransitioning {
+ var viewController: UIViewController;
+ switch (operation) {
+ case UINavigationControllerOperation.UINavigationControllerOperationPush:
+ viewController = toVC;
+ break;
+ case UINavigationControllerOperation.UINavigationControllerOperationPop:
+ viewController = fromVC;
+ break;
+ }
+
+ if (!viewController) {
+ return null;
+ }
+
+ var navigationTransition = viewController[TRANSITION];
+ if (!navigationTransition) {
+ return null;
+ }
+
+ trace.write(`UINavigationControllerImpl.navigationControllerAnimationControllerForOperationFromViewControllerToViewController(${operation}, ${fromVC}, ${toVC}), transition: ${JSON.stringify(navigationTransition)}`, trace.categories.NativeLifecycle);
+ var _transitionModule: typeof transitionModule = require("ui/transition");
+ return _transitionModule._createIOSAnimatedTransitioning(navigationTransition, operation, fromVC, toVC);
+ }
+}
+
+function _getTransitionId(nativeTransition: UIViewAnimationTransition, transitionType: string): string {
+ var name;
+ switch (nativeTransition) {
+ case UIViewAnimationTransition.UIViewAnimationTransitionCurlDown: name = "CurlDown"; break;
+ case UIViewAnimationTransition.UIViewAnimationTransitionCurlUp: name = "CurlUp"; break;
+ case UIViewAnimationTransition.UIViewAnimationTransitionFlipFromLeft: name = "FlipFromLeft"; break;
+ case UIViewAnimationTransition.UIViewAnimationTransitionFlipFromRight: name = "FlipFromRight"; break;
+ case UIViewAnimationTransition.UIViewAnimationTransitionNone: name = "None"; break;
+ }
+
+ return `${name} ${transitionType}`;
+}
+
+function _getNativeTransition(navigationTransition: definition.NavigationTransition, push: boolean): UIViewAnimationTransition {
+ if (types.isString(navigationTransition.transition)) {
+ switch (navigationTransition.transition.toLowerCase()) {
+ case "flip":
+ case "flipright":
+ return push ? UIViewAnimationTransition.UIViewAnimationTransitionFlipFromRight : UIViewAnimationTransition.UIViewAnimationTransitionFlipFromLeft;
+ case "flipleft":
+ return push ? UIViewAnimationTransition.UIViewAnimationTransitionFlipFromLeft : UIViewAnimationTransition.UIViewAnimationTransitionFlipFromRight;
+ case "curl":
+ case "curlup":
+ return push ? UIViewAnimationTransition.UIViewAnimationTransitionCurlUp : UIViewAnimationTransition.UIViewAnimationTransitionCurlDown;
+ case "curldown":
+ return push ? UIViewAnimationTransition.UIViewAnimationTransitionCurlDown : UIViewAnimationTransition.UIViewAnimationTransitionCurlUp;
+ }
+ }
+ return null;
+}
+
+function _getNativeCurve(transition: definition.NavigationTransition) : UIViewAnimationCurve{
+ if (transition.curve) {
+ var animation: typeof animationModule = require("ui/animation");
+ return animation._resolveAnimationCurve(transition.curve);
+ }
+
+ return UIViewAnimationCurve.UIViewAnimationCurveEaseInOut;
}
/* tslint:disable */
diff --git a/ui/page/page.android.ts b/ui/page/page.android.ts
index 827c6d672..09b5ca2d5 100644
--- a/ui/page/page.android.ts
+++ b/ui/page/page.android.ts
@@ -23,6 +23,8 @@ function ensureColor() {
}
}
+export var DIALOG_FRAGMENT_TAG = "dialog";
+
var DialogFragmentClass;
function ensureDialogFragmentClass() {
if (DialogFragmentClass) {
@@ -120,7 +122,7 @@ export class Page extends pageCommon.Page {
if (skipDetached) {
ensureTrace();
// Do not detach the context and android reference.
- trace.write("Caching Page " + this._domId, trace.categories.NativeLifecycle);
+ trace.write(`Caching ${this}`, trace.categories.NativeLifecycle);
}
else {
super._onDetached();
@@ -153,7 +155,7 @@ export class Page extends pageCommon.Page {
});
super._raiseShowingModallyEvent();
- this._dialogFragment.show(parent.frame.android.activity.getFragmentManager(), "dialog");
+ this._dialogFragment.show(parent.frame.android.activity.getFragmentManager(), DIALOG_FRAGMENT_TAG);
super._raiseShownModallyEvent(parent, context, closeCallback);
}
diff --git a/ui/page/page.ios.ts b/ui/page/page.ios.ts
index 1af9e86e2..e7eb31034 100644
--- a/ui/page/page.ios.ts
+++ b/ui/page/page.ios.ts
@@ -98,15 +98,6 @@ class UIViewControllerImpl extends UIViewController {
//https://github.com/NativeScript/NativeScript/issues/1201
owner._viewWillDisappear = false;
-
- // In iOS we intentionally delay the raising of the 'loaded' event so both platforms behave identically.
- // The loaded event must be raised AFTER the page is part of the windows hierarchy and
- // frame.topmost().currentPage is set to the page instance.
- // https://github.com/NativeScript/NativeScript/issues/779
- if (!owner._isModal) {
- owner._delayLoadedEvent = true;
- }
-
owner.onLoaded();
owner._enableLoadedEvents = false;
}
@@ -145,7 +136,6 @@ export class Page extends pageCommon.Page {
public _enableLoadedEvents: boolean;
public _isModal: boolean;
public _UIModalPresentationFormSheet: boolean;
- public _delayLoadedEvent: boolean;
public _viewWillDisappear: boolean;
constructor(options?: definition.Options) {
@@ -174,18 +164,6 @@ export class Page extends pageCommon.Page {
this._updateActionBar(false);
}
- public notify(data: T) {
- // In iOS we intentionally delay the raising of the 'loaded' event so both platforms behave identically.
- // The loaded event must be raised AFTER the page is part of the windows hierarchy and
- // frame.topmost().currentPage is set to the page instance.
- // https://github.com/NativeScript/NativeScript/issues/779
- if (data.eventName === View.loadedEvent && this._delayLoadedEvent) {
- return;
- }
-
- super.notify(data);
- }
-
public onUnloaded() {
// loaded/unloaded events are handled in page viewWillAppear/viewDidDisappear
if (this._enableLoadedEvents) {
diff --git a/ui/transition/fade-transition.android.ts b/ui/transition/fade-transition.android.ts
new file mode 100644
index 000000000..186ccd59a
--- /dev/null
+++ b/ui/transition/fade-transition.android.ts
@@ -0,0 +1,29 @@
+import transition = require("ui/transition");
+import platform = require("platform");
+
+var floatType = java.lang.Float.class.getField("TYPE").get(null);
+
+export class FadeTransition extends transition.Transition {
+ public createAndroidAnimator(transitionType: string): android.animation.Animator {
+ var alphaValues = java.lang.reflect.Array.newInstance(floatType, 2);
+ switch (transitionType) {
+ case transition.AndroidTransitionType.enter:
+ case transition.AndroidTransitionType.popEnter:
+ alphaValues[0] = 0;
+ alphaValues[1] = 1;
+ break;
+ case transition.AndroidTransitionType.exit:
+ case transition.AndroidTransitionType.popExit:
+ alphaValues[0] = 1;
+ alphaValues[1] = 0;
+ break;
+ }
+ var animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", alphaValues);
+ var duration = this.getDuration();
+ if (duration !== undefined) {
+ animator.setDuration(duration);
+ }
+ animator.setInterpolator(this.getCurve());
+ return animator;
+ }
+}
\ No newline at end of file
diff --git a/ui/transition/fade-transition.ios.ts b/ui/transition/fade-transition.ios.ts
new file mode 100644
index 000000000..2a391dccf
--- /dev/null
+++ b/ui/transition/fade-transition.ios.ts
@@ -0,0 +1,25 @@
+import transition = require("ui/transition");
+
+export class FadeTransition extends transition.Transition {
+ public animateIOSTransition(containerView: UIView, fromView: UIView, toView: UIView, operation: UINavigationControllerOperation, completion: (finished: boolean) => void): void {
+ toView.alpha = 0.0;
+ fromView.alpha = 1.0;
+
+ switch (operation) {
+ case UINavigationControllerOperation.UINavigationControllerOperationPush:
+ containerView.insertSubviewAboveSubview(toView, fromView);
+ break;
+ case UINavigationControllerOperation.UINavigationControllerOperationPop:
+ containerView.insertSubviewBelowSubview(toView, fromView);
+ break;
+ }
+
+ var duration = this.getDuration();
+ var curve = this.getCurve();
+ UIView.animateWithDurationAnimationsCompletion(duration, () => {
+ UIView.setAnimationCurve(curve);
+ toView.alpha = 1.0;
+ fromView.alpha = 0.0;
+ }, completion);
+ }
+}
\ No newline at end of file
diff --git a/ui/transition/flip-transition.android.ts b/ui/transition/flip-transition.android.ts
new file mode 100644
index 000000000..4f3966d65
--- /dev/null
+++ b/ui/transition/flip-transition.android.ts
@@ -0,0 +1,120 @@
+import transition = require("ui/transition");
+import platform = require("platform");
+
+var floatType = java.lang.Float.class.getField("TYPE").get(null);
+
+//http://developer.android.com/training/animation/cardflip.html
+export class FlipTransition extends transition.Transition {
+ private _direction: string;
+
+ constructor(direction: string, duration: number, curve: any) {
+ super(duration, curve);
+ this._direction = direction;
+ }
+
+ public createAndroidAnimator(transitionType: string): android.animation.Animator {
+ var objectAnimators;
+ var values;
+ var animator: android.animation.ObjectAnimator;
+ var animatorSet = new android.animation.AnimatorSet();
+ var fullDuration = this.getDuration() || 300;
+ var interpolator = this.getCurve();
+ var rotationY = this._direction === "right" ? 180 : -180;
+
+ switch (transitionType) {
+ case transition.AndroidTransitionType.enter: // card_flip_right_in
+ objectAnimators = java.lang.reflect.Array.newInstance(android.animation.Animator.class, 3);
+
+ values = java.lang.reflect.Array.newInstance(floatType, 2);
+ values[0] = 1.0;
+ values[1] = 0.0;
+ animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
+ animator.setDuration(0);
+ objectAnimators[0] = animator;
+
+ values = java.lang.reflect.Array.newInstance(floatType, 2);
+ values[0] = rotationY;
+ values[1] = 0.0;
+ animator = android.animation.ObjectAnimator.ofFloat(null, "rotationY", values);
+ animator.setInterpolator(interpolator);
+ animator.setDuration(fullDuration);
+ objectAnimators[1] = animator;
+
+ values = java.lang.reflect.Array.newInstance(floatType, 2);
+ values[0] = 0.0;
+ values[1] = 1.0;
+ animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
+ animator.setStartDelay(fullDuration / 2);
+ animator.setDuration(1);
+ objectAnimators[2] = animator;
+ break;
+ case transition.AndroidTransitionType.exit: // card_flip_right_out
+ objectAnimators = java.lang.reflect.Array.newInstance(android.animation.Animator.class, 2);
+
+ values = java.lang.reflect.Array.newInstance(floatType, 2);
+ values[0] = 0.0;
+ values[1] = -rotationY;
+ animator = android.animation.ObjectAnimator.ofFloat(null, "rotationY", values);
+ animator.setInterpolator(interpolator);
+ animator.setDuration(fullDuration);
+ objectAnimators[0] = animator;
+
+ values = java.lang.reflect.Array.newInstance(floatType, 2);
+ values[0] = 1.0;
+ values[1] = 0.0;
+ animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
+ animator.setStartDelay(fullDuration / 2);
+ animator.setDuration(1);
+ objectAnimators[1] = animator;
+ break;
+ case transition.AndroidTransitionType.popEnter: // card_flip_left_in
+ objectAnimators = java.lang.reflect.Array.newInstance(android.animation.Animator.class, 3);
+
+ values = java.lang.reflect.Array.newInstance(floatType, 2);
+ values[0] = 1.0;
+ values[1] = 0.0;
+ animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
+ animator.setDuration(0);
+ objectAnimators[0] = animator;
+
+ values = java.lang.reflect.Array.newInstance(floatType, 2);
+ values[0] = -rotationY;
+ values[1] = 0.0;
+ animator = android.animation.ObjectAnimator.ofFloat(null, "rotationY", values);
+ animator.setInterpolator(interpolator);
+ animator.setDuration(fullDuration);
+ objectAnimators[1] = animator;
+
+ values = java.lang.reflect.Array.newInstance(floatType, 2);
+ values[0] = 0.0;
+ values[1] = 1.0;
+ animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
+ animator.setStartDelay(fullDuration / 2);
+ animator.setDuration(1);
+ objectAnimators[2] = animator;
+ break;
+ case transition.AndroidTransitionType.popExit: // card_flip_left_out
+ objectAnimators = java.lang.reflect.Array.newInstance(android.animation.Animator.class, 2);
+
+ values = java.lang.reflect.Array.newInstance(floatType, 2);
+ values[0] = 0.0;
+ values[1] = rotationY;
+ animator = android.animation.ObjectAnimator.ofFloat(null, "rotationY", values);
+ animator.setInterpolator(interpolator);
+ animator.setDuration(fullDuration);
+ objectAnimators[0] = animator;
+
+ values = java.lang.reflect.Array.newInstance(floatType, 2);
+ values[0] = 1.0;
+ values[1] = 0.0;
+ animator = android.animation.ObjectAnimator.ofFloat(null, "alpha", values);
+ animator.setStartDelay(fullDuration / 2);
+ animator.setDuration(1);
+ objectAnimators[1] = animator;
+ break;
+ }
+
+ animatorSet.playTogether(objectAnimators);
+ return animatorSet;
+ }
+}
\ No newline at end of file
diff --git a/ui/transition/package.json b/ui/transition/package.json
new file mode 100644
index 000000000..fa8e316f8
--- /dev/null
+++ b/ui/transition/package.json
@@ -0,0 +1,2 @@
+{ "name" : "transition",
+ "main" : "transition.js" }
diff --git a/ui/transition/slide-transition.android.ts b/ui/transition/slide-transition.android.ts
new file mode 100644
index 000000000..c054317b2
--- /dev/null
+++ b/ui/transition/slide-transition.android.ts
@@ -0,0 +1,116 @@
+import transition = require("ui/transition");
+import platform = require("platform");
+
+var floatType = java.lang.Float.class.getField("TYPE").get(null);
+var screenWidth = platform.screen.mainScreen.widthPixels;
+var screenHeight = platform.screen.mainScreen.heightPixels;
+
+export class SlideTransition extends transition.Transition {
+ private _direction: string;
+
+ constructor(direction: string, duration: number, curve: any) {
+ super(duration, curve);
+ this._direction = direction;
+ }
+
+ public createAndroidAnimator(transitionType: string): android.animation.Animator {
+ var translationValues = java.lang.reflect.Array.newInstance(floatType, 2);
+ switch (this._direction) {
+ case "left":
+ switch (transitionType) {
+ case transition.AndroidTransitionType.enter:
+ translationValues[0] = screenWidth;
+ translationValues[1] = 0;
+ break;
+ case transition.AndroidTransitionType.exit:
+ translationValues[0] = 0;
+ translationValues[1] = -screenWidth;
+ break;
+ case transition.AndroidTransitionType.popEnter:
+ translationValues[0] = -screenWidth;
+ translationValues[1] = 0;
+ break;
+ case transition.AndroidTransitionType.popExit:
+ translationValues[0] = 0;
+ translationValues[1] = screenWidth;
+ break;
+ }
+ break;
+ case "right":
+ switch (transitionType) {
+ case transition.AndroidTransitionType.enter:
+ translationValues[0] = -screenWidth;
+ translationValues[1] = 0;
+ break;
+ case transition.AndroidTransitionType.exit:
+ translationValues[0] = 0;
+ translationValues[1] = screenWidth;
+ break;
+ case transition.AndroidTransitionType.popEnter:
+ translationValues[0] = screenWidth;
+ translationValues[1] = 0;
+ break;
+ case transition.AndroidTransitionType.popExit:
+ translationValues[0] = 0;
+ translationValues[1] = -screenWidth;
+ break;
+ }
+ break;
+ case "top":
+ switch (transitionType) {
+ case transition.AndroidTransitionType.enter:
+ translationValues[0] = screenHeight;
+ translationValues[1] = 0;
+ break;
+ case transition.AndroidTransitionType.exit:
+ translationValues[0] = 0;
+ translationValues[1] = -screenHeight;
+ break;
+ case transition.AndroidTransitionType.popEnter:
+ translationValues[0] = -screenHeight;
+ translationValues[1] = 0;
+ break;
+ case transition.AndroidTransitionType.popExit:
+ translationValues[0] = 0;
+ translationValues[1] = screenHeight;
+ break;
+ }
+ break;
+ case "bottom":
+ switch (transitionType) {
+ case transition.AndroidTransitionType.enter:
+ translationValues[0] = -screenHeight;
+ translationValues[1] = 0;
+ break;
+ case transition.AndroidTransitionType.exit:
+ translationValues[0] = 0;
+ translationValues[1] = screenHeight;
+ break;
+ case transition.AndroidTransitionType.popEnter:
+ translationValues[0] = screenHeight;
+ translationValues[1] = 0;
+ break;
+ case transition.AndroidTransitionType.popExit:
+ translationValues[0] = 0;
+ translationValues[1] = -screenHeight;
+ break;
+ }
+ break;
+ }
+ var prop;
+ if (this._direction === "left" || this._direction === "right") {
+ prop = "translationX";
+ }
+ else {
+ prop = "translationY";
+ }
+
+ var animator = android.animation.ObjectAnimator.ofFloat(null, prop, translationValues);
+ var duration = this.getDuration();
+ if (duration !== undefined) {
+ animator.setDuration(duration);
+ }
+ animator.setInterpolator(this.getCurve());
+ return animator;
+ }
+}
\ No newline at end of file
diff --git a/ui/transition/slide-transition.ios.ts b/ui/transition/slide-transition.ios.ts
new file mode 100644
index 000000000..a06f4e895
--- /dev/null
+++ b/ui/transition/slide-transition.ios.ts
@@ -0,0 +1,64 @@
+import transition = require("ui/transition");
+import platform = require("platform");
+
+var screenWidth = platform.screen.mainScreen.widthDIPs;
+var screenHeight = platform.screen.mainScreen.heightDIPs;
+var leftEdge = CGAffineTransformMakeTranslation(-screenWidth, 0);
+var rightEdge = CGAffineTransformMakeTranslation(screenWidth, 0);
+var topEdge = CGAffineTransformMakeTranslation(0, -screenHeight);
+var bottomEdge = CGAffineTransformMakeTranslation(0, screenHeight);
+
+export class SlideTransition extends transition.Transition {
+ private _direction: string;
+
+ constructor(direction: string, duration: number, curve: any) {
+ super(duration, curve);
+ this._direction = direction;
+ }
+
+ public animateIOSTransition(containerView: UIView, fromView: UIView, toView: UIView, operation: UINavigationControllerOperation, completion: (finished: boolean) => void): void {
+ var fromViewEndTransform: CGAffineTransform;
+ var toViewBeginTransform: CGAffineTransform;
+ var push = (operation === UINavigationControllerOperation.UINavigationControllerOperationPush);
+
+ switch (this._direction) {
+ case "left":
+ toViewBeginTransform = push ? rightEdge : leftEdge;
+ fromViewEndTransform = push ? leftEdge : rightEdge;
+ break;
+ case "right":
+ toViewBeginTransform = push ? leftEdge : rightEdge;
+ fromViewEndTransform = push ? rightEdge : leftEdge;
+ break;
+ case "top":
+ toViewBeginTransform = push ? bottomEdge : topEdge;
+ fromViewEndTransform = push ? topEdge : bottomEdge;
+ break;
+ case "bottom":
+ toViewBeginTransform = push ? topEdge : bottomEdge;
+ fromViewEndTransform = push ? bottomEdge : topEdge;
+ break;
+ }
+
+ var originalToViewTransform = toView.transform;
+ toView.transform = toViewBeginTransform;
+ //fromView.transform = CGAffineTransformIdentity;
+
+ switch (operation) {
+ case UINavigationControllerOperation.UINavigationControllerOperationPush:
+ containerView.insertSubviewAboveSubview(toView, fromView);
+ break;
+ case UINavigationControllerOperation.UINavigationControllerOperationPop:
+ containerView.insertSubviewBelowSubview(toView, fromView);
+ break;
+ }
+
+ var duration = this.getDuration();
+ var curve = this.getCurve();
+ UIView.animateWithDurationAnimationsCompletion(duration, () => {
+ UIView.setAnimationCurve(curve);
+ toView.transform = originalToViewTransform;
+ fromView.transform = fromViewEndTransform;
+ }, completion);
+ }
+}
\ No newline at end of file
diff --git a/ui/transition/transition.android.ts b/ui/transition/transition.android.ts
new file mode 100644
index 000000000..3eeed0e78
--- /dev/null
+++ b/ui/transition/transition.android.ts
@@ -0,0 +1,395 @@
+import definition = require("ui/transition");
+import platform = require("platform");
+import frameModule = require("ui/frame");
+import pageModule = require("ui/page");
+import * as animationModule from "ui/animation";
+import types = require("utils/types");
+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 enterFakeResourceId = -10;
+var exitFakeResourceId = -20;
+var popEnterFakeResourceId = -30;
+var popExitFakeResourceId = -40;
+
+export module AndroidTransitionType {
+ export var enter: string = "enter";
+ export var exit: string = "exit";
+ export var popEnter: string = "popEnter";
+ export var popExit: string = "popExit";
+}
+
+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;
+ }
+
+ if (_sdkVersion >= 21) {
+ var exitTransition = (fragment).getExitTransition();
+ if (exitTransition) {
+ trace.write(`Cleared Exit ${exitTransition.getClass().getSimpleName()} transition for ${fragment.getTag()}`, trace.categories.Transition);
+ (fragment).setExitTransition(null);//exit
+ }
+ var reenterTransition = (fragment).getReenterTransition();
+ if (reenterTransition) {
+ trace.write(`Cleared Pop Enter ${reenterTransition.getClass().getSimpleName()} transition for ${fragment.getTag()}`, trace.categories.Transition);
+ (fragment).setReenterTransition(null);//popEnter
+ }
+ }
+}
+
+export function _setAndroidFragmentTransitions(navigationTransition: frameModule.NavigationTransition, currentFragment: any, newFragment: any, fragmentTransaction: any): void {
+ var name;
+ if (types.isString(navigationTransition.transition)) {
+ name = navigationTransition.transition.toLowerCase();
+ }
+
+ var useLollipopTransition = name && (name.indexOf("slide") === 0 || name === "fade" || name === "explode") && _sdkVersion >= 21;
+ if (useLollipopTransition) {
+ // setEnterTransition: Enter
+ // setExitTransition: Exit
+ // setReenterTransition: Pop Enter, same as Exit if not specified
+ // setReturnTransition: Pop Exit, same as Enter if not specified
+
+ newFragment.setAllowEnterTransitionOverlap(true);
+ newFragment.setAllowReturnTransitionOverlap(true);
+ if (currentFragment) {
+ currentFragment.setAllowEnterTransitionOverlap(true);
+ currentFragment.setAllowReturnTransitionOverlap(true);
+ }
+
+ if (name.indexOf("slide") === 0) {
+ var direction = name.substr("slide".length) || "left"; //Extract the direction from the string
+ switch (direction) {
+ case "left":
+ let rightEdge = new (android).transition.Slide((android).view.Gravity.RIGHT);
+ _setUpNativeTransition(navigationTransition, rightEdge);
+ _addNativeTransitionListener(newFragment, rightEdge);
+ newFragment.setEnterTransition(rightEdge);
+ if (currentFragment) {
+ let leftEdge = new (android).transition.Slide((android).view.Gravity.LEFT);
+ _setUpNativeTransition(navigationTransition, leftEdge);
+ _addNativeTransitionListener(currentFragment, leftEdge);
+ currentFragment.setExitTransition(leftEdge);
+ }
+ break;
+ case "right":
+ let leftEdge = new (android).transition.Slide((android).view.Gravity.LEFT);
+ _setUpNativeTransition(navigationTransition, leftEdge);
+ _addNativeTransitionListener(newFragment, leftEdge);
+ newFragment.setEnterTransition(leftEdge);
+ if (currentFragment) {
+ let rightEdge = new (android).transition.Slide((android).view.Gravity.RIGHT);
+ _setUpNativeTransition(navigationTransition, rightEdge);
+ _addNativeTransitionListener(currentFragment, rightEdge);
+ currentFragment.setExitTransition(rightEdge);
+ }
+ break;
+ case "top":
+ let bottomEdge = new (android).transition.Slide((android).view.Gravity.BOTTOM);
+ _setUpNativeTransition(navigationTransition, bottomEdge);
+ _addNativeTransitionListener(newFragment, bottomEdge);
+ newFragment.setEnterTransition(bottomEdge);
+ if (currentFragment) {
+ let topEdge = new (android).transition.Slide((android).view.Gravity.TOP);
+ _setUpNativeTransition(navigationTransition, topEdge);
+ _addNativeTransitionListener(currentFragment, topEdge);
+ currentFragment.setExitTransition(topEdge);
+ }
+ break;
+ case "bottom":
+ let topEdge = new (android).transition.Slide((android).view.Gravity.TOP);
+ _setUpNativeTransition(navigationTransition, topEdge);
+ _addNativeTransitionListener(newFragment, topEdge);
+ newFragment.setEnterTransition(topEdge);
+ if (currentFragment) {
+ let bottomEdge = new (android).transition.Slide((android).view.Gravity.BOTTOM);
+ _setUpNativeTransition(navigationTransition, bottomEdge);
+ _addNativeTransitionListener(currentFragment, bottomEdge);
+ currentFragment.setExitTransition(bottomEdge);
+ }
+ break;
+ }
+ }
+ else if (name === "fade") {
+ let fadeEnter = new (android).transition.Fade((android).transition.Fade.IN);
+ _setUpNativeTransition(navigationTransition, fadeEnter);
+ _addNativeTransitionListener(newFragment, fadeEnter);
+ newFragment.setEnterTransition(fadeEnter);
+ let fadeReturn = new (android).transition.Fade((android).transition.Fade.OUT);
+ _setUpNativeTransition(navigationTransition, fadeReturn);
+ _addNativeTransitionListener(newFragment, fadeReturn);
+ newFragment.setReturnTransition(fadeReturn);
+ if (currentFragment) {
+ let fadeExit = new (android).transition.Fade((android).transition.Fade.OUT);
+ _setUpNativeTransition(navigationTransition, fadeExit);
+ _addNativeTransitionListener(currentFragment, fadeExit);
+ currentFragment.setExitTransition(fadeExit);
+ let fadeReenter = new (android).transition.Fade((android).transition.Fade.IN);
+ _setUpNativeTransition(navigationTransition, fadeReenter);
+ _addNativeTransitionListener(currentFragment, fadeReenter);
+ currentFragment.setReenterTransition(fadeReenter);
+ }
+ }
+ else if (name === "explode") {
+ let explodeEnter = new (android).transition.Explode();
+ _setUpNativeTransition(navigationTransition, explodeEnter);
+ _addNativeTransitionListener(newFragment, explodeEnter);
+ newFragment.setEnterTransition(explodeEnter);
+ if (currentFragment) {
+ let explodeExit = new (android).transition.Explode();
+ _setUpNativeTransition(navigationTransition, explodeExit);
+ _addNativeTransitionListener(currentFragment, explodeExit);
+ currentFragment.setExitTransition(explodeExit);
+ }
+ }
+ return;
+ }
+
+ var transition: Transition;
+ if (name) {
+ if (name.indexOf("slide") === 0) {
+ var slideTransitionModule = require("./slide-transition");
+ var direction = name.substr("slide".length) || "left"; //Extract the direction from the string
+ transition = new slideTransitionModule.SlideTransition(direction, navigationTransition.duration, navigationTransition.curve);
+ }
+ else if (name === "fade") {
+ var fadeTransitionModule = require("./fade-transition");
+ transition = new fadeTransitionModule.FadeTransition(navigationTransition.duration, navigationTransition.curve);
+ }
+ else if (name.indexOf("flip") === 0) {
+ var flipTransitionModule = require("./flip-transition");
+ var direction = name.substr("flip".length) || "right"; //Extract the direction from the string
+ transition = new flipTransitionModule.FlipTransition(direction, navigationTransition.duration, navigationTransition.curve);
+ }
+ }
+ else {
+ transition = navigationTransition.transition; // User-defined instance of Transition
+ }
+
+ if (transition) {
+ newFragment[ENTER_POPEXIT_TRANSITION] = transition;
+ if (currentFragment) {
+ currentFragment[EXIT_POPENTER_TRANSITION] = transition;
+ }
+ fragmentTransaction.setCustomAnimations(enterFakeResourceId, exitFakeResourceId, popEnterFakeResourceId, popExitFakeResourceId);
+ }
+}
+
+function _setUpNativeTransition(navigationTransition: frameModule.NavigationTransition, nativeTransition: any/*android.transition.Transition*/) {
+ if (navigationTransition.duration) {
+ nativeTransition.setDuration(navigationTransition.duration);
+ }
+
+ if (navigationTransition.curve) {
+ var animation: typeof animationModule = require("ui/animation");
+ var interpolator = animation._resolveAnimationCurve(navigationTransition.curve);
+ nativeTransition.setInterpolator(interpolator);
+ }
+ else {
+ nativeTransition.setInterpolator(_defaultInterpolator);
+ }
+}
+
+export function _onFragmentShown(fragment: android.app.Fragment, isBack: boolean): void {
+ 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 };
+ }
+ 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 };
+ }
+ }
+
+ if (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS] === undefined) {
+ _completePageAddition(fragment, isBack, true);
+ }
+}
+
+export function _onFragmentHidden(fragment: android.app.Fragment, isBack: boolean) {
+ 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;
+ }
+ 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;
+ }
+ }
+
+ if (fragment[COMPLETE_PAGE_REMOVAL_WHEN_TRANSITION_ENDS] === undefined) {
+ // This might be a second call if the fragment is hidden and then destroyed.
+ _completePageRemoval(fragment, true);
+ }
+}
+
+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 _completePageRemoval(fragment: android.app.Fragment, force?: boolean) {
+ 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);
+ }
+ trace.write(`REMOVAL of ${page} completed`, trace.categories.Transition);
+ }
+}
+
+function _addNativeTransitionListener(fragment: android.app.Fragment, nativeTransition: any/*android.transition.Transition*/) {
+ 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 (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS]) {
+ _completePageAddition(fragment, fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS].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 (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS]) {
+ _completePageAddition(fragment, fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS].isBack);
+ }
+ },
+ onTransitionPause: function (transition: any): void {
+ trace.write(`PAUSE ${nativeTransition} transition for ${fragment}`, trace.categories.Transition);
+ },
+ onTransitionResume: function (transition: any): void {
+ trace.write(`RESUME ${nativeTransition} transition for ${fragment}`, trace.categories.Transition);
+ },
+ onTransitionStart: function (transition: any): void {
+ trace.write(`START ${nativeTransition} transition for ${fragment}`, trace.categories.Transition);
+ }
+ });
+ nativeTransition.addListener(transitionListener);
+}
+
+export function _onFragmentCreateAnimator(fragment: android.app.Fragment, nextAnim: number): android.animation.Animator {
+ var transitionType;
+ switch (nextAnim) {
+ case enterFakeResourceId: transitionType = AndroidTransitionType.enter; break;
+ case exitFakeResourceId: transitionType = AndroidTransitionType.exit; break;
+ case popEnterFakeResourceId: transitionType = AndroidTransitionType.popEnter; break;
+ case popExitFakeResourceId: transitionType = AndroidTransitionType.popExit; break;
+ }
+
+ var transition;
+ switch (transitionType) {
+ case AndroidTransitionType.enter:
+ case AndroidTransitionType.popExit:
+ transition = fragment[ENTER_POPEXIT_TRANSITION];
+ break;
+ case AndroidTransitionType.exit:
+ case AndroidTransitionType.popEnter:
+ transition = fragment[EXIT_POPENTER_TRANSITION];
+ break;
+ }
+
+ var animator: android.animation.Animator;
+ if (transition) {
+ animator = transition.createAndroidAnimator(transitionType);
+ var transitionListener = new android.animation.Animator.AnimatorListener({
+ onAnimationStart: function (animator: android.animation.Animator): void {
+ trace.write(`START ${transitionType} ${transition} for ${fragment.getTag()}`, trace.categories.Transition);
+ },
+ onAnimationRepeat: function (animator: android.animation.Animator): void {
+ trace.write(`REPEAT ${transitionType} ${transition} for ${fragment.getTag()}`, trace.categories.Transition);
+ },
+ 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 (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS]) {
+ _completePageAddition(fragment, fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS].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 (fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS]) {
+ _completePageAddition(fragment, fragment[COMPLETE_PAGE_ADDITION_WHEN_TRANSITION_ENDS].isBack);
+ }
+ }
+ });
+ animator.addListener(transitionListener);
+ }
+
+ return animator;
+}
+
+var transitionId = 0;
+export class Transition implements definition.Transition {
+ private _duration: number;
+ private _interpolator: android.view.animation.Interpolator;
+ private _id: number;
+
+ constructor(duration: number, curve: any) {
+ this._duration = duration;
+ if (curve) {
+ var animation: typeof animationModule = require("ui/animation");
+ this._interpolator = animation._resolveAnimationCurve(curve);
+ }
+ else {
+ this._interpolator = _defaultInterpolator;
+ }
+ this._id = transitionId++;
+ }
+
+ public getDuration(): number {
+ return this._duration;
+ }
+
+ public getCurve(): android.view.animation.Interpolator {
+ return this._interpolator;
+ }
+
+ public animateIOSTransition(containerView: any, fromView: any, toView: any, operation: any, completion: (finished: boolean) => void): void {
+ throw new Error("Abstract method call");
+ }
+
+ public createAndroidAnimator(transitionType: string): android.animation.Animator {
+ throw new Error("Abstract method call");
+ }
+
+ public toString(): string {
+ return `${types.getClass(this)}@${this._id}`;
+ }
+}
\ No newline at end of file
diff --git a/ui/transition/transition.d.ts b/ui/transition/transition.d.ts
new file mode 100644
index 000000000..639e97068
--- /dev/null
+++ b/ui/transition/transition.d.ts
@@ -0,0 +1,29 @@
+declare module "ui/transition" {
+ import frame = require("ui/frame");
+
+ export module AndroidTransitionType {
+ export var enter: string;
+ export var exit: string;
+ export var popEnter: string;
+ export var popExit: string;
+ }
+
+ export class Transition {
+ constructor(duration: number, curve: any);
+ public getDuration(): number;
+ public getCurve(): any;
+ public animateIOSTransition(containerView: any, fromView: any, toView: any, operation: any, completion: (finished: boolean) => void): void;
+ public createAndroidAnimator(transitionType: string): any;
+ public toString(): string;
+ }
+
+ //@private
+ export function _clearForwardTransitions(fragment: any): void;
+ export function _setAndroidFragmentTransitions(navigationTransition: frame.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): void;
+
+ export function _createIOSAnimatedTransitioning(navigationTransition: frame.NavigationTransition, operation: number, fromVC: any, toVC: any): any;
+ //@endprivate
+}
\ No newline at end of file
diff --git a/ui/transition/transition.ios.ts b/ui/transition/transition.ios.ts
new file mode 100644
index 000000000..ad108adeb
--- /dev/null
+++ b/ui/transition/transition.ios.ts
@@ -0,0 +1,115 @@
+import definition = require("ui/transition");
+import frame = require("ui/frame");
+import * as animationModule from "ui/animation";
+import types = require("utils/types");
+import trace = require("trace");
+
+class AnimatedTransitioning extends NSObject implements UIViewControllerAnimatedTransitioning {
+ public static ObjCProtocols = [UIViewControllerAnimatedTransitioning];
+
+ private _transition: Transition;
+ private _operation: UINavigationControllerOperation;
+ private _fromVC: UIViewController;
+ private _toVC: UIViewController;
+ private _transitionType: string;
+
+ public static init(transition: Transition, operation: UINavigationControllerOperation, fromVC: UIViewController, toVC: UIViewController): AnimatedTransitioning {
+ var impl = AnimatedTransitioning.new();
+ impl._transition = transition;
+ impl._operation = operation;
+ impl._fromVC = fromVC;
+ impl._toVC = toVC;
+ return impl;
+}
+
+ public animateTransition(transitionContext: any): void {
+ let containerView = transitionContext.performSelector("containerView");
+ var completion = (finished: boolean) => {
+ transitionContext.performSelectorWithObject("completeTransition:", finished);
+ }
+ switch (this._operation) {
+ case UINavigationControllerOperation.UINavigationControllerOperationPush: this._transitionType = "push"; break;
+ case UINavigationControllerOperation.UINavigationControllerOperationPop: this._transitionType = "pop"; break;
+ case UINavigationControllerOperation.UINavigationControllerOperationNone: this._transitionType = "none"; break;
+ }
+ trace.write(`START ${this._transition} ${this._transitionType}`, trace.categories.Transition);
+ this._transition.animateIOSTransition(containerView, this._fromVC.view, this._toVC.view, this._operation, completion);
+ }
+
+ public transitionDuration(transitionContext: UIViewControllerContextTransitioning): number {
+ return this._transition.getDuration();
+ }
+
+ public animationEnded(transitionCompleted: boolean): void {
+ if (transitionCompleted) {
+ trace.write(`END ${this._transition} ${this._transitionType}`, trace.categories.Transition);
+ }
+ else {
+ trace.write(`CANCEL ${this._transition} ${this._transitionType}`, trace.categories.Transition);
+ }
+ }
+}
+
+var transitionId = 0;
+export class Transition implements definition.Transition {
+ private _duration: number;
+ private _curve: UIViewAnimationCurve;
+ private _id: number;
+
+ constructor(duration: number, curve: any) {
+ this._duration = duration ? (duration / 1000) : 0.35;
+ if (curve) {
+ var animation: typeof animationModule = require("ui/animation");
+ this._curve = animation._resolveAnimationCurve(curve);
+ }
+ else {
+ this._curve = UIViewAnimationCurve.UIViewAnimationCurveEaseInOut;
+ }
+ }
+
+ public getDuration(): number {
+ return this._duration;
+ }
+
+ public getCurve(): UIViewAnimationCurve {
+ return this._curve;
+ }
+
+ public animateIOSTransition(containerView: UIView, fromView: UIView, toView: UIView, operation: UINavigationControllerOperation, completion: (finished: boolean) => void): void {
+ throw new Error("Abstract method call");
+ }
+
+ public createAndroidAnimator(transitionType: string): any {
+ throw new Error("Abstract method call");
+ }
+
+ public toString(): string {
+ return `${types.getClass(this)}@${this._id}`;
+ }
+}
+
+export function _createIOSAnimatedTransitioning(navigationTransition: frame.NavigationTransition, operation: UINavigationControllerOperation, fromVC: UIViewController, toVC: UIViewController): UIViewControllerAnimatedTransitioning {
+ var transition: Transition;
+
+ if (types.isString(navigationTransition.transition)) {
+ var name = navigationTransition.transition.toLowerCase();
+ if (name.indexOf("slide") === 0) {
+ var slideTransitionModule = require("./slide-transition");
+ var direction = name.substr("slide".length) || "left"; //Extract the direction from the string
+ transition = new slideTransitionModule.SlideTransition(direction, navigationTransition.duration, navigationTransition.curve);
+ }
+ else if (name === "fade") {
+ var fadeTransitionModule = require("./fade-transition");
+ transition = new fadeTransitionModule.FadeTransition(navigationTransition.duration, navigationTransition.curve);
+ }
+ }
+ else {
+ transition = navigationTransition.transition;
+ }
+
+ if (transition) {
+ return AnimatedTransitioning.init(transition, operation, fromVC, toVC);
+ }
+
+ return null;
+}
diff --git a/ui/ui.d.ts b/ui/ui.d.ts
index e6407223d..76730f68c 100644
--- a/ui/ui.d.ts
+++ b/ui/ui.d.ts
@@ -4,6 +4,7 @@
declare module "ui" {
export * from "ui/action-bar";
export * from "ui/activity-indicator";
+ export * from "ui/animation";
export * from "ui/builder";
export * from "ui/button";
export * from "ui/content-view";