Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d04010c
fix(core): Better handling for nested frames
CatchABus Mar 1, 2025
6681717
test: Updated tab-view automated tests
CatchABus Mar 1, 2025
1edaae1
chore: Renamed new load children method
CatchABus Mar 1, 2025
f8991e6
chore: Reverted test changes
CatchABus Mar 3, 2025
8052bd8
ref: Use event listeners to process nested frame entries in the corre…
CatchABus Mar 3, 2025
f557233
chore: Making onLoaded less busy
CatchABus Mar 3, 2025
0d1b8d9
ref: Use frameEntryLoaded event to properly process entry
CatchABus Mar 16, 2025
67eb657
test: Added automated test
CatchABus Apr 1, 2025
af0bb62
test: Corrected automated test for android
CatchABus May 11, 2025
132527f
fix: dispose native transitions during view disposal
CatchABus May 20, 2025
720d4de
chore: minor refactoring and deprecation warnings
CatchABus May 25, 2025
1fcf2fb
fix: restore transitions for the correct entry
CatchABus May 25, 2025
dcee835
fix: added nested frames old fix to the right place
CatchABus May 26, 2025
e5cf840
chore: added optional chaining
CatchABus May 31, 2025
a25fc5b
chore: added comment for clarification
CatchABus May 31, 2025
d43d852
chore: added weakref precaution
CatchABus May 31, 2025
a0a74b3
fix: corrected frame new unloaded handling
CatchABus May 31, 2025
8f40627
fix: avoid removing needed frame entry listeners
CatchABus Jun 2, 2025
360ba2d
chore: small refactor
CatchABus Jun 2, 2025
417c6d6
chore: set callback earlier
CatchABus Jun 2, 2025
2c64037
ref: Removed new frame entry init implementation
CatchABus Jun 29, 2025
8a54695
fix: Allow attach listener to handle recreating entry fragment
CatchABus Jun 29, 2025
211f28f
fix: Emit frame create timeout during attach state
CatchABus Jun 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions packages/core/ui/core/view-base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* Returns an instance of a view (if found), otherwise undefined.
*/
export function getAncestor(view: ViewBaseDefinition, criterion: string | { new () }): ViewBaseDefinition {
let matcher: (view: ViewBaseDefinition) => boolean = null;
export function getAncestor<T extends ViewBaseDefinition = ViewBaseDefinition>(view: T, criterion: string | { new () }): T {
let matcher: (view: ViewBaseDefinition) => view is T;

if (typeof criterion === 'string') {
matcher = (view: ViewBaseDefinition) => view.typeName === criterion;
matcher = (view: ViewBaseDefinition): view is T => view.typeName === criterion;
} 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) {
Expand Down
2 changes: 0 additions & 2 deletions packages/core/ui/frame/callbacks/activity-callbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { AndroidActivityBackPressedEventData, AndroidActivityNewIntentEventData,
import { Trace } from '../../../trace';
import { View } from '../../core/view';

import { _clearEntry, _clearFragment, _getAnimatedEntries, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions } from '../fragment.transitions';

import { profile } from '../../../profiling';
import { isEmbedded, setEmbeddedView } from '../../embedding';

Expand Down
78 changes: 67 additions & 11 deletions packages/core/ui/frame/fragment.transitions.android.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Definitions.
import { NavigationType } from './frame-common';
import { NavigationType, TransitionState } from './frame-common';
import { NavigationTransition, BackstackEntry } from '.';

// Types.
Expand Down Expand Up @@ -152,7 +152,7 @@ export function _setAndroidFragmentTransitions(animated: boolean, navigationTran
setupCurrentFragmentExplodeTransition(navigationTransition, currentEntry);
}
} 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);

setupNewFragmentCustomTransition(navigationTransition, newEntry, flipTransition);
Expand Down Expand Up @@ -282,23 +282,28 @@ export function _getAnimatedEntries(frameId: number): Set<BackstackEntry> {

export function _updateTransitions(entry: ExpandedEntry): void {
const fragment = entry.fragment;

if (!fragment) {
return;
}

const enterTransitionListener = entry.enterTransitionListener;
if (enterTransitionListener && fragment) {
if (enterTransitionListener) {
fragment.setEnterTransition(enterTransitionListener.transition);
}

const exitTransitionListener = entry.exitTransitionListener;
if (exitTransitionListener && fragment) {
if (exitTransitionListener) {
fragment.setExitTransition(exitTransitionListener.transition);
}

const reenterTransitionListener = entry.reenterTransitionListener;
if (reenterTransitionListener && fragment) {
if (reenterTransitionListener) {
fragment.setReenterTransition(reenterTransitionListener.transition);
}

const returnTransitionListener = entry.returnTransitionListener;
if (returnTransitionListener && fragment) {
if (returnTransitionListener) {
fragment.setReturnTransition(returnTransitionListener.transition);
}
}
Expand Down Expand Up @@ -428,6 +433,16 @@ function addToWaitingQueue(entry: ExpandedEntry): void {
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 {
const fragment: androidx.fragment.app.Fragment = entry.fragment;
const exitListener = entry.exitTransitionListener;
Expand Down Expand Up @@ -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 {
clearEntry(entry, false);
clearTransitions(entry, false);
}

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

const fragment: androidx.fragment.app.Fragment = entry.fragment;
Expand Down Expand Up @@ -569,7 +625,7 @@ function setReturnTransition(navigationTransition: NavigationTransition, entry:

function setupNewFragmentSlideTransition(navTransition: NavigationTransition, entry: ExpandedEntry, name: string): void {
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) {
case 'left':
setEnterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.RIGHT));
Expand All @@ -594,7 +650,7 @@ function setupNewFragmentSlideTransition(navTransition: NavigationTransition, en
}

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) {
case 'left':
setExitTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.LEFT));
Expand Down
10 changes: 9 additions & 1 deletion packages/core/ui/frame/fragment.transitions.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NavigationTransition, BackstackEntry } from '.';
import { NavigationTransition, BackstackEntry, TransitionState } from '.';

/**
* @private
Expand All @@ -20,6 +20,14 @@ export function _updateTransitions(entry: BackstackEntry): void;
* Reverse transitions from entry to fragment if any.
*/
export function _reverseTransitions(previousEntry: BackstackEntry, currentEntry: BackstackEntry): boolean;
/**
* @private
*/
export function _getTransitionState(entry: BackstackEntry): TransitionState;
/**
* @private
*/
export function _restoreTransitionState(snapshot: TransitionState): void;
/**
* @private
* Called when entry is removed from backstack (either back navigation or
Expand Down
10 changes: 5 additions & 5 deletions packages/core/ui/frame/frame-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ export class FrameBase extends CustomLayoutView {
return true;
} else if (top) {
let parentFrameCanGoBack = false;
let parentFrame = <FrameBase>getAncestor(top, 'Frame');
let parentFrame = getAncestor(top, 'Frame');

while (parentFrame && !parentFrameCanGoBack) {
if (parentFrame && parentFrame.canGoBack()) {
parentFrameCanGoBack = true;
} else {
parentFrame = <FrameBase>getAncestor(parentFrame, 'Frame');
parentFrame = getAncestor(parentFrame, 'Frame');
}
Comment on lines +79 to 86
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use FrameBase instead of string literal to get proper type-inference

getAncestor is now generic and will only infer FrameBase (and thus expose canGoBack, goBack, etc.) when the second argument is the class reference, not the string literal.
Passing 'Frame' collapses the inferred type to ViewBase | undefined, forcing the compiler to fall back to any and silently losing type-safety.

-			let parentFrame = getAncestor(top, 'Frame');
+			let parentFrame: FrameBase | undefined = getAncestor(top, FrameBase);-					parentFrame = getAncestor(parentFrame, 'Frame');
+					parentFrame = getAncestor(parentFrame, FrameBase);

Besides safer autocompletion, this change will surface misspelled members at compile-time instead of runtime.
(No behavioural impact.)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let parentFrame = getAncestor(top, 'Frame');
while (parentFrame && !parentFrameCanGoBack) {
if (parentFrame && parentFrame.canGoBack()) {
parentFrameCanGoBack = true;
} else {
parentFrame = <FrameBase>getAncestor(parentFrame, 'Frame');
parentFrame = getAncestor(parentFrame, 'Frame');
}
// Before: let parentFrame = getAncestor(top, 'Frame');
let parentFrame: FrameBase | undefined = getAncestor(top, FrameBase);
while (parentFrame && !parentFrameCanGoBack) {
if (parentFrame && parentFrame.canGoBack()) {
parentFrameCanGoBack = true;
} else {
// Before: parentFrame = getAncestor(parentFrame, 'Frame');
parentFrame = getAncestor(parentFrame, FrameBase);
}
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 82-82: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🤖 Prompt for AI Agents
In packages/core/ui/frame/frame-common.ts around lines 79 to 86, replace the
string literal 'Frame' passed to getAncestor with the class reference FrameBase
to enable proper type inference. This change ensures getAncestor returns a
FrameBase type, exposing methods like canGoBack with full type safety and better
autocompletion. Update all occurrences of 'Frame' in getAncestor calls within
this block to FrameBase accordingly.

}

Expand Down Expand Up @@ -122,7 +122,6 @@ export class FrameBase extends CustomLayoutView {
@profile
public onLoaded() {
super.onLoaded();

this._processNextNavigationEntry();
}

Expand Down Expand Up @@ -323,9 +322,10 @@ export class FrameBase extends CustomLayoutView {
}

private isNestedWithin(parentFrameCandidate: FrameBase): boolean {
let frameAncestor: FrameBase = this;
let frameAncestor = this as FrameBase;

while (frameAncestor) {
frameAncestor = <FrameBase>getAncestor(frameAncestor, FrameBase);
frameAncestor = getAncestor(frameAncestor, FrameBase);
if (frameAncestor === parentFrameCandidate) {
return true;
}
Expand Down
Loading