Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type {ParamListBase, ScreenLayoutArgs} from '@react-navigation/native';
import {StackNavigationOptions} from '@react-navigation/stack';

Check failure on line 2 in src/libs/Navigation/AppNavigator/Navigators/InteractionManagerLayout.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

All imports in the declaration are only used as types. Use `import type`

Check failure on line 2 in src/libs/Navigation/AppNavigator/Navigators/InteractionManagerLayout.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

All imports in the declaration are only used as types. Use `import type`
import {useLayoutEffect} from 'react';
import {PlatformStackNavigationOptions, PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNavigation/types';

Check failure on line 4 in src/libs/Navigation/AppNavigator/Navigators/InteractionManagerLayout.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

All imports in the declaration are only used as types. Use `import type`

Check failure on line 4 in src/libs/Navigation/AppNavigator/Navigators/InteractionManagerLayout.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

All imports in the declaration are only used as types. Use `import type`
import {endTransition, startTransition} from '@libs/Navigation/TransitionTracker';

Check failure on line 5 in src/libs/Navigation/AppNavigator/Navigators/InteractionManagerLayout.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

'startTransition' is defined but never used

Check failure on line 5 in src/libs/Navigation/AppNavigator/Navigators/InteractionManagerLayout.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

'endTransition' is defined but never used

Check failure on line 5 in src/libs/Navigation/AppNavigator/Navigators/InteractionManagerLayout.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'startTransition' is defined but never used

Check failure on line 5 in src/libs/Navigation/AppNavigator/Navigators/InteractionManagerLayout.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'endTransition' is defined but never used

function InteractionManagerLayout({
children,
navigation,
options,
route,
}: ScreenLayoutArgs<ParamListBase, string, StackNavigationOptions | PlatformStackNavigationOptions, PlatformStackNavigationProp<ParamListBase>>) {
useLayoutEffect(() => {
const transitionStartListener = navigation.addListener('transitionStart', () => {
console.log('transitionStart', route?.name);

Check failure on line 15 in src/libs/Navigation/AppNavigator/Navigators/InteractionManagerLayout.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

Unexpected console statement. Only these console methods are allowed: debug, error

Check failure on line 15 in src/libs/Navigation/AppNavigator/Navigators/InteractionManagerLayout.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Unexpected console statement. Only these console methods are allowed: debug, error
// startTransition();
});
const transitionEndListener = navigation.addListener('transitionEnd', () => {
console.log('transitionEnd', route?.name);

Check failure on line 19 in src/libs/Navigation/AppNavigator/Navigators/InteractionManagerLayout.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

Unexpected console statement. Only these console methods are allowed: debug, error

Check failure on line 19 in src/libs/Navigation/AppNavigator/Navigators/InteractionManagerLayout.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Unexpected console statement. Only these console methods are allowed: debug, error
// endTransition();
});

return () => {
transitionStartListener();
transitionEndListener();
};
}, [navigation, options.animation, route?.name]);

return children;
}

export default InteractionManagerLayout;
35 changes: 27 additions & 8 deletions src/libs/Navigation/Navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {CommonActions, getPathFromState, StackActions} from '@react-navigation/n
import {Str} from 'expensify-common';
// eslint-disable-next-line you-dont-need-lodash-underscore/omit
import omit from 'lodash/omit';
import {DeviceEventEmitter, Dimensions, InteractionManager} from 'react-native';
import {DeviceEventEmitter, Dimensions} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import type {Writable} from 'type-fest';
Expand Down Expand Up @@ -39,6 +39,7 @@ import setNavigationActionToMicrotaskQueue from './helpers/setNavigationActionTo
import {linkingConfig} from './linkingConfig';
import {SPLIT_TO_SIDEBAR} from './linkingConfig/RELATIONS';
import navigationRef from './navigationRef';
import {runAfterTransition} from './TransitionTracker';
import type {
NavigationPartialRoute,
NavigationRef,
Expand Down Expand Up @@ -313,6 +314,10 @@ function navigate(route: Route, options?: LinkToOptions) {
}
linkTo(navigationRef.current, route, options);
closeSidePanelOnNarrowScreen();

if (options?.afterTransition) {
runAfterTransition(options.afterTransition);
}
}
/**
* When routes are compared to determine whether the fallback route passed to the goUp function is in the state,
Expand Down Expand Up @@ -377,10 +382,13 @@ type GoBackOptions = {
* In that case we want to goUp to a country picker with any params so we don't compare them.
*/
compareParams?: boolean;
// Callback to execute after the navigation transition animation completes.
afterTransition?: () => void | undefined;
};

const defaultGoBackOptions: Required<GoBackOptions> = {
compareParams: true,
afterTransition: () => {},
};

/**
Expand Down Expand Up @@ -455,6 +463,9 @@ function goBack(backToRoute?: Route, options?: GoBackOptions) {

if (backToRoute) {
goUp(backToRoute, options);
if (options?.afterTransition) {
runAfterTransition(options.afterTransition);
}
return;
}

Expand All @@ -464,6 +475,9 @@ function goBack(backToRoute?: Route, options?: GoBackOptions) {
}

navigationRef.current?.goBack();
if (options?.afterTransition) {
runAfterTransition(options.afterTransition);
}
}

/**
Expand Down Expand Up @@ -696,13 +710,13 @@ function getTopmostSuperWideRHPReportID(state: NavigationState = navigationRef.g
*
* @param options - Configuration object
* @param options.ref - Navigation ref to use (defaults to navigationRef)
* @param options.callback - Optional callback to execute after the modal has finished closing.
* The callback fires when RightModalNavigator unmounts.
* @param options.callback - Optional callback to execute when the modal unmounts (fires on MODAL_EVENTS.CLOSED).
* @param options.afterTransition - Optional callback to execute after the navigation transition animation completes.
*
* For detailed information about dismissing modals,
* see the NAVIGATION.md documentation.
*/
const dismissModal = ({ref = navigationRef, callback}: {ref?: NavigationRef; callback?: () => void} = {}) => {
const dismissModal = ({ref = navigationRef, callback, afterTransition}: {ref?: NavigationRef; callback?: () => void; afterTransition?: () => void} = {}) => {
clearSelectedText();
isNavigationReady().then(() => {
if (callback) {
Expand All @@ -713,6 +727,10 @@ const dismissModal = ({ref = navigationRef, callback}: {ref?: NavigationRef; cal
}

ref.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL});

if (afterTransition) {
runAfterTransition(afterTransition);
}
});
};

Expand Down Expand Up @@ -743,10 +761,10 @@ const dismissModalWithReport = ({reportID, reportActionID, referrer, backTo}: Re
navigate(reportRoute, {forceReplace: true});
return;
}
dismissModal();
// eslint-disable-next-line @typescript-eslint/no-deprecated
InteractionManager.runAfterInteractions(() => {
navigate(reportRoute);
dismissModal({
afterTransition: () => {
navigate(reportRoute);
},
});
});
};
Expand Down Expand Up @@ -952,6 +970,7 @@ export default {
getTopmostSuperWideRHPReportID,
getTopmostSearchReportRouteParams,
navigateBackToLastSuperWideRHPScreen,
runAfterTransition,
};

export {navigationRef, getDeepestFocusedScreenName, isTwoFactorSetupScreen, shouldShowRequire2FAPage};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
PlatformStackNavigatorProps,
PlatformStackRouterOptions,
} from '@libs/Navigation/PlatformStackNavigation/types';
import InteractionManagerLayout from '@libs/Navigation/AppNavigator/Navigators/InteractionManagerLayout';

function createPlatformStackNavigatorComponent<RouterOptions extends PlatformStackRouterOptions = PlatformStackRouterOptions>(
displayName: string,
Expand All @@ -35,6 +36,7 @@
defaultCentralScreen,
parentRoute,
persistentScreens,
screenLayout,
...props
}: PlatformStackNavigatorProps<ParamListBase>) {
const {
Expand Down Expand Up @@ -62,6 +64,7 @@
sidebarScreen,
parentRoute,
persistentScreens,
screenLayout: (props) => <InteractionManagerLayout {...props} />,

Check failure on line 67 in src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

Prop spreading is forbidden

Check failure on line 67 in src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

'props' is already declared in the upper scope on line 40 column 12

Check failure on line 67 in src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “PlatformNavigator” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true

Check failure on line 67 in src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx

View workflow job for this annotation

GitHub Actions / typecheck

Type '{ route: RouteProp<ParamListBase, string>; options: StackNavigationOptions | PlatformStackNavigationOptions; navigation: string; theme: Theme; children: ReactElement<...>; }' is not assignable to type 'ScreenLayoutArgs<ParamListBase, string, StackNavigationOptions | PlatformStackNavigationOptions, PlatformStackNavigationProp<ParamListBase>>'.

Check failure on line 67 in src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Prop spreading is forbidden

Check failure on line 67 in src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'props' is already declared in the upper scope on line 40 column 12

Check failure on line 67 in src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “PlatformNavigator” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true
},
convertToWebNavigationOptions,
);
Expand Down
54 changes: 54 additions & 0 deletions src/libs/Navigation/TransitionTracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
type CancelHandle = {cancel: () => void};

let activeTransitionCount = 0;
const pendingCallbacks: Array<() => void> = [];

function startTransition(): void {
activeTransitionCount++;
}

function endTransition(): void {
activeTransitionCount = Math.max(0, activeTransitionCount - 1);
if (activeTransitionCount === 0) {
flushCallbacks();
}
}

function flushCallbacks(): void {
while (pendingCallbacks.length > 0) {
const cb = pendingCallbacks.shift();
cb?.();
}
}

function runAfterTransition(callback: () => void): CancelHandle {
if (activeTransitionCount === 0) {
let cancelled = false;
queueMicrotask(() => {

Check failure on line 27 in src/libs/Navigation/TransitionTracker.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Prefer an early return to a conditionally-wrapped function body

Check failure on line 27 in src/libs/Navigation/TransitionTracker.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Prefer an early return to a conditionally-wrapped function body
if (!cancelled) {
callback();
}
});
return {
cancel: () => {
cancelled = true;
},
};
}

pendingCallbacks.push(callback);
return {
cancel: () => {
const idx = pendingCallbacks.indexOf(callback);
if (idx !== -1) {
pendingCallbacks.splice(idx, 1);
}
},
};
}

function isTransitioning(): boolean {
return activeTransitionCount > 0;
}

export {startTransition, endTransition, runAfterTransition, isTransitioning};
4 changes: 3 additions & 1 deletion src/libs/Navigation/helpers/linkTo/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ type ActionPayload = {

type LinkToOptions = {
// To explicitly set the action type to replace.
forceReplace: boolean;
forceReplace?: boolean;
// Callback to execute after the navigation transition animation completes.
afterTransition?: () => void;
};

export type {ActionPayload, LinkToOptions};
Loading