fix(android): improved handling for nested frames (#10713)

This commit is contained in:
Dimitris-Rafail Katsampas
2025-07-21 06:52:55 +03:00
committed by GitHub
parent 2377b6ae98
commit 86524229bc
7 changed files with 163 additions and 99 deletions

View File

@ -117,12 +117,13 @@ export interface ShowModalOptions {
* @param criterion - The type of ancestor view we are looking for. Could be a string containing a class name or an actual type. * @param criterion - The type of ancestor view we are looking for. Could be a string containing a class name or an actual type.
* Returns an instance of a view (if found), otherwise undefined. * Returns an instance of a view (if found), otherwise undefined.
*/ */
export function getAncestor(view: ViewBaseDefinition, criterion: string | { new () }): ViewBaseDefinition { export function getAncestor<T extends ViewBaseDefinition = ViewBaseDefinition>(view: T, criterion: string | { new () }): T {
let matcher: (view: ViewBaseDefinition) => boolean = null; let matcher: (view: ViewBaseDefinition) => view is T;
if (typeof criterion === 'string') { if (typeof criterion === 'string') {
matcher = (view: ViewBaseDefinition) => view.typeName === criterion; matcher = (view: ViewBaseDefinition): view is T => view.typeName === criterion;
} else { } else {
matcher = (view: ViewBaseDefinition) => view instanceof criterion; matcher = (view: ViewBaseDefinition): view is T => view instanceof criterion;
} }
for (let parent = view.parent; parent != null; parent = parent.parent) { for (let parent = view.parent; parent != null; parent = parent.parent) {

View File

@ -5,8 +5,6 @@ import { AndroidActivityBackPressedEventData, AndroidActivityNewIntentEventData,
import { Trace } from '../../../trace'; import { Trace } from '../../../trace';
import { View } from '../../core/view'; import { View } from '../../core/view';
import { _clearEntry, _clearFragment, _getAnimatedEntries, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions } from '../fragment.transitions';
import { profile } from '../../../profiling'; import { profile } from '../../../profiling';
import { isEmbedded, setEmbeddedView } from '../../embedding'; import { isEmbedded, setEmbeddedView } from '../../embedding';

View File

@ -1,5 +1,5 @@
// Definitions. // Definitions.
import { NavigationType } from './frame-common'; import { NavigationType, TransitionState } from './frame-common';
import { NavigationTransition, BackstackEntry } from '.'; import { NavigationTransition, BackstackEntry } from '.';
// Types. // Types.
@ -152,7 +152,7 @@ export function _setAndroidFragmentTransitions(animated: boolean, navigationTran
setupCurrentFragmentExplodeTransition(navigationTransition, currentEntry); setupCurrentFragmentExplodeTransition(navigationTransition, currentEntry);
} }
} else if (name.indexOf('flip') === 0) { } else if (name.indexOf('flip') === 0) {
const direction = name.substr('flip'.length) || 'right'; //Extract the direction from the string const direction = name.substring('flip'.length) || 'right'; //Extract the direction from the string
const flipTransition = new FlipTransition(direction, navigationTransition.duration, navigationTransition.curve); const flipTransition = new FlipTransition(direction, navigationTransition.duration, navigationTransition.curve);
setupNewFragmentCustomTransition(navigationTransition, newEntry, flipTransition); setupNewFragmentCustomTransition(navigationTransition, newEntry, flipTransition);
@ -282,23 +282,28 @@ export function _getAnimatedEntries(frameId: number): Set<BackstackEntry> {
export function _updateTransitions(entry: ExpandedEntry): void { export function _updateTransitions(entry: ExpandedEntry): void {
const fragment = entry.fragment; const fragment = entry.fragment;
if (!fragment) {
return;
}
const enterTransitionListener = entry.enterTransitionListener; const enterTransitionListener = entry.enterTransitionListener;
if (enterTransitionListener && fragment) { if (enterTransitionListener) {
fragment.setEnterTransition(enterTransitionListener.transition); fragment.setEnterTransition(enterTransitionListener.transition);
} }
const exitTransitionListener = entry.exitTransitionListener; const exitTransitionListener = entry.exitTransitionListener;
if (exitTransitionListener && fragment) { if (exitTransitionListener) {
fragment.setExitTransition(exitTransitionListener.transition); fragment.setExitTransition(exitTransitionListener.transition);
} }
const reenterTransitionListener = entry.reenterTransitionListener; const reenterTransitionListener = entry.reenterTransitionListener;
if (reenterTransitionListener && fragment) { if (reenterTransitionListener) {
fragment.setReenterTransition(reenterTransitionListener.transition); fragment.setReenterTransition(reenterTransitionListener.transition);
} }
const returnTransitionListener = entry.returnTransitionListener; const returnTransitionListener = entry.returnTransitionListener;
if (returnTransitionListener && fragment) { if (returnTransitionListener) {
fragment.setReturnTransition(returnTransitionListener.transition); fragment.setReturnTransition(returnTransitionListener.transition);
} }
} }
@ -428,6 +433,16 @@ function addToWaitingQueue(entry: ExpandedEntry): void {
entries.add(entry); entries.add(entry);
} }
function cloneExpandedTransitionListener(expandedTransitionListener: ExpandedTransitionListener) {
if (!expandedTransitionListener) {
return null;
}
const cloneTransition = expandedTransitionListener.transition.clone();
return addNativeTransitionListener(expandedTransitionListener.entry, cloneTransition);
}
function clearExitAndReenterTransitions(entry: ExpandedEntry, removeListener: boolean): void { function clearExitAndReenterTransitions(entry: ExpandedEntry, removeListener: boolean): void {
const fragment: androidx.fragment.app.Fragment = entry.fragment; const fragment: androidx.fragment.app.Fragment = entry.fragment;
const exitListener = entry.exitTransitionListener; const exitListener = entry.exitTransitionListener;
@ -469,15 +484,56 @@ function clearExitAndReenterTransitions(entry: ExpandedEntry, removeListener: bo
} }
} }
export function _getTransitionState(entry: ExpandedEntry): TransitionState {
let transitionState: TransitionState;
if (entry.enterTransitionListener && entry.exitTransitionListener) {
transitionState = {
enterTransitionListener: cloneExpandedTransitionListener(entry.enterTransitionListener),
exitTransitionListener: cloneExpandedTransitionListener(entry.exitTransitionListener),
reenterTransitionListener: cloneExpandedTransitionListener(entry.reenterTransitionListener),
returnTransitionListener: cloneExpandedTransitionListener(entry.returnTransitionListener),
transitionName: entry.transitionName,
entry,
};
} else {
transitionState = null;
}
return transitionState;
}
export function _restoreTransitionState(snapshot: TransitionState): void {
const entry = snapshot.entry as ExpandedEntry;
if (snapshot.enterTransitionListener) {
entry.enterTransitionListener = snapshot.enterTransitionListener;
}
if (snapshot.exitTransitionListener) {
entry.exitTransitionListener = snapshot.exitTransitionListener;
}
if (snapshot.reenterTransitionListener) {
entry.reenterTransitionListener = snapshot.reenterTransitionListener;
}
if (snapshot.returnTransitionListener) {
entry.returnTransitionListener = snapshot.returnTransitionListener;
}
entry.transitionName = snapshot.transitionName;
}
export function _clearFragment(entry: ExpandedEntry): void { export function _clearFragment(entry: ExpandedEntry): void {
clearEntry(entry, false); clearTransitions(entry, false);
} }
export function _clearEntry(entry: ExpandedEntry): void { export function _clearEntry(entry: ExpandedEntry): void {
clearEntry(entry, true); clearTransitions(entry, true);
} }
function clearEntry(entry: ExpandedEntry, removeListener: boolean): void { function clearTransitions(entry: ExpandedEntry, removeListener: boolean): void {
clearExitAndReenterTransitions(entry, removeListener); clearExitAndReenterTransitions(entry, removeListener);
const fragment: androidx.fragment.app.Fragment = entry.fragment; const fragment: androidx.fragment.app.Fragment = entry.fragment;
@ -569,7 +625,7 @@ function setReturnTransition(navigationTransition: NavigationTransition, entry:
function setupNewFragmentSlideTransition(navTransition: NavigationTransition, entry: ExpandedEntry, name: string): void { function setupNewFragmentSlideTransition(navTransition: NavigationTransition, entry: ExpandedEntry, name: string): void {
setupCurrentFragmentSlideTransition(navTransition, entry, name); setupCurrentFragmentSlideTransition(navTransition, entry, name);
const direction = name.substr('slide'.length) || 'left'; //Extract the direction from the string const direction = name.substring('slide'.length) || 'left'; //Extract the direction from the string
switch (direction) { switch (direction) {
case 'left': case 'left':
setEnterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.RIGHT)); setEnterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.RIGHT));
@ -594,7 +650,7 @@ function setupNewFragmentSlideTransition(navTransition: NavigationTransition, en
} }
function setupCurrentFragmentSlideTransition(navTransition: NavigationTransition, entry: ExpandedEntry, name: string): void { function setupCurrentFragmentSlideTransition(navTransition: NavigationTransition, entry: ExpandedEntry, name: string): void {
const direction = name.substr('slide'.length) || 'left'; //Extract the direction from the string const direction = name.substring('slide'.length) || 'left'; //Extract the direction from the string
switch (direction) { switch (direction) {
case 'left': case 'left':
setExitTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.LEFT)); setExitTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.LEFT));

View File

@ -1,4 +1,4 @@
import { NavigationTransition, BackstackEntry } from '.'; import { NavigationTransition, BackstackEntry, TransitionState } from '.';
/** /**
* @private * @private
@ -20,6 +20,14 @@ export function _updateTransitions(entry: BackstackEntry): void;
* Reverse transitions from entry to fragment if any. * Reverse transitions from entry to fragment if any.
*/ */
export function _reverseTransitions(previousEntry: BackstackEntry, currentEntry: BackstackEntry): boolean; export function _reverseTransitions(previousEntry: BackstackEntry, currentEntry: BackstackEntry): boolean;
/**
* @private
*/
export function _getTransitionState(entry: BackstackEntry): TransitionState;
/**
* @private
*/
export function _restoreTransitionState(snapshot: TransitionState): void;
/** /**
* @private * @private
* Called when entry is removed from backstack (either back navigation or * Called when entry is removed from backstack (either back navigation or

View File

@ -76,13 +76,13 @@ export class FrameBase extends CustomLayoutView {
return true; return true;
} else if (top) { } else if (top) {
let parentFrameCanGoBack = false; let parentFrameCanGoBack = false;
let parentFrame = <FrameBase>getAncestor(top, 'Frame'); let parentFrame = getAncestor(top, 'Frame');
while (parentFrame && !parentFrameCanGoBack) { while (parentFrame && !parentFrameCanGoBack) {
if (parentFrame && parentFrame.canGoBack()) { if (parentFrame && parentFrame.canGoBack()) {
parentFrameCanGoBack = true; parentFrameCanGoBack = true;
} else { } else {
parentFrame = <FrameBase>getAncestor(parentFrame, 'Frame'); parentFrame = getAncestor(parentFrame, 'Frame');
} }
} }
@ -122,7 +122,6 @@ export class FrameBase extends CustomLayoutView {
@profile @profile
public onLoaded() { public onLoaded() {
super.onLoaded(); super.onLoaded();
this._processNextNavigationEntry(); this._processNextNavigationEntry();
} }
@ -323,9 +322,10 @@ export class FrameBase extends CustomLayoutView {
} }
private isNestedWithin(parentFrameCandidate: FrameBase): boolean { private isNestedWithin(parentFrameCandidate: FrameBase): boolean {
let frameAncestor: FrameBase = this; let frameAncestor = this as FrameBase;
while (frameAncestor) { while (frameAncestor) {
frameAncestor = <FrameBase>getAncestor(frameAncestor, FrameBase); frameAncestor = getAncestor(frameAncestor, FrameBase);
if (frameAncestor === parentFrameCandidate) { if (frameAncestor === parentFrameCandidate) {
return true; return true;
} }

View File

@ -11,7 +11,7 @@ import { Trace } from '../../trace';
import { View } from '../core/view'; import { View } from '../core/view';
import { _stack, FrameBase, NavigationType } from './frame-common'; import { _stack, FrameBase, NavigationType } from './frame-common';
import { _clearEntry, _clearFragment, _getAnimatedEntries, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions, addNativeTransitionListener } from './fragment.transitions'; import { _clearEntry, _clearFragment, _getAnimatedEntries, _getTransitionState, _restoreTransitionState, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions, addNativeTransitionListener } from './fragment.transitions';
import { profile } from '../../profiling'; import { profile } from '../../profiling';
import { android as androidUtils } from '../../utils/native-helper'; import { android as androidUtils } from '../../utils/native-helper';
@ -28,6 +28,7 @@ const FRAMEID = '_frameId';
const CALLBACKS = '_callbacks'; const CALLBACKS = '_callbacks';
const ownerSymbol = Symbol('_owner'); const ownerSymbol = Symbol('_owner');
const isPendingDetachSymbol = Symbol('_isPendingDetach');
let navDepth = -1; let navDepth = -1;
let fragmentId = -1; let fragmentId = -1;
@ -58,6 +59,12 @@ function getAttachListener(): android.view.View.OnAttachStateChangeListener {
if (owner) { if (owner) {
owner._onDetachedFromWindow(); owner._onDetachedFromWindow();
} }
if (view[isPendingDetachSymbol]) {
delete view[isPendingDetachSymbol];
view.removeOnAttachStateChangeListener(this);
view[ownerSymbol] = null;
}
}, },
}); });
@ -73,7 +80,10 @@ export class Frame extends FrameBase {
private _containerViewId = -1; private _containerViewId = -1;
private _tearDownPending = false; private _tearDownPending = false;
private _attachedToWindow = false; private _attachedToWindow = false;
private _wasReset = false; /**
* This property indicates that the view is to be reused as a root view or has been previously disposed.
*/
private _isReset = false;
private _cachedTransitionState: TransitionState; private _cachedTransitionState: TransitionState;
private _frameCreateTimeout: NodeJS.Timeout; private _frameCreateTimeout: NodeJS.Timeout;
@ -143,8 +153,9 @@ export class Frame extends FrameBase {
} }
this._attachedToWindow = true; this._attachedToWindow = true;
this._wasReset = false; this._isReset = false;
this._processNextNavigationEntry(); this._processNextNavigationEntry();
this._ensureEntryFragment();
} }
_onDetachedFromWindow(): void { _onDetachedFromWindow(): void {
@ -162,12 +173,12 @@ export class Frame extends FrameBase {
return; return;
} }
// in case the activity is "reset" using resetRootView we must wait for // in case the activity is "reset" using resetRootView or disposed we must wait for
// the attachedToWindow event to make the first navigation or it will crash // the attachedToWindow event to make the first navigation or it will crash
// https://github.com/NativeScript/NativeScript/commit/9dd3e1a8076e5022e411f2f2eeba34aabc68d112 // https://github.com/NativeScript/NativeScript/commit/9dd3e1a8076e5022e411f2f2eeba34aabc68d112
// though we should not do it on app "start" // though we should not do it on app "start"
// or it will create a "flash" to activity background color // or it will create a "flash" to activity background color
if (this._wasReset && !this._attachedToWindow) { if (this._isReset && !this._attachedToWindow) {
return; return;
} }
@ -194,7 +205,7 @@ export class Frame extends FrameBase {
// simulated navigation (NoTransition, zero duration animator) and thus the fragment immediately disappears; // simulated navigation (NoTransition, zero duration animator) and thus the fragment immediately disappears;
// the user only sees the animation of the entering fragment as per its specific enter animation settings. // the user only sees the animation of the entering fragment as per its specific enter animation settings.
// NOTE: we are restoring the animation settings in Frame.setCurrent(...) as navigation completes asynchronously // NOTE: we are restoring the animation settings in Frame.setCurrent(...) as navigation completes asynchronously
const cachedTransitionState = getTransitionState(this._currentEntry); const cachedTransitionState = _getTransitionState(this._currentEntry);
if (cachedTransitionState) { if (cachedTransitionState) {
this._cachedTransitionState = cachedTransitionState; this._cachedTransitionState = cachedTransitionState;
@ -228,7 +239,7 @@ export class Frame extends FrameBase {
public _onRootViewReset(): void { public _onRootViewReset(): void {
super._onRootViewReset(); super._onRootViewReset();
// used to handle the "first" navigate differently on first run and on reset // used to handle the "first" navigate differently on first run and on reset
this._wasReset = true; this._isReset = true;
// call this AFTER the super call to ensure descendants apply their rootview-reset logic first // call this AFTER the super call to ensure descendants apply their rootview-reset logic first
// i.e. in a scenario with nested frames / frame with tabview let the descendandt cleanup the inner // i.e. in a scenario with nested frames / frame with tabview let the descendandt cleanup the inner
// fragments first, and then cleanup the parent fragments // fragments first, and then cleanup the parent fragments
@ -241,6 +252,33 @@ export class Frame extends FrameBase {
this.backgroundColor = this._originalBackground; this.backgroundColor = this._originalBackground;
this._originalBackground = null; this._originalBackground = null;
} }
this._ensureEntryFragment();
super.onLoaded();
}
onUnloaded() {
super.onUnloaded();
if (typeof this._frameCreateTimeout === 'number') {
clearTimeout(this._frameCreateTimeout);
this._frameCreateTimeout = null;
}
}
/**
* TODO: Check if this fragment precaution is still needed
*/
private _ensureEntryFragment(): void {
// in case the activity is "reset" using resetRootView or disposed we must wait for
// the attachedToWindow event to make the first navigation or it will crash
// https://github.com/NativeScript/NativeScript/commit/9dd3e1a8076e5022e411f2f2eeba34aabc68d112
// though we should not do it on app "start"
// or it will create a "flash" to activity background color
if (this._isReset && !this._attachedToWindow) {
return;
}
this._frameCreateTimeout = setTimeout(() => { this._frameCreateTimeout = setTimeout(() => {
// there's a bug with nested frames where sometimes the nested fragment is not recreated at all // there's a bug with nested frames where sometimes the nested fragment is not recreated at all
// so we manually check on loaded event if the fragment is not recreated and recreate it // so we manually check on loaded event if the fragment is not recreated and recreate it
@ -255,17 +293,9 @@ export class Frame extends FrameBase {
transaction.commitAllowingStateLoss(); transaction.commitAllowingStateLoss();
} }
} }
}, 0);
super.onLoaded();
}
onUnloaded() {
super.onUnloaded();
if (typeof this._frameCreateTimeout === 'number') {
clearTimeout(this._frameCreateTimeout);
this._frameCreateTimeout = null; this._frameCreateTimeout = null;
} }, 0);
} }
private disposeCurrentFragment(): void { private disposeCurrentFragment(): void {
@ -352,7 +382,7 @@ export class Frame extends FrameBase {
// restore cached animation settings if we just completed simulated first navigation (no animation) // restore cached animation settings if we just completed simulated first navigation (no animation)
if (this._cachedTransitionState) { if (this._cachedTransitionState) {
restoreTransitionState(this._currentEntry, this._cachedTransitionState); _restoreTransitionState(this._cachedTransitionState);
this._cachedTransitionState = null; this._cachedTransitionState = null;
} }
@ -391,7 +421,7 @@ export class Frame extends FrameBase {
// HACK: This @profile decorator creates a circular dependency // HACK: This @profile decorator creates a circular dependency
// HACK: because the function parameter type is evaluated with 'typeof' // HACK: because the function parameter type is evaluated with 'typeof'
@profile @profile
public _navigateCore(newEntry: any) { public _navigateCore(newEntry: BackstackEntry) {
// should be (newEntry: BackstackEntry) // should be (newEntry: BackstackEntry)
super._navigateCore(newEntry); super._navigateCore(newEntry);
@ -487,19 +517,19 @@ export class Frame extends FrameBase {
if (removed.fragment) { if (removed.fragment) {
_clearEntry(removed); _clearEntry(removed);
removed.fragment = null;
} }
removed.fragment = null;
removed.viewSavedState = null; removed.viewSavedState = null;
} }
protected _disposeBackstackEntry(entry: BackstackEntry): void { protected _disposeBackstackEntry(entry: BackstackEntry): void {
if (entry.fragment) { if (entry.fragment) {
_clearFragment(entry); _clearFragment(entry);
entry.fragment = null;
} }
entry.recreated = false; entry.recreated = false;
entry.fragment = null;
super._disposeBackstackEntry(entry); super._disposeBackstackEntry(entry);
} }
@ -518,9 +548,12 @@ export class Frame extends FrameBase {
public initNativeView(): void { public initNativeView(): void {
super.initNativeView(); super.initNativeView();
const listener = getAttachListener(); const listener = getAttachListener();
this.nativeViewProtected.addOnAttachStateChangeListener(listener); const nativeView = this.nativeViewProtected as android.view.ViewGroup;
this.nativeViewProtected[ownerSymbol] = this;
this._android.rootViewGroup = this.nativeViewProtected; nativeView.addOnAttachStateChangeListener(listener);
nativeView[ownerSymbol] = this;
this._android.rootViewGroup = nativeView;
if (this._containerViewId < 0) { if (this._containerViewId < 0) {
this._containerViewId = android.view.View.generateViewId(); this._containerViewId = android.view.View.generateViewId();
} }
@ -528,12 +561,23 @@ export class Frame extends FrameBase {
} }
public disposeNativeView() { public disposeNativeView() {
const nativeView = this.nativeViewProtected as android.view.ViewGroup;
const listener = getAttachListener(); const listener = getAttachListener();
this.nativeViewProtected.removeOnAttachStateChangeListener(listener);
this.nativeViewProtected[ownerSymbol] = null; // There are cases like root view when detach listener is not called upon removing view from view-tree
// so mark those views as pending and remove listener once the view is detached
if (nativeView.isAttachedToWindow()) {
nativeView[isPendingDetachSymbol] = true;
} else {
nativeView.removeOnAttachStateChangeListener(listener);
nativeView[ownerSymbol] = null;
}
this._tearDownPending = !!this._executingContext; this._tearDownPending = !!this._executingContext;
const current = this._currentEntry; const current = this._currentEntry;
const executingEntry = this._executingContext ? this._executingContext.entry : null; const executingEntry = this._executingContext ? this._executingContext.entry : null;
this.backStack.forEach((entry) => { this.backStack.forEach((entry) => {
// Don't destroy current and executing entries or UI will look blank. // Don't destroy current and executing entries or UI will look blank.
// We will do it in setCurrent. // We will do it in setCurrent.
@ -546,6 +590,12 @@ export class Frame extends FrameBase {
this._disposeBackstackEntry(current); this._disposeBackstackEntry(current);
} }
// Dispose cached transition and store it again if view ever gets re-used
this._cachedTransitionState = null;
// Mark as reset in order to properly re-initialize fragments if view ever gets re-used
this._isReset = true;
this._android.rootViewGroup = null; this._android.rootViewGroup = null;
this._removeFromFrameStack(); this._removeFromFrameStack();
super.disposeNativeView(); super.disposeNativeView();
@ -603,55 +653,6 @@ export function reloadPage(context?: ModuleContext): void {
// attach on global, so it can be overwritten in NativeScript Angular // attach on global, so it can be overwritten in NativeScript Angular
global.__onLiveSyncCore = Frame.reloadPage; global.__onLiveSyncCore = Frame.reloadPage;
function cloneExpandedTransitionListener(expandedTransitionListener: any) {
if (!expandedTransitionListener) {
return null;
}
const cloneTransition = expandedTransitionListener.transition.clone();
return addNativeTransitionListener(expandedTransitionListener.entry, cloneTransition);
}
function getTransitionState(entry: BackstackEntry): TransitionState {
const expandedEntry = <any>entry;
const transitionState = <TransitionState>{};
if (expandedEntry.enterTransitionListener && expandedEntry.exitTransitionListener) {
transitionState.enterTransitionListener = cloneExpandedTransitionListener(expandedEntry.enterTransitionListener);
transitionState.exitTransitionListener = cloneExpandedTransitionListener(expandedEntry.exitTransitionListener);
transitionState.reenterTransitionListener = cloneExpandedTransitionListener(expandedEntry.reenterTransitionListener);
transitionState.returnTransitionListener = cloneExpandedTransitionListener(expandedEntry.returnTransitionListener);
transitionState.transitionName = expandedEntry.transitionName;
transitionState.entry = entry;
} else {
return null;
}
return transitionState;
}
function restoreTransitionState(entry: BackstackEntry, snapshot: TransitionState): void {
const expandedEntry = <any>entry;
if (snapshot.enterTransitionListener) {
expandedEntry.enterTransitionListener = snapshot.enterTransitionListener;
}
if (snapshot.exitTransitionListener) {
expandedEntry.exitTransitionListener = snapshot.exitTransitionListener;
}
if (snapshot.reenterTransitionListener) {
expandedEntry.reenterTransitionListener = snapshot.reenterTransitionListener;
}
if (snapshot.returnTransitionListener) {
expandedEntry.returnTransitionListener = snapshot.returnTransitionListener;
}
expandedEntry.transitionName = snapshot.transitionName;
}
let framesCounter = 0; let framesCounter = 0;
const framesCache = new Array<WeakRef<AndroidFrame>>(); const framesCache = new Array<WeakRef<AndroidFrame>>();

View File

@ -97,7 +97,7 @@ export class Frame extends FrameBase {
// !!! THIS PROFILE DECORATOR CREATES A CIRCULAR DEPENDENCY // !!! THIS PROFILE DECORATOR CREATES A CIRCULAR DEPENDENCY
// !!! BECAUSE THE PARAMETER TYPE IS EVALUATED WITH TYPEOF // !!! BECAUSE THE PARAMETER TYPE IS EVALUATED WITH TYPEOF
@profile @profile
public _navigateCore(backstackEntry: any) { public _navigateCore(backstackEntry: BackstackEntry) {
super._navigateCore(backstackEntry); super._navigateCore(backstackEntry);
const viewController: UIViewController = backstackEntry.resolvedPage.ios; const viewController: UIViewController = backstackEntry.resolvedPage.ios;
@ -507,7 +507,7 @@ class UINavigationControllerImpl extends UINavigationController {
} }
} }
private animateWithDuration(navigationTransition: NavigationTransition, nativeTransition: UIViewAnimationTransition, transitionType: string, baseCallback: Function): void { private animateWithDuration(navigationTransition: NavigationTransition, nativeTransition: UIViewAnimationTransition, transitionType: string, baseCallback: () => void): void {
const duration = navigationTransition.duration ? navigationTransition.duration / 1000 : CORE_ANIMATION_DEFAULTS.duration; const duration = navigationTransition.duration ? navigationTransition.duration / 1000 : CORE_ANIMATION_DEFAULTS.duration;
const curve = _getNativeCurve(navigationTransition); const curve = _getNativeCurve(navigationTransition);