diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4dd5bed9f6..66ac64ca40 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
Cross Platform Modules Changelog
==============================
+## 3.4.0 (2017, December 20)
+
+## Fixed
+- [(# 5047)](https://github.com/NativeScript/NativeScript/pull/5047) Android: Click next moves the focus to next focusable TextField
+- [(# 5046)](https://github.com/NativeScript/NativeScript/pull/5046) Android: Fix search-bar bug
+- [(# 5019)](https://github.com/NativeScript/NativeScript/issues/5019) IOS: App crash on low memory notification with custom fonts
+- [(# 4993)](https://github.com/NativeScript/NativeScript/issues/4993) ScrollView base: Adding listener when you are supposed to remove it
+
+### New
+- [(# 4871)](https://github.com/NativeScript/NativeScript/issues/4871) Application Settings blocks UI thread
+- [(# 2992)](https://github.com/NativeScript/NativeScript/issues/2992) CSS background shorthand property
+
+
## 3.3.0 (2017, October 25)
### Fixed
diff --git a/README.md b/README.md
index 348572358b..ada81df524 100644
--- a/README.md
+++ b/README.md
@@ -46,16 +46,16 @@ Our Getting Started Guides are hands-on tutorials that walk you through installi
The NativeScript framework consists of a number of components, all of which are open source and on GitHub. Here are the major ones:
- **[Cross-platform modules](//github.com/NativeScript/NativeScript/)**
- [](https://www.npmjs.com/package/tns-core-modules)
+ [](https://www.npmjs.com/package/tns-core-modules) [](https://waffle.io/NativeScript/NativeScript)
- This repo contains the [NativeScript cross-platform modules](http://docs.nativescript.org/core-concepts/modules), which abstract iOS and Android APIs into JavaScript APIs—e.g. `camera.takePicture()`. The modules are written in TypeScript.
- **[iOS runtime](//github.com/NativeScript/ios-runtime/)**
- [](https://www.npmjs.com/package/tns-ios)
+ [](https://www.npmjs.com/package/tns-ios) [](https://waffle.io/NativeScript/android-runtime)
- This repo contains the NativeScript iOS runtime—the code that hosts NativeScript iOS apps, and allows JavaScript code to be executed on iOS devices. The iOS runtime is written in a fun mix of C++, Objective-C, and more.
- **[Android runtime](//github.com/NativeScript/android-runtime)**
- [](https://www.npmjs.com/package/tns-android)
+ [](https://www.npmjs.com/package/tns-android) [](https://waffle.io/NativeScript/android-runtime)
- This repo contains the NativeScript Android—the code that hosts NativeScript Android apps, and allows JavaScript code to be executed on Android devices. The Android runtime is written in a fun mix of C++ and Java.
- **[CLI](//github.com/NativeScript/nativescript-cli)**
- [](https://www.npmjs.com/package/nativescript)
+ [](https://www.npmjs.com/package/nativescript) [](https://waffle.io/NativeScript/android-runtime)
- This repo contains the NativeScript command-line interface, which lets you create, build, and run apps using the NativeScript framework. The CLI is written in TypeScript.
- **[Docs](//github.com/NativeScript/docs)**
- This repo contains the NativeScript framework documentation, which is available at . The docs are written in Markdown.
diff --git a/tests/app/ui/page/page-tests.android.ts b/tests/app/ui/page/page-tests.android.ts
index ded2e561fe..d95386d5cd 100644
--- a/tests/app/ui/page/page-tests.android.ts
+++ b/tests/app/ui/page/page-tests.android.ts
@@ -105,6 +105,6 @@ export var test_Resolve_Fragment_ForPage = function () {
frame.navigate(pageFactory);
TKUnit.waitUntilReady(() => frame.navigationQueueIsEmpty());
- const fragment = frame.android.fragmentForPage(testPage);
+ const fragment = frame.android.fragmentForPage(frame._currentEntry);
TKUnit.assertNotNull(fragment, "Failed to resolve native fragment for page");
}
\ No newline at end of file
diff --git a/tns-core-modules/application/application.ios.ts b/tns-core-modules/application/application.ios.ts
index 939c98d500..e0e039510a 100644
--- a/tns-core-modules/application/application.ios.ts
+++ b/tns-core-modules/application/application.ios.ts
@@ -154,6 +154,11 @@ class IOSApplication implements IOSApplicationDefinition {
let ios = utils.ios.getter(UIApplication, UIApplication.sharedApplication);
let object = this;
notify({ eventName: resumeEvent, object, ios });
+ const content = this._window.content;
+ if (content && !content.isLoaded) {
+ content.onLoaded();
+ }
+
if (!displayedOnce) {
notify({ eventName: displayedEvent, object, ios });
displayedOnce = true;
@@ -161,10 +166,18 @@ class IOSApplication implements IOSApplicationDefinition {
}
private didEnterBackground(notification: NSNotification) {
+ const content = this._window.content;
+ if (content && content.isLoaded) {
+ content.onUnloaded();
+ }
notify({ eventName: suspendEvent, object: this, ios: utils.ios.getter(UIApplication, UIApplication.sharedApplication) });
}
private willTerminate(notification: NSNotification) {
+ const content = this._window.content;
+ if (content && content.isLoaded) {
+ content.onUnloaded();
+ }
notify({ eventName: exitEvent, object: this, ios: utils.ios.getter(UIApplication, UIApplication.sharedApplication) });
}
diff --git a/tns-core-modules/globals/globals.ts b/tns-core-modules/globals/globals.ts
index 3e18ce4639..ba8cfa236e 100644
--- a/tns-core-modules/globals/globals.ts
+++ b/tns-core-modules/globals/globals.ts
@@ -57,11 +57,20 @@ global.registerWebpackModules = function registerWebpackModules(context: Context
const registerName = base + registerExt;
if (registerName.startsWith("./") && registerName.endsWith(".js")) {
- const jsNickName = registerName.substr(2, registerName.length - 5);
- // This is extremely short version like "main-page" that was promoted to be used with global.registerModule("module-name", loaderFunc);
- if (isSourceFile || !global.moduleExists(jsNickName)) {
- global.registerModule(jsNickName, () => context(key));
- }
+ const jsNickNames = [
+ // This is extremely short version like "main-page" that was promoted to be used with global.registerModule("module-name", loaderFunc);
+ registerName.substr(2, registerName.length - 5),
+ // This is for supporting module names like "./main/main-page"
+ registerName.substr(0, registerName.length - 3),
+ // This is for supporting module names like "main/main-page.js"
+ registerName.substr(2),
+ ];
+
+ jsNickNames.forEach(jsNickName => {
+ if (isSourceFile || !global.moduleExists(jsNickName)) {
+ global.registerModule(jsNickName, () => context(key));
+ }
+ });
}
if (isSourceFile || !global.moduleExists(registerName)) {
global.registerModule(registerName, () => context(key));
diff --git a/tns-core-modules/ui/frame/fragment.android.ts b/tns-core-modules/ui/frame/fragment.android.ts
index 53826e7850..cc6b8f960f 100644
--- a/tns-core-modules/ui/frame/fragment.android.ts
+++ b/tns-core-modules/ui/frame/fragment.android.ts
@@ -19,6 +19,10 @@ class FragmentClass extends android.app.Fragment {
return result;
}
+ public onStop(): void {
+ this._callbacks.onStop(this, super.onStop);
+ }
+
public onCreate(savedInstanceState: android.os.Bundle) {
if (!this._callbacks) {
setFragmentCallbacks(this);
diff --git a/tns-core-modules/ui/frame/fragment.transitions.android.ts b/tns-core-modules/ui/frame/fragment.transitions.android.ts
index 2d62218443..25651c48db 100644
--- a/tns-core-modules/ui/frame/fragment.transitions.android.ts
+++ b/tns-core-modules/ui/frame/fragment.transitions.android.ts
@@ -13,27 +13,10 @@ import lazy from "../../utils/lazy";
import { isEnabled as traceEnabled, write as traceWrite, categories as traceCategories } from "../../trace";
-// SAME as frame.android.ts!!! Not imported because we don't want cycle reference.
-const CALLBACKS = "_callbacks";
-
-const sdkVersion = lazy(() => parseInt(device.sdkVersion));
-const intEvaluator = lazy(() => new android.animation.IntEvaluator());
-const defaultInterpolator = lazy(() => new android.view.animation.AccelerateDecelerateInterpolator());
-
-const waitingQueue = new Set();
-
interface TransitionListener {
new(entry: ExpandedEntry, transition: android.transition.Transition): ExpandedTransitionListener;
}
-let TransitionListener: TransitionListener;
-let AnimationListener: android.animation.Animator.AnimatorListener;
-let loadAnimatorMethod: java.lang.reflect.Method;
-let reflectionDone: boolean;
-let defaultEnterAnimatorStatic: android.animation.Animator;
-let defaultExitAnimatorStatic: android.animation.Animator;
-let fragmentCompleted: android.app.Fragment;
-
interface ExpandedAnimator extends android.animation.Animator {
entry: ExpandedEntry;
transitionType?: string;
@@ -55,8 +38,12 @@ interface ExpandedEntry extends BackstackEntry {
popEnterAnimator: ExpandedAnimator;
popExitAnimator: ExpandedAnimator;
+ defaultEnterAnimator: ExpandedAnimator;
+ defaultExitAnimator: ExpandedAnimator;
+
transition: Transition;
transitionName: string;
+ frameId: number
}
interface FragmentCallbacks {
@@ -64,18 +51,38 @@ interface FragmentCallbacks {
entry: ExpandedEntry;
}
+const sdkVersion = lazy(() => parseInt(device.sdkVersion));
+const intEvaluator = lazy(() => new android.animation.IntEvaluator());
+const defaultInterpolator = lazy(() => new android.view.animation.AccelerateDecelerateInterpolator());
+
+export const waitingQueue = new Map>();
+export const completedEntries = new Map();
+
+let TransitionListener: TransitionListener;
+let AnimationListener: android.animation.Animator.AnimatorListener;
+let loadAnimatorMethod: java.lang.reflect.Method;
+let reflectionDone: boolean;
+let defaultEnterAnimatorStatic: android.animation.Animator;
+let defaultExitAnimatorStatic: android.animation.Animator;
+
export function _setAndroidFragmentTransitions(
animated: boolean,
navigationTransition: NavigationTransition,
- currentFragment: android.app.Fragment,
- newFragment: android.app.Fragment,
+ currentEntry: ExpandedEntry,
+ newEntry: ExpandedEntry,
fragmentTransaction: android.app.FragmentTransaction,
- manager: android.app.FragmentManager): void {
+ manager: android.app.FragmentManager,
+ frameId: number): void {
- if (waitingQueue.size > 0) {
- throw new Error('Calling navigation before previous queue completes.');
+ const currentFragment: android.app.Fragment = currentEntry ? currentEntry.fragment : null;
+ const newFragment: android.app.Fragment = newEntry.fragment;
+ const entries = waitingQueue.get(frameId);
+ if (entries && entries.size > 0) {
+ throw new Error('Calling navigation before previous navigation finish.');
}
+ initDefaultAnimations(manager);
+
if (sdkVersion() >= 21) {
allowTransitionOverlap(currentFragment);
allowTransitionOverlap(newFragment);
@@ -101,9 +108,6 @@ export function _setAndroidFragmentTransitions(
name = 'default';
}
- const callbacks = getFragmentCallbacks(newFragment);
- const newEntry = callbacks.entry;
- const currentEntry = currentFragment ? getFragmentCallbacks(currentFragment).entry : null;
let currentFragmentNeedsDifferentAnimation = false;
if (currentEntry) {
_updateTransitions(currentEntry);
@@ -117,7 +121,6 @@ export function _setAndroidFragmentTransitions(
if (name === 'none') {
transition = new NoTransition(0, null);
} else if (name === 'default') {
- initDefaultAnimations(manager);
transition = new DefaultTransition(0, null);
} else if (useLollipopTransition) {
// setEnterTransition: Enter
@@ -172,24 +175,50 @@ export function _setAndroidFragmentTransitions(
}
}
+ initDefaultAnimations(manager);
+ setupDefaultAnimations(newEntry, new DefaultTransition(0, null));
+
printTransitions(currentEntry);
printTransitions(newEntry);
}
-export function _onFragmentCreateAnimator(fragment: android.app.Fragment, nextAnim: number): android.animation.Animator {
- const entry = getFragmentCallbacks(fragment).entry;
+export function _onFragmentCreateAnimator(entry: ExpandedEntry, fragment: android.app.Fragment, nextAnim: number, enter: boolean): android.animation.Animator {
+ let animator: android.animation.Animator;
switch (nextAnim) {
case AnimationType.enterFakeResourceId:
- return entry.enterAnimator;
+ animator = entry.enterAnimator;
+ break;
+
case AnimationType.exitFakeResourceId:
- return entry.exitAnimator;
+ animator = entry.exitAnimator;
+ break;
+
case AnimationType.popEnterFakeResourceId:
- return entry.popEnterAnimator;
+ animator = entry.popEnterAnimator;
+ break;
+
case AnimationType.popExitFakeResourceId:
- return entry.popExitAnimator;
+ animator = entry.popExitAnimator;
+ break;
+ }
+
+ if (!animator && sdkVersion() >= 21) {
+ const view = fragment.getView();
+ const jsParent = entry.resolvedPage.parent;
+ const parent = view.getParent() || (jsParent && jsParent.nativeViewProtected);
+ const animatedEntries = _getAnimatedEntries(entry.frameId);
+ if (!animatedEntries || !animatedEntries.has(entry)) {
+ if (parent && !(parent).isLaidOut()) {
+ animator = enter ? entry.defaultEnterAnimator : entry.defaultExitAnimator;
+ }
+ }
}
- return null;
+ return animator;
+}
+
+export function _getAnimatedEntries(frameId: number): Set {
+ return waitingQueue.get(frameId);
}
export function _updateTransitions(entry: ExpandedEntry): void {
@@ -241,10 +270,6 @@ export function _reverseTransitions(previousEntry: ExpandedEntry, currentEntry:
return transitionUsed;
}
-function getFragmentCallbacks(fragment: android.app.Fragment): FragmentCallbacks {
- return fragment[CALLBACKS] as FragmentCallbacks;
-}
-
// Transition listener can't be static because
// android is cloning transitions and we can't expand them :(
function getTransitionListener(entry: ExpandedEntry, transition: android.transition.Transition): ExpandedTransitionListener {
@@ -257,40 +282,38 @@ function getTransitionListener(entry: ExpandedEntry, transition: android.transit
}
public onTransitionStart(transition: android.transition.Transition): void {
- const fragment = this.entry.fragment;
- waitingQueue.add(fragment);
+ const entry = this.entry;
+ addToWaitingQueue(entry);
if (traceEnabled()) {
- traceWrite(`START ${toShortString(transition)} transition for ${fragment}`, traceCategories.Transition);
+ traceWrite(`START ${toShortString(transition)} transition for ${entry.fragmentTag}`, traceCategories.Transition);
}
}
onTransitionEnd(transition: android.transition.Transition): void {
- const fragment = this.entry.fragment;
+ const entry = this.entry;
if (traceEnabled()) {
- traceWrite(`END ${toShortString(transition)} transition for ${fragment}`, traceCategories.Transition);
+ traceWrite(`END ${toShortString(transition)} transition for ${entry.fragmentTag}`, traceCategories.Transition);
}
- transitionOrAnimationCompleted(fragment);
+ transitionOrAnimationCompleted(entry);
}
onTransitionResume(transition: android.transition.Transition): void {
if (traceEnabled()) {
- const fragment = this.entry.fragment;
+ const fragment = this.entry.fragmentTag;
traceWrite(`RESUME ${toShortString(transition)} transition for ${fragment}`, traceCategories.Transition);
}
}
onTransitionPause(transition: android.transition.Transition): void {
if (traceEnabled()) {
- const fragment = this.entry.fragment;
- traceWrite(`PAUSE ${toShortString(transition)} transition for ${fragment}`, traceCategories.Transition);
+ traceWrite(`PAUSE ${toShortString(transition)} transition for ${this.entry.fragmentTag}`, traceCategories.Transition);
}
}
onTransitionCancel(transition: android.transition.Transition): void {
if (traceEnabled()) {
- const fragment = this.entry.fragment;
- traceWrite(`CANCEL ${toShortString(transition)} transition for ${fragment}`, traceCategories.Transition);
+ traceWrite(`CANCEL ${toShortString(transition)} transition for ${this.entry.fragmentTag}`, traceCategories.Transition);
}
}
}
@@ -311,30 +334,30 @@ function getAnimationListener(): android.animation.Animator.IAnimatorListener {
}
onAnimationStart(animator: ExpandedAnimator): void {
- const fragment = animator.entry.fragment;
- waitingQueue.add(fragment);
+ const entry = animator.entry;
+ addToWaitingQueue(entry);
if (traceEnabled()) {
- traceWrite(`START ${animator.transitionType} for ${fragment}`, traceCategories.Transition);
+ traceWrite(`START ${animator.transitionType} for ${entry.fragmentTag}`, traceCategories.Transition);
}
}
onAnimationRepeat(animator: ExpandedAnimator): void {
if (traceEnabled()) {
- traceWrite(`REPEAT ${animator.transitionType} for ${animator.entry.fragment}`, traceCategories.Transition);
+ traceWrite(`REPEAT ${animator.transitionType} for ${animator.entry.fragmentTag}`, traceCategories.Transition);
}
}
onAnimationEnd(animator: ExpandedAnimator): void {
if (traceEnabled()) {
- traceWrite(`END ${animator.transitionType} for ${animator.entry.fragment}`, traceCategories.Transition);
+ traceWrite(`END ${animator.transitionType} for ${animator.entry.fragmentTag}`, traceCategories.Transition);
}
- transitionOrAnimationCompleted(animator.entry.fragment);
+ transitionOrAnimationCompleted(animator.entry);
}
onAnimationCancel(animator: ExpandedAnimator): void {
if (traceEnabled()) {
- traceWrite(`CANCEL ${animator.transitionType} for ${animator.entry.fragment}`, traceCategories.Transition);
+ traceWrite(`CANCEL ${animator.transitionType} for ${animator.entry.fragmentTag}`, traceCategories.Transition);
}
}
}
@@ -345,6 +368,17 @@ function getAnimationListener(): android.animation.Animator.IAnimatorListener {
return AnimationListener;
}
+function addToWaitingQueue(entry: ExpandedEntry): void {
+ const frameId = entry.frameId;
+ let entries = waitingQueue.get(frameId);
+ if (!entries) {
+ entries = new Set();
+ waitingQueue.set(frameId, entries);
+ }
+
+ entries.add(entry);
+}
+
function clearAnimationListener(animator: ExpandedAnimator, listener: android.animation.Animator.IAnimatorListener): void {
if (!animator) {
return;
@@ -352,10 +386,9 @@ function clearAnimationListener(animator: ExpandedAnimator, listener: android.an
animator.removeListener(listener);
- const entry = animator.entry;
- const fragment = entry.fragment;
if (traceEnabled()) {
- traceWrite(`Clear ${animator.transitionType} - ${entry.transition} for ${fragment}`, traceCategories.Transition);
+ const entry = animator.entry;
+ traceWrite(`Clear ${animator.transitionType} - ${entry.transition} for ${entry.fragmentTag}`, traceCategories.Transition);
}
animator.entry = null;
@@ -403,8 +436,9 @@ function clearExitAndReenterTransitions(entry: ExpandedEntry, removeListener: bo
}
}
}
-export function _clearFragment(fragment: android.app.Fragment): void {
- clearEntry(getFragmentCallbacks(fragment).entry, false);
+
+export function _clearFragment(entry: ExpandedEntry): void {
+ clearEntry(entry, false);
}
export function _clearEntry(entry: ExpandedEntry): void {
@@ -633,6 +667,22 @@ function setupAllAnimation(entry: ExpandedEntry, transition: Transition): void {
entry.popExitAnimator = popExitAnimator;
}
+function setupDefaultAnimations(entry: ExpandedEntry, transition: Transition): void {
+ const listener = getAnimationListener();
+
+ const enterAnimator = transition.createAndroidAnimator(AndroidTransitionType.enter);
+ enterAnimator.transitionType = AndroidTransitionType.enter;
+ enterAnimator.entry = entry;
+ enterAnimator.addListener(listener);
+ entry.defaultEnterAnimator = enterAnimator;
+
+ const exitAnimator = transition.createAndroidAnimator(AndroidTransitionType.exit);
+ exitAnimator.transitionType = AndroidTransitionType.exit;
+ exitAnimator.entry = entry;
+ exitAnimator.addListener(listener);
+ entry.defaultExitAnimator = exitAnimator;
+}
+
function setUpNativeTransition(navigationTransition: NavigationTransition, nativeTransition: android.transition.Transition) {
if (navigationTransition.duration) {
nativeTransition.setDuration(navigationTransition.duration);
@@ -648,22 +698,28 @@ function addNativeTransitionListener(entry: ExpandedEntry, nativeTransition: and
return listener;
}
-function transitionOrAnimationCompleted(fragment: android.app.Fragment): void {
- waitingQueue.delete(fragment);
- if (waitingQueue.size === 0) {
- const callbacks = getFragmentCallbacks(fragment);
- const entry = callbacks.entry;
- const frame = callbacks.frame;
- const setAsCurrent = frame.isCurrent(entry) ? fragmentCompleted : fragment;
-
- fragmentCompleted = null;
+function transitionOrAnimationCompleted(entry: ExpandedEntry): void {
+ const frameId = entry.frameId;
+ const entries = waitingQueue.get(frameId);
+ entries.delete(entry);
+ if (entries.size === 0) {
+ const frame = entry.resolvedPage.frame;
+ // We have 0 or 1 entry per frameId in completedEntries
+ // So there is no need to make it to Set like waitingQueue
+ const previousCompletedAnimationEntry = completedEntries.get(frameId);
+ completedEntries.delete(frameId);
+ waitingQueue.delete(frameId);
+
+ let current = frame.isCurrent(entry) ? previousCompletedAnimationEntry : entry;
+ current = current || entry;
// Will be null if Frame is shown modally...
// AnimationCompleted fires again (probably bug in android).
- if (setAsCurrent) {
- setTimeout(() => frame.setCurrent(getFragmentCallbacks(setAsCurrent).entry));
+ if (current) {
+ const isBack = frame._isBack;
+ setTimeout(() => frame.setCurrent(current, isBack));
}
} else {
- fragmentCompleted = fragment;
+ completedEntries.set(frameId, entry);
}
}
@@ -719,8 +775,7 @@ function createDummyZeroDurationAnimator(): android.animation.Animator {
function printTransitions(entry: ExpandedEntry) {
if (entry && traceEnabled()) {
- const fragment = entry.fragment;
- let result = `${fragment} Transitions:`;
+ let result = `${entry.fragmentTag} Transitions:`;
if (entry.transitionName) {
result += `transitionName=${entry.transitionName}, `;
}
@@ -732,6 +787,7 @@ function printTransitions(entry: ExpandedEntry) {
result += `popExitAnimator=${entry.popExitAnimator}, `;
}
if (sdkVersion() >= 21) {
+ const fragment = entry.fragment;
result += `${fragment.getEnterTransition() ? " enter=" + toShortString(fragment.getEnterTransition()) : ""}`;
result += `${fragment.getExitTransition() ? " exit=" + toShortString(fragment.getExitTransition()) : ""}`;
result += `${fragment.getReenterTransition() ? " popEnter=" + toShortString(fragment.getReenterTransition()) : ""}`;
diff --git a/tns-core-modules/ui/frame/fragment.transitions.d.ts b/tns-core-modules/ui/frame/fragment.transitions.d.ts
index 7a715adcb6..4d8709fb78 100644
--- a/tns-core-modules/ui/frame/fragment.transitions.d.ts
+++ b/tns-core-modules/ui/frame/fragment.transitions.d.ts
@@ -21,14 +21,19 @@ export const enum AnimationType {
export function _setAndroidFragmentTransitions(
animated: boolean,
navigationTransition: NavigationTransition,
- currentFragment: any,
- newFragment: any,
+ currentEntry: BackstackEntry,
+ newEntry: BackstackEntry,
fragmentTransaction: any,
- manager: any /* android.app.FragmentManager */): void;
+ manager: any /* android.app.FragmentManager */,
+ frameId: number): void;
/**
* @private
*/
-export function _onFragmentCreateAnimator(fragment: any, nextAnim: number): any;
+export function _onFragmentCreateAnimator(entry: BackstackEntry, fragment: any, nextAnim: number, enter: boolean): any;
+/**
+ * @private
+ */
+export function _getAnimatedEntries(frameId: number): Set;
/**
* @private
* Called once fragment is recreated after it was destroyed.
@@ -54,9 +59,9 @@ export function _clearEntry(entry: BackstackEntry): void;
* Removes all animations and transitions but keeps them on the entry
* in order to reapply them when new fragment is created for the same entry.
*/
-export function _clearFragment(fragment: any): void;
+export function _clearFragment(entry: BackstackEntry): void;
/**
* @private
*/
export function _createIOSAnimatedTransitioning(navigationTransition: NavigationTransition, nativeCurve: any, operation: number, fromVC: any, toVC: any): any;
-//@endprivate
\ No newline at end of file
+//@endprivate
diff --git a/tns-core-modules/ui/frame/frame-common.ts b/tns-core-modules/ui/frame/frame-common.ts
index a4a3407798..155bdd2257 100644
--- a/tns-core-modules/ui/frame/frame-common.ts
+++ b/tns-core-modules/ui/frame/frame-common.ts
@@ -165,38 +165,89 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
public static androidOptionSelectedEvent = "optionSelected";
private _animated: boolean;
- public _currentEntry: BackstackEntry;
private _transition: NavigationTransition;
private _backStack = new Array();
private _navigationQueue = new Array();
+ public _currentEntry: BackstackEntry;
+ public _executingEntry: BackstackEntry;
public _isInFrameStack = false;
public static defaultAnimatedNavigation = true;
public static defaultTransition: NavigationTransition;
// TODO: Currently our navigation will not be synchronized in case users directly call native navigation methods like Activity.startActivity.
+ public _addChildFromBuilder(name: string, value: any) {
+ if (value instanceof Page) {
+ this.navigate({ create: () => value });
+ }
+ }
+
+ @profile
+ public onLoaded() {
+ super.onLoaded();
+ this._processNextNavigationEntry();
+ }
+
public canGoBack(): boolean {
- return this._backStack.length > 0;
+ let backstack = this._backStack.length;
+ let previousForwardNotInBackstack = false;
+ this._navigationQueue.forEach(item => {
+ const entry = item.entry;
+ if (item.isBackNavigation) {
+ previousForwardNotInBackstack = false;
+ if (!entry) {
+ backstack--;
+ } else {
+ const backstackIndex = this._backStack.indexOf(entry);
+ if (backstackIndex !== -1) {
+ backstack = backstackIndex;
+ } else {
+ // NOTE: We don't search for entries in navigationQueue because there is no way for
+ // developer to get reference to BackstackEntry unless transition is completed.
+ // At that point the entry is put in the backstack array.
+ // If we start to return Backstack entry from navigate method then
+ // here we should check also navigationQueue as well.
+ backstack--;
+ }
+ }
+ } else if (entry.entry.clearHistory) {
+ previousForwardNotInBackstack = false;
+ backstack = 0;
+ } else {
+ backstack++;
+ if (previousForwardNotInBackstack) {
+ backstack--;
+ }
+
+ previousForwardNotInBackstack = entry.entry.backstackVisible === false;
+ }
+ });
+
+ // this is our first navigation which is not completed yet.
+ if (this._navigationQueue.length > 0 && !this._currentEntry) {
+ backstack--;
+ }
+
+ return backstack > 0;
}
/**
* Navigates to the previous entry (if any) in the back stack.
* @param to The backstack entry to navigate back to.
*/
- public goBack(backstackEntry?: BackstackEntry) {
+ public goBack(backstackEntry?: BackstackEntry): void {
if (traceEnabled()) {
traceWrite(`GO BACK`, traceCategories.Navigation);
}
if (!this.canGoBack()) {
- // TODO: Do we need to throw an error?
return;
}
if (backstackEntry) {
- const backIndex = this._backStack.indexOf(backstackEntry);
- if (backIndex < 0) {
+ const index = this._backStack.indexOf(backstackEntry);
+ if (index < 0) {
return;
}
}
@@ -207,19 +258,18 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
}
this._navigationQueue.push(navigationContext);
-
- if (this._navigationQueue.length === 1) {
- this._processNavigationContext(navigationContext);
- }
- else {
- if (traceEnabled()) {
- traceWrite(`Going back scheduled`, traceCategories.Navigation);
- }
- }
+ this._processNextNavigationEntry();
}
- public _removeBackstackEntries(removed: BackstackEntry[]): void {
- // Handled in android.
+ public _removeEntry(removed: BackstackEntry): void {
+ const page = removed.resolvedPage;
+ const frame = page.frame;
+ (page)._frame = null;
+ if (frame) {
+ frame._removeView(page);
+ } else {
+ page._tearDownUI(true);
+ }
}
// Attempts to implement https://github.com/NativeScript/NativeScript/issues/1311
@@ -249,7 +299,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
}
const entry = buildEntryFromArgs(param);
- const page = resolvePageFromEntry(entry);
+ const page = resolvePageFromEntry(entry) as Page;
// Attempts to implement https://github.com/NativeScript/NativeScript/issues/1311
// if (page["isBiOrientational"] && entry.moduleName && !this._subscribedToOrientationChangedEvent){
@@ -276,22 +326,59 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
}
this._navigationQueue.push(navigationContext);
-
- if (this._navigationQueue.length === 1) {
- this._processNavigationContext(navigationContext);
- } else {
- if (traceEnabled()) {
- traceWrite(`Navigation scheduled`, traceCategories.Navigation);
- }
- }
+ this._processNextNavigationEntry();
}
public isCurrent(entry: BackstackEntry): boolean {
return this._currentEntry === entry;
}
- public setCurrent(entry: BackstackEntry): void {
+ public setCurrent(entry: BackstackEntry, isBack: boolean): void {
+ const newPage = entry.resolvedPage;
+ // In case we navigated forward to a page that was in the backstack
+ // with clearHistory: true
+ if (!newPage.frame) {
+ this._addView(newPage);
+ (newPage)._frame = this;
+ }
+
this._currentEntry = entry;
+ this._executingEntry = null;
+ newPage.onNavigatedTo(isBack);
+ }
+
+ public _updateBackstack(entry: BackstackEntry, isBack: boolean): void {
+ this.raiseCurrentPageNavigatedEvents(isBack);
+ const current = this._currentEntry;
+
+ if (isBack) {
+ const index = this._backStack.indexOf(entry);
+ this._backStack.splice(index + 1).forEach(e => this._removeEntry(e));
+ this._backStack.pop();
+ } else {
+ if (entry.entry.clearHistory) {
+ this._backStack.forEach(e => this._removeEntry(e));
+ this._backStack.length = 0;
+ } else if (FrameBase._isEntryBackstackVisible(current)) {
+ this._backStack.push(current);
+ }
+ }
+
+ if (current && this._backStack.indexOf(current) < 0) {
+ this._removeEntry(current);
+ }
+ }
+
+ private raiseCurrentPageNavigatedEvents(isBack: boolean) {
+ const page = this.currentPage;
+ if (page) {
+ if (page.isLoaded) {
+ // Forward navigation does not remove page from frame so we raise unloaded manually.
+ page.onUnloaded();
+ }
+
+ page.onNavigatedFrom(isBack);
+ }
}
public _processNavigationQueue(page: Page) {
@@ -300,8 +387,8 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
return;
}
- let entry = this._navigationQueue[0].entry;
- let currentNavigationPage = entry.resolvedPage;
+ const entry = this._navigationQueue[0].entry;
+ const currentNavigationPage = entry.resolvedPage;
if (page !== currentNavigationPage) {
// If the page is not the one that requested navigation - skip it.
return;
@@ -309,12 +396,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
// remove completed operation.
this._navigationQueue.shift();
-
- if (this._navigationQueue.length > 0) {
- let navigationContext = this._navigationQueue[0];
- this._processNavigationContext(navigationContext);
- }
-
+ this._processNextNavigationEntry();
this._updateActionBar();
}
@@ -353,29 +435,25 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
//traceWrite("calling _updateActionBar on Frame", traceCategories.Navigation);
}
- protected _processNavigationContext(navigationContext: NavigationContext) {
- if (navigationContext.isBackNavigation) {
- this.performGoBack(navigationContext);
- } else {
- this.performNavigation(navigationContext);
+ protected _processNextNavigationEntry() {
+ if (!this.isLoaded || this._executingEntry) {
+ return;
}
- }
- public _clearBackStack(): void {
- this._backStack.length = 0;
+ if (this._navigationQueue.length > 0) {
+ const navigationContext = this._navigationQueue[0];
+ if (navigationContext.isBackNavigation) {
+ this.performGoBack(navigationContext);
+ } else {
+ this.performNavigation(navigationContext);
+ }
+ }
}
@profile
private performNavigation(navigationContext: NavigationContext) {
- let navContext = navigationContext.entry;
-
- // TODO: This should happen once navigation is completed.
- if (navigationContext.entry.entry.clearHistory) {
- // Don't clear backstack immediately or we can't remove pages from frame.
- } else if (FrameBase._isEntryBackstackVisible(this._currentEntry)) {
- this._backStack.push(this._currentEntry);
- }
-
+ const navContext = navigationContext.entry;
+ this._executingEntry = navContext;
this._onNavigatingTo(navContext, navigationContext.isBackNavigation);
this._navigateCore(navContext);
}
@@ -383,16 +461,13 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
@profile
private performGoBack(navigationContext: NavigationContext) {
let backstackEntry = navigationContext.entry;
+ const backstack = this._backStack;
if (!backstackEntry) {
- backstackEntry = this._backStack.pop();
+ backstackEntry = backstack[backstack.length - 1];
navigationContext.entry = backstackEntry;
- } else {
- const index = this._backStack.indexOf(backstackEntry);
- const removed = this._backStack.splice(index + 1);
- this._backStack.pop();
- this._removeBackstackEntries(removed);
}
-
+
+ this._executingEntry = backstackEntry;
this._onNavigatingTo(backstackEntry, true);
this._goBackCore(backstackEntry);
}
@@ -485,8 +560,9 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
}
public eachChildView(callback: (child: View) => boolean) {
- if (this.currentPage) {
- callback(this.currentPage);
+ const page = this.currentPage;
+ if (page) {
+ callback(page);
}
}
@@ -588,7 +664,7 @@ export function topmost(): FrameBase {
export function goBack(): boolean {
const top = topmost();
- if (top.canGoBack()) {
+ if (top && top.canGoBack()) {
top.goBack();
return true;
}
diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts
index d039fae758..a9111ca8a4 100644
--- a/tns-core-modules/ui/frame/frame.android.ts
+++ b/tns-core-modules/ui/frame/frame.android.ts
@@ -1,16 +1,19 @@
// Definitions.
import {
- AndroidFrame as AndroidFrameDefinition, BackstackEntry,
+ AndroidFrame as AndroidFrameDefinition, BackstackEntry, NavigationEntry,
NavigationTransition, AndroidFragmentCallbacks, AndroidActivityCallbacks
} from ".";
import { Page } from "../page";
// Types.
import * as application from "../../application";
-import { FrameBase, NavigationContext, stack, goBack, View, Observable, traceEnabled, traceWrite, traceCategories } from "./frame-common";
-import { DIALOG_FRAGMENT_TAG } from "../page/constants";
import {
- _setAndroidFragmentTransitions, _onFragmentCreateAnimator,
+ FrameBase, NavigationContext, stack, goBack, View, Observable, topmost,
+ traceEnabled, traceWrite, traceCategories
+} from "./frame-common";
+
+import {
+ _setAndroidFragmentTransitions, _onFragmentCreateAnimator, _getAnimatedEntries,
_updateTransitions, _reverseTransitions, _clearEntry, _clearFragment, AnimationType
} from "./fragment.transitions";
@@ -21,9 +24,10 @@ export * from "./frame-common";
const INTENT_EXTRA = "com.tns.activity";
const FRAMEID = "_frameId";
const CALLBACKS = "_callbacks";
+
let navDepth = -1;
let fragmentId = -1;
-let activityInitialized: boolean;
+export let moduleLoaded: boolean;
if (global && global.__inspector) {
const devtools = require("tns-core-modules/debugger/devtools-elements");
@@ -35,6 +39,7 @@ export class Frame extends FrameBase {
private _android: AndroidFrame;
private _delayedNavigationEntry: BackstackEntry;
private _containerViewId: number = -1;
+ private _tearDownPending = false;
public _isBack: boolean = true;
constructor() {
@@ -64,9 +69,57 @@ export class Frame extends FrameBase {
return this._android;
}
+ public _getFragmentManager(): android.app.FragmentManager {
+ const activity = this._android.activity;
+ return activity && activity.getFragmentManager();
+ }
+
+ protected _processNextNavigationEntry(): void {
+ // In case activity was destroyed because of back button pressed (e.g. app exit)
+ // and application is restored from recent apps, current fragment isn't recreated.
+ // In this case call _navigateCore in order to recreate the current fragment.
+ // Don't call navigate because it will fire navigation events.
+ // As JS instances are alive it is already done for the current page.
+ if (!this.isLoaded) {
+ return;
+ }
+
+ const animatedEntries = _getAnimatedEntries(this._android.frameId);
+ if (animatedEntries) {
+ // // recreate UI on the animated fragments because we have new context.
+ // // We need to recreate the UI because it Frame will do it only for currentPage.
+ // // Once currentPage is changed due to transition end we will have no UI on the
+ // // new Page.
+ // animatedEntries.forEach(entry => {
+ // const page = entry.resolvedPage;
+ // if (page._context !== this._context) {
+ // page._tearDownUI(true);
+ // page._setupUI(this._context);
+ // }
+ // });
+
+ // Wait until animations are completed.
+ if (animatedEntries.size > 0) {
+ return;
+ }
+ }
+
+ const manager = this._getFragmentManager();
+ const entry = this._currentEntry;
+ if (entry && manager && !manager.findFragmentByTag(entry.fragmentTag)) {
+ // Simulate first navigation (e.g. no animations or transitions)
+ this._currentEntry = null;
+ // NavigateCore will eventually call _processNextNavigationEntry again.
+ this._navigateCore(entry);
+ this._currentEntry = entry;
+ } else {
+ super._processNextNavigationEntry();
+ }
+ }
+
private createFragment(backstackEntry: BackstackEntry, fragmentTag: string): android.app.Fragment {
ensureFragmentClass();
- const newFragment: android.app.Fragment = new fragmentClass();
+ const newFragment = new fragmentClass();
const args = new android.os.Bundle();
args.putInt(FRAMEID, this._android.frameId);
newFragment.setArguments(args);
@@ -84,42 +137,72 @@ export class Frame extends FrameBase {
return newFragment;
}
- public setCurrent(entry: BackstackEntry): void {
- this.changeCurrentPage(entry);
- this._currentEntry = entry;
- this._isBack = true;
- this._processNavigationQueue(entry.resolvedPage);
- }
+ public setCurrent(entry: BackstackEntry, isBack: boolean): void {
+ const current = this._currentEntry;
+ const currentEntryChanged = current !== entry;
+ if (currentEntryChanged) {
+ this._updateBackstack(entry, isBack);
+ }
- private changeCurrentPage(entry: BackstackEntry) {
- const isBack = this._isBack;
- let page: Page = this.currentPage;
- if (page) {
- if (page.frame === this &&
- (isBack || this.backStack.indexOf(this._currentEntry) < 0)) {
- // If going back or navigate forward but current entry is not backstack visible.
- removeEntry(this._currentEntry, page, this, true);
- }
+ if (currentEntryChanged) {
+ // If activity was destroyed we need to destroy fragment and UI
+ // of current and new entries.
+ if (this._tearDownPending) {
+ this._tearDownPending = false;
+ if (!entry.recreated) {
+ clearEntry(entry);
+ }
+
+ if (current && !current.recreated) {
+ clearEntry(current);
+ }
+
+ // If we have context activity was recreated. Create new fragment
+ // and UI for the new current page.
+ const context = this._context;
+ if (context && !entry.recreated) {
+ entry.fragment = this.createFragment(entry, entry.fragmentTag);
+ entry.resolvedPage._setupUI(context);
+ }
- if (page.isLoaded) {
- // Forward navigation does not remove page from frame so we raise unloaded manually.
- page.onUnloaded();
+ entry.recreated = false;
+ current.recreated = false;
}
- page.onNavigatedFrom(isBack);
+ super.setCurrent(entry, isBack);
+
+ // If we had real navigation process queue.
+ this._processNavigationQueue(entry.resolvedPage);
+ } else {
+ // Otherwise currentPage was recreated so this wasn't real navigation.
+ // Continue with next item in the queue.
+ this._processNextNavigationEntry();
+ }
+ }
+
+ public _onBackPressed(): boolean {
+ if (this.canGoBack()) {
+ this.goBack();
+ return true;
+ } else if (!this.navigationQueueIsEmpty()) {
+ const manager = this._getFragmentManager();
+ if (manager) {
+ manager.executePendingTransactions();
+ return true;
+ }
}
- const newPage = entry.resolvedPage;
- newPage._fragmentTag = entry.fragmentTag;
- this._currentEntry = entry;
- newPage.onNavigatedTo(isBack);
+ return false;
}
@profile
- public _navigateCore(backstackEntry: BackstackEntry) {
- super._navigateCore(backstackEntry);
+ public _navigateCore(newEntry: BackstackEntry) {
+ super._navigateCore(newEntry);
this._isBack = false;
+ // set frameId here so that we could use it in fragment.transitions
+ newEntry.frameId = this._android.frameId;
+
const activity = this._android.activity;
if (!activity) {
// Activity not associated. In this case we have two execution paths:
@@ -130,15 +213,13 @@ export class Frame extends FrameBase {
startActivity(currentActivity, this._android.frameId);
}
- this._delayedNavigationEntry = backstackEntry;
+ this._delayedNavigationEntry = newEntry;
return;
}
- const manager = activity.getFragmentManager();
-
- // Current Fragment
- const currentFragment = this._currentEntry ? manager.findFragmentByTag(this._currentEntry.fragmentTag) : null;
- const clearHistory = backstackEntry.entry.clearHistory;
+ const manager: android.app.FragmentManager = this._getFragmentManager();
+ const clearHistory = newEntry.entry.clearHistory;
+ const currentEntry = this._currentEntry;
// New Fragment
if (clearHistory) {
@@ -148,22 +229,21 @@ export class Frame extends FrameBase {
navDepth++;
fragmentId++;
const newFragmentTag = `fragment${fragmentId}[${navDepth}]`;
- const newFragment = this.createFragment(backstackEntry, newFragmentTag);
+ const newFragment = this.createFragment(newEntry, newFragmentTag);
const transaction = manager.beginTransaction();
- const animated = this._getIsAnimatedNavigation(backstackEntry.entry);
+ const animated = this._getIsAnimatedNavigation(newEntry.entry);
// NOTE: Don't use transition for the initial nagivation (same as on iOS)
// On API 21+ transition won't be triggered unless there was at least one
// layout pass so we will wait forever for transitionCompleted handler...
// https://github.com/NativeScript/NativeScript/issues/4895
- const navigationTransition = this._currentEntry ? this._getNavigationTransition(backstackEntry.entry) : null;
+ const navigationTransition = this._currentEntry ? this._getNavigationTransition(newEntry.entry) : null;
- _setAndroidFragmentTransitions(animated, navigationTransition, currentFragment, newFragment, transaction, manager);
- if (clearHistory) {
- destroyPages(this.backStack, true);
- this._clearBackStack();
- }
+ _setAndroidFragmentTransitions(animated, navigationTransition, currentEntry, newEntry, transaction, manager, this._android.frameId);
+ // if (clearHistory) {
+ // deleteEntries(this.backStack);
+ // }
- if (currentFragment && animated && !navigationTransition) {
+ if (currentEntry && animated && !navigationTransition) {
transaction.setTransition(android.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
}
@@ -172,11 +252,11 @@ export class Frame extends FrameBase {
}
public _goBackCore(backstackEntry: BackstackEntry) {
+ this._isBack = true;
super._goBackCore(backstackEntry);
navDepth = backstackEntry.navDepth;
- const activity = this._android.activity;
- const manager = activity.getFragmentManager();
+ const manager: android.app.FragmentManager = this._getFragmentManager();
const transaction = manager.beginTransaction();
if (!backstackEntry.fragment) {
@@ -198,26 +278,46 @@ export class Frame extends FrameBase {
transaction.commit();
}
- public _removeBackstackEntries(removed: BackstackEntry[]): void {
- destroyPages(removed, true);
+ public _removeEntry(removed: BackstackEntry): void {
+ super._removeEntry(removed);
+
+ if (removed.fragment) {
+ _clearEntry(removed);
+ }
+
+ removed.fragment = null;
+ removed.viewSavedState = null;
}
public createNativeView() {
- const root = new org.nativescript.widgets.ContentLayout(this._context);
- if (this._containerViewId < 0) {
- this._containerViewId = android.view.View.generateViewId();
- }
- return root;
+ return new org.nativescript.widgets.ContentLayout(this._context);
}
public initNativeView(): void {
super.initNativeView();
this._android.rootViewGroup = this.nativeViewProtected;
+ if (this._containerViewId < 0) {
+ this._containerViewId = android.view.View.generateViewId();
+ }
this._android.rootViewGroup.setId(this._containerViewId);
}
public disposeNativeView() {
- // we should keep the reference to underlying native object, since frame can contain many pages.
+ this._tearDownPending = !!this._executingEntry;
+ const current = this._currentEntry;
+
+ this.backStack.forEach(entry => {
+ // Don't destroy current and executing entries or UI will look blank.
+ // We will do it in setCurrent.
+ if (entry !== this._executingEntry) {
+ clearEntry(entry);
+ }
+ });
+
+ if (current && !this._executingEntry) {
+ clearEntry(current);
+ }
+
this._android.rootViewGroup = null;
super.disposeNativeView();
}
@@ -233,20 +333,6 @@ export class Frame extends FrameBase {
}
}
- public _printNativeBackStack() {
- if (!this._android.activity) {
- return;
- }
- const manager = this._android.activity.getFragmentManager();
- const length = manager.getBackStackEntryCount();
- let i = length - 1;
- console.log(`Fragment Manager Back Stack: `);
- while (i >= 0) {
- const fragment = manager.findFragmentByTag(manager.getBackStackEntryAt(i--).getName());
- console.log(`\t${fragment}`);
- }
- }
-
public _getNavBarVisible(page: Page): boolean {
if (page.actionBarHidden !== undefined) {
return !page.actionBarHidden;
@@ -271,85 +357,21 @@ export class Frame extends FrameBase {
}
});
}
-
- protected _processNavigationContext(navigationContext: NavigationContext) {
- let activity = this._android.activity;
- if (activity) {
- let isForegroundActivity = activity === application.android.foregroundActivity;
- let isPaused = application.android.paused;
-
- if (activity && !isForegroundActivity || (isForegroundActivity && isPaused)) {
- let weakActivity = new WeakRef(activity);
- let resume = (args: application.AndroidActivityEventData) => {
- let weakActivityInstance = weakActivity.get();
- let isCurrent = args.activity === weakActivityInstance;
- if (!weakActivityInstance) {
- if (traceEnabled()) {
- traceWrite(`Frame _processNavigationContext: Drop For Activity GC-ed`, traceCategories.Navigation);
- }
- unsubscribe();
- return;
- }
- if (isCurrent) {
- if (traceEnabled()) {
- traceWrite(`Frame _processNavigationContext: Activity.Resumed, Continue`, traceCategories.Navigation);
- }
- super._processNavigationContext(navigationContext);
- unsubscribe();
- }
- };
- let unsubscribe = () => {
- if (traceEnabled()) {
- traceWrite(`Frame _processNavigationContext: Unsubscribe from Activity.Resumed`, traceCategories.Navigation);
- }
- application.android.off(application.AndroidApplication.activityResumedEvent, resume);
- application.android.off(application.AndroidApplication.activityStoppedEvent, unsubscribe);
- application.android.off(application.AndroidApplication.activityDestroyedEvent, unsubscribe);
- };
-
- if (traceEnabled()) {
- traceWrite(`Frame._processNavigationContext: Subscribe for Activity.Resumed`, traceCategories.Navigation);
- }
- application.android.on(application.AndroidApplication.activityResumedEvent, resume);
- application.android.on(application.AndroidApplication.activityStoppedEvent, unsubscribe);
- application.android.on(application.AndroidApplication.activityDestroyedEvent, unsubscribe);
- return;
- }
- }
- super._processNavigationContext(navigationContext);
- }
}
-function removeEntry(entry: BackstackEntry, page: Page, frame: View, entryUnused: boolean): void {
+function clearEntry(entry: BackstackEntry): void {
if (entry.fragment) {
- if (entryUnused) {
- _clearEntry(entry);
- } else {
- _clearFragment(entry.fragment);
- }
+ _clearFragment(entry);
}
+ entry.recreated = false;
entry.fragment = null;
- if (entryUnused) {
- entry.viewSavedState = null;
- }
-
- if (frame) {
- frame._removeView(page);
+ const page = entry.resolvedPage;
+ if (page._context) {
+ entry.resolvedPage._tearDownUI(true);
}
}
-function destroyPages(backStack: Array, entryUnused: boolean): void {
- if (traceEnabled()) {
- traceWrite(`CLEAR HISTORY`, traceCategories.Navigation);
- }
-
- backStack.forEach((entry) => {
- const page = entry.resolvedPage;
- removeEntry(entry, page, page.frame, entryUnused);
- });
-}
-
let framesCounter = 0;
let framesCache = new Array>();
@@ -445,15 +467,10 @@ class AndroidFrame extends Observable implements AndroidFrameDefinition {
return this.activity.getIntent().getAction() !== android.content.Intent.ACTION_MAIN;
}
- public fragmentForPage(page: Page): any {
- if (!page) {
- return undefined;
- }
-
- let tag = page._fragmentTag;
+ public fragmentForPage(entry: BackstackEntry): any {
+ const tag = entry && entry.fragmentTag;
if (tag) {
- let manager = this.activity.getFragmentManager();
- return manager.findFragmentByTag(tag);
+ return this.owner._getFragmentManager().findFragmentByTag(tag);
}
return undefined;
@@ -466,12 +483,21 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) {
traceWrite(`Finding page for ${fragmentTag}.`, traceCategories.NativeLifecycle);
}
- if (fragmentTag === DIALOG_FRAGMENT_TAG) {
- return;
+ let entry: BackstackEntry;
+ const current = frame._currentEntry;
+ const navigating = frame._executingEntry;
+ if (current && current.fragmentTag === fragmentTag) {
+ entry = current;
+ } else if (navigating && navigating.fragmentTag === fragmentTag) {
+ entry = navigating;
+ }
+
+ let page: Page;
+ if (entry) {
+ entry.recreated = true;
+ page = entry.resolvedPage;
}
- const entry = frame._findEntryForTag(fragmentTag);
- const page = entry ? entry.resolvedPage : undefined;
if (page) {
const callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
callbacks.frame = frame;
@@ -549,7 +575,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
case -40: nextAnimString = "popExit"; break;
}
- let animator = _onFragmentCreateAnimator(fragment, nextAnim);
+ let animator = _onFragmentCreateAnimator(this.entry, fragment, nextAnim, enter);
if (!animator) {
animator = superFunc.call(fragment, transit, enter, nextAnim);
}
@@ -570,7 +596,8 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
// There is no entry set to the fragment, so this must be destroyed fragment that was recreated by Android.
// We should find its corresponding page in our backstack and set it manually.
if (!this.entry) {
- const frameId = fragment.getArguments().getInt(FRAMEID);
+ const args = fragment.getArguments();
+ const frameId = args.getInt(FRAMEID);
const frame = getFrameById(frameId);
if (!frame) {
throw new Error(`Cannot find Frame for ${fragment}`);
@@ -588,19 +615,26 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
const entry = this.entry;
const page = entry.resolvedPage;
- try {
- const frame = this.frame;
- if (page.parent === frame) {
- if (frame.isLoaded && !page.isLoaded) {
- page.onLoaded();
- }
- } else {
- this.frame._addView(page);
+ const frame = this.frame;
+ if (page.parent === frame) {
+ // If we are navigating to a page that was destroyed
+ // reinitialize its UI.
+ if (!page._context) {
+ const context = container && container.getContext() || inflater && inflater.getContext();
+ page._setupUI(context);
}
- } catch (ex) {
- const label = new android.widget.TextView(container.getContext());
- label.setText(ex.message + ", " + ex.stackTrace);
- return label;
+ } else {
+ this.frame._addView(page);
+ }
+
+ // Load page here even if root view is not loaded yet.
+ // Otherwiaw it will show as blank,
+ // The case is Tab->Frame->Page activity recreated, fragments are
+ // created before Tab loads its items.
+ // TODO: addCheck if the fragment is visible so we don't load pages
+ // that are not in the selectedIndex of the Tab!!!!!!
+ if (!page.isLoaded) {
+ page.onLoaded();
}
const savedState = entry.viewSavedState;
@@ -636,14 +670,25 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
superFunc.call(fragment);
}
+ @profile
+ public onStop(fragment: android.app.Fragment, superFunc: Function): void {
+ superFunc.call(fragment);
+ this.entry.resolvedPage.onUnloaded();
+ }
+
@profile
public toStringOverride(fragment: android.app.Fragment, superFunc: Function): string {
- return `${fragment.getTag()}<${this.entry ? this.entry.resolvedPage : ""}>`;
+ const entry = this.entry;
+ if (entry) {
+ return `${entry.fragmentTag}<${entry.resolvedPage}>`;
+ } else {
+ return "NO ENTRY, " + superFunc.call(fragment);
+ }
}
}
class ActivityCallbacksImplementation implements AndroidActivityCallbacks {
- private _rootView: View;
+ public _rootView: View;
@profile
public onCreate(activity: android.app.Activity, savedInstanceState: android.os.Bundle, superFunc: Function): void {
@@ -693,10 +738,10 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks {
// If there is savedInstanceState this call will recreate all fragments that were previously in the navigation.
// We take care of associating them with a Page from our backstack in the onAttachFragment callback.
- // If there is savedInstanceState and activityInitialized is false we are restarted but process was killed.
+ // If there is savedInstanceState and moduleLoaded is false we are restarted but process was killed.
// For now we treat it like first run (e.g. we are not passing savedInstanceState so no fragments are being restored).
// When we add support for application save/load state - revise this logic.
- let isRestart = !!savedInstanceState && activityInitialized;
+ let isRestart = !!savedInstanceState && moduleLoaded;
superFunc.call(activity, isRestart ? savedInstanceState : null);
this._rootView = rootView;
@@ -709,7 +754,7 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks {
frame.navigate(navParam);
}
- activityInitialized = true;
+ moduleLoaded = true;
}
@profile
@@ -760,23 +805,19 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks {
@profile
public onDestroy(activity: any, superFunc: Function): void {
- const rootView = this._rootView;
- if (rootView && rootView._context) {
- rootView._tearDownUI(true);
- }
-
- superFunc.call(activity);
-
if (traceEnabled()) {
traceWrite("NativeScriptActivity.onDestroy();", traceCategories.NativeLifecycle);
}
+ const rootView = this._rootView;
+ if (rootView) {
+ rootView._tearDownUI(true);
+ }
+
const exitArgs = { eventName: application.exitEvent, object: application.android, android: activity };
application.notify(exitArgs);
- if (rootView instanceof Frame) {
- destroyPages(rootView.backStack, false);
- }
+ superFunc.call(activity);
}
@profile
@@ -792,12 +833,29 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks {
cancel: false,
};
application.android.notify(args);
-
if (args.cancel) {
return;
}
- if (!goBack()) {
+ const view = this._rootView;
+ let callSuper = false;
+ if (view instanceof Frame) {
+ callSuper = !view._onBackPressed();
+ } else {
+ const viewArgs = {
+ eventName: "activityBackPressed",
+ object: view,
+ activity: activity,
+ cancel: false,
+ };
+ view.notify(viewArgs);
+
+ if (!viewArgs.cancel) { //&& !view._onBackPressed()) {
+ callSuper = true;
+ }
+ }
+
+ if (callSuper) {
superFunc.call(activity);
}
}
diff --git a/tns-core-modules/ui/frame/frame.d.ts b/tns-core-modules/ui/frame/frame.d.ts
index f26b6986a9..850940feac 100644
--- a/tns-core-modules/ui/frame/frame.d.ts
+++ b/tns-core-modules/ui/frame/frame.d.ts
@@ -110,8 +110,9 @@ export class Frame extends View {
/**
* @private
* @param entry to set as current
+ * @param isBack true when we set current because of back navigation.
*/
- setCurrent(entry: BackstackEntry): void;
+ setCurrent(entry: BackstackEntry, isBack: boolean): void;
/**
* @private
*/
@@ -120,6 +121,10 @@ export class Frame extends View {
* @private
*/
navigationBarHeight: number;
+ /**
+ * @private
+ */
+ _currentEntry: BackstackEntry;
/**
* @private
*/
@@ -139,7 +144,7 @@ export class Frame extends View {
/**
* @private
*/
- _clearBackStack(): void;
+ _updateBackstack(entry: BackstackEntry, isBack: boolean): void;
/**
* @private
*/
@@ -300,6 +305,14 @@ export interface BackstackEntry {
* @private
*/
viewSavedState?: any;
+ /**
+ * @private
+ */
+ frameId?: number;
+ /**
+ * @private
+ */
+ recreated?: boolean;
//@endprivate
}
@@ -360,7 +373,7 @@ export interface AndroidFrame extends Observable {
* Finds the native android.app.Fragment instance created for the specified Page.
* @param page The Page instance to search for.
*/
- fragmentForPage(page: Page): any;
+ fragmentForPage(entry: BackstackEntry): any;
}
export interface AndroidActivityCallbacks {
@@ -382,6 +395,7 @@ export interface AndroidFragmentCallbacks {
onSaveInstanceState(fragment: any, outState: any, superFunc: Function): void;
onDestroyView(fragment: any, superFunc: Function): void;
onDestroy(fragment: any, superFunc: Function): void;
+ onStop(fragment: any, superFunc: Function): void;
toStringOverride(fragment: any, superFunc: Function): string;
}
diff --git a/tns-core-modules/ui/frame/frame.ios.ts b/tns-core-modules/ui/frame/frame.ios.ts
index e8eecd98d1..76a72550d0 100644
--- a/tns-core-modules/ui/frame/frame.ios.ts
+++ b/tns-core-modules/ui/frame/frame.ios.ts
@@ -1,5 +1,5 @@
// Definitions.
-import { iOSFrame as iOSFrameDefinition, BackstackEntry, NavigationTransition } from ".";
+import { iOSFrame as iOSFrameDefinition, BackstackEntry, NavigationTransition, NavigationEntry } from ".";
import { Page } from "../page";
import { profile } from "../../profiling";
@@ -54,12 +54,11 @@ function handleNotification(notification: NSNotification): void {
}
export class Frame extends FrameBase {
+ public viewController: UINavigationControllerImpl;
private _ios: iOSFrame;
- private _paramToNavigate: any;
public _animatedDelegate = UINavigationControllerAnimatedDelegate.new();
public _shouldSkipNativePop: boolean = false;
- public _navigateToEntry: BackstackEntry;
public _widthMeasureSpec: number;
public _heightMeasureSpec: number;
public _right: number;
@@ -69,27 +68,12 @@ export class Frame extends FrameBase {
constructor() {
super();
this._ios = new iOSFrame(this);
+ this.viewController = this._ios.controller;
this.nativeViewProtected = this._ios.controller.view;
}
- @profile
- public onLoaded() {
- super.onLoaded();
-
- if (this._paramToNavigate) {
- this.navigate(this._paramToNavigate);
- this._paramToNavigate = undefined;
- }
- }
-
- public navigate(param: any) {
- if (this.isLoaded) {
- super.navigate(param);
- this._isInitialNavigation = false;
- }
- else {
- this._paramToNavigate = param;
- }
+ public get ios(): iOSFrame {
+ return this._ios;
}
@profile
@@ -103,7 +87,6 @@ export class Frame extends FrameBase {
let clearHistory = backstackEntry.entry.clearHistory;
if (clearHistory) {
- this._clearBackStack();
navDepth = -1;
}
navDepth++;
@@ -264,10 +247,6 @@ export class Frame extends FrameBase {
}
}
- public get ios(): iOSFrame {
- return this._ios;
- }
-
public static get defaultAnimatedNavigation(): boolean {
return FrameBase.defaultAnimatedNavigation;
}
@@ -302,12 +281,6 @@ export class Frame extends FrameBase {
this._heightMeasureSpec = heightMeasureSpec;
let result = this.measurePage(this.currentPage);
- if (this._navigateToEntry && this.currentPage) {
- let newPageSize = this.measurePage(this._navigateToEntry.resolvedPage);
- result.measuredWidth = Math.max(result.measuredWidth, newPageSize.measuredWidth);
- result.measuredHeight = Math.max(result.measuredHeight, newPageSize.measuredHeight);
- }
-
let widthAndState = View.resolveSizeAndState(result.measuredWidth, width, widthMode, 0);
let heightAndState = View.resolveSizeAndState(result.measuredHeight, height, heightMode, 0);
@@ -332,9 +305,6 @@ export class Frame extends FrameBase {
this._bottom = bottom;
this._handleHigherInCallStatusBarIfNeeded();
this.layoutPage(this.currentPage);
- if (this._navigateToEntry && this.currentPage) {
- this.layoutPage(this._navigateToEntry.resolvedPage);
- }
}
public layoutPage(page: Page): void {
@@ -506,12 +476,21 @@ class UINavigationControllerImpl extends UINavigationController {
@profile
public viewWillAppear(animated: boolean): void {
super.viewWillAppear(animated);
- let owner = this._owner.get();
- if (owner && (!owner.isLoaded && !owner.parent)) {
+ const owner = this._owner.get();
+ if (owner && !owner.isLoaded && !owner.parent) {
owner.onLoaded();
}
}
+ @profile
+ public viewDidDisappear(animated: boolean): void {
+ super.viewDidDisappear(animated);
+ const owner = this._owner.get();
+ if (owner && owner.isLoaded && !owner.parent) {
+ owner.onUnloaded();
+ }
+ }
+
@profile
public viewDidLayoutSubviews(): void {
let owner = this._owner.get();
diff --git a/tns-core-modules/ui/page/page.ios.ts b/tns-core-modules/ui/page/page.ios.ts
index 2149092521..7d9b5c5dab 100644
--- a/tns-core-modules/ui/page/page.ios.ts
+++ b/tns-core-modules/ui/page/page.ios.ts
@@ -19,16 +19,15 @@ const ENTRY = "_entry";
const DELEGATE = "_delegate";
function isBackNavigationTo(page: Page, entry): boolean {
- let frame = page.frame;
+ const frame = page.frame;
if (!frame) {
return false;
}
if (frame.navigationQueueIsEmpty()) {
return true;
- }
- else {
- let navigationQueue = (frame)._navigationQueue;
+ } else {
+ const navigationQueue = (frame)._navigationQueue;
for (let i = 0; i < navigationQueue.length; i++) {
if (navigationQueue[i].entry === entry) {
return navigationQueue[i].isBackNavigation;
@@ -65,7 +64,7 @@ class UIViewControllerImpl extends UIViewController {
public shown: boolean;
public static initWithOwner(owner: WeakRef): UIViewControllerImpl {
- let controller = UIViewControllerImpl.new();
+ const controller = UIViewControllerImpl.new();
controller._owner = owner;
controller.automaticallyAdjustsScrollViewInsets = false;
controller.shown = false;
@@ -152,7 +151,7 @@ class UIViewControllerImpl extends UIViewController {
// Don't raise event if currentPage was showing modal page.
if (!page._presentedViewController && newEntry && (!frame || frame.currentPage !== page)) {
- let isBack = isBackNavigationTo(page, newEntry);
+ const isBack = isBackNavigationTo(page, newEntry);
page.onNavigatingTo(newEntry.entry.context, isBack, newEntry.entry.bindingContext);
}
@@ -165,18 +164,14 @@ class UIViewControllerImpl extends UIViewController {
if (frame) {
if (!page.parent) {
- if (!frame._currentEntry) {
- frame._currentEntry = newEntry;
- } else {
- frame._navigateToEntry = newEntry;
- }
-
frame._addView(page);
- frame.remeasureFrame();
} else if (page.parent !== frame) {
throw new Error("Page is already shown on another frame.");
}
+ frame.measurePage(page);
+ frame.layoutPage(page);
+
page.actionBar.update();
}
@@ -206,7 +201,8 @@ class UIViewControllerImpl extends UIViewController {
//https://github.com/NativeScript/NativeScript/issues/1201
page._viewWillDisappear = false;
- let frame = this.navigationController ? (this.navigationController).owner : null;
+ const navigationController = this.navigationController;
+ const frame = navigationController ? (navigationController).owner : null;
// Skip navigation events if modal page is shown.
if (!page._presentedViewController && frame) {
let newEntry = this[ENTRY];
@@ -216,25 +212,24 @@ class UIViewControllerImpl extends UIViewController {
isBack = false;
}
- frame._navigateToEntry = null;
- frame._currentEntry = newEntry;
+ frame._updateBackstack(newEntry, isBack);
+ frame.setCurrent(newEntry, isBack);
frame.remeasureFrame();
frame._updateActionBar(page);
- page.onNavigatedTo(isBack);
-
// If page was shown with custom animation - we need to set the navigationController.delegate to the animatedDelegate.
frame.ios.controller.delegate = this[DELEGATE];
+ frame._processNavigationQueue(page);
+
+ // _processNavigationQueue will shift navigationQueue. Check canGoBack after that.
// Workaround for disabled backswipe on second custom native transition
if (frame.canGoBack()) {
- this.navigationController.interactivePopGestureRecognizer.delegate = this.navigationController;
- this.navigationController.interactivePopGestureRecognizer.enabled = page.enableSwipeBackNavigation;
+ navigationController.interactivePopGestureRecognizer.delegate = navigationController;
+ navigationController.interactivePopGestureRecognizer.enabled = page.enableSwipeBackNavigation;
} else {
- this.navigationController.interactivePopGestureRecognizer.enabled = false;
+ navigationController.interactivePopGestureRecognizer.enabled = false;
}
-
- frame._processNavigationQueue(page);
}
if (!this.presentedViewController) {
@@ -243,7 +238,7 @@ class UIViewControllerImpl extends UIViewController {
// If we clean it when we have viewController then once presented VC is dismissed then
page._presentedViewController = null;
}
- };
+ }
@profile
public viewWillDisappear(animated: boolean): void {
@@ -299,34 +294,38 @@ class UIViewControllerImpl extends UIViewController {
// Manually pop backStack when Back button is pressed or navigating back with edge swipe.
// Don't pop if we are hiding modally shown page.
- let frame = page.frame;
+ // const frame = page.frame;
// We are not modal page, have frame with backstack and navigation queue is empty and currentPage is closed
// then pop our backstack.
- if (!modalParent && frame && frame.backStack.length > 0 && frame.navigationQueueIsEmpty() && frame.currentPage === page) {
- (frame)._backStack.pop();
- }
+ // If we are in frame wich is in tab and tab.selectedControler is not the frame
+ // skip navigation.
+ // const tab = this.tabBarController;
+ // const fireNavigationEvents = !tab
+ // || tab.selectedViewController === this.navigationController;
+
+ // Remove from parent if page was in frame and we navigated back or
+ // navigate forward but current entry is not backstack visible.
+ // Showing page modally will not pass isBack check so currentPage won't be removed from Frame.
+ // const isBack = isBackNavigationFrom(this, page);
+ // if (frame && page.frame === frame &&
+ // (isBack || !frame._isCurrentEntryBackstackVisible)) {
+ // // Remove parent when navigating back.
+ // frame._removeBackstackEntries([_removeBackstackEntries])
+ // frame._removeView(page);
+ // page._frame = null;
+ // }
page._enableLoadedEvents = true;
- // Remove from parent if page was in frame and we navigated back.
- // Showing page modally will not pass isBack check so currentPage won't be removed from Frame.
- let isBack = isBackNavigationFrom(this, page);
- if (isBack) {
- // Remove parent when navigating back.
- frame._removeView(page);
- }
-
// Forward navigation does not remove page from frame so we raise unloaded manually.
if (page.isLoaded) {
page.onUnloaded();
}
- page._enableLoadedEvents = false;
-
- if (!modalParent) {
- // Last raise onNavigatedFrom event if we are not modally shown.
- page.onNavigatedFrom(isBack);
- }
+ // if (!modalParent && fireNavigationEvents) {
+ // // Last raise onNavigatedFrom event if we are not modally shown.
+ // page.onNavigatedFrom(isBack);
+ // }
}
}
@@ -461,10 +460,10 @@ export class Page extends PageBase {
public _updateStatusBarStyle(value?: string) {
const frame = this.frame;
if (this.frame && value) {
- let navigationController = frame.ios.controller;
- let navigationBar = navigationController.navigationBar;
+ const navigationController: UINavigationController = frame.ios.controller;
+ const navigationBar = navigationController.navigationBar;
- navigationBar.barStyle = value === "dark" ? 1 : 0;
+ navigationBar.barStyle = value === "dark" ? UIBarStyle.Black : UIBarStyle.Default;
}
}
@@ -587,9 +586,6 @@ export class Page extends PageBase {
super._removeViewFromNativeVisualTree(view);
}
- [actionBarHiddenProperty.getDefault](): boolean {
- return undefined;
- }
[actionBarHiddenProperty.setNative](value: boolean) {
this._updateEnableSwipeBackNavigation(value);
if (this.isLoaded) {
@@ -602,9 +598,9 @@ export class Page extends PageBase {
return UIBarStyle.Default;
}
[statusBarStyleProperty.setNative](value: string | UIBarStyle) {
- let frame = this.frame;
+ const frame = this.frame;
if (frame) {
- let navigationBar = (frame.ios.controller).navigationBar;
+ const navigationBar = (frame.ios.controller).navigationBar;
if (typeof value === "string") {
navigationBar.barStyle = value === "dark" ? UIBarStyle.Black : UIBarStyle.Default;
} else {