From 3223b42f11edef8afdc6f398d4afd51b1bf8c55a Mon Sep 17 00:00:00 2001 From: Chuck Dries Date: Fri, 19 Jun 2026 16:24:47 -0700 Subject: [PATCH 1/5] Animate entire MFA panel out on finishClose --- ...ultifactorAuthenticationModalNavigator.tsx | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx index 33021747593c..f097769760df 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx @@ -1,8 +1,9 @@ import {BaseNavigationContainer, NavigationIndependentTree} from '@react-navigation/core'; import type {StackCardInterpolationProps} from '@react-navigation/stack'; -import React, {useEffect, useState} from 'react'; +import React, {useCallback, useEffect, useState} from 'react'; import {StyleSheet, View} from 'react-native'; -import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import Animated, {cancelAnimation, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import {scheduleOnRN} from 'react-native-worklets'; import {DefaultCancelConfirmModal} from '@components/MultifactorAuthentication/components/Modals'; import {useMultifactorAuthentication, useMultifactorAuthenticationActions, useMultifactorAuthenticationState} from '@components/MultifactorAuthentication/Context'; import type {MultifactorAuthenticationModalNavigatorInternalParamList} from '@components/MultifactorAuthentication/mfaNavigation'; @@ -15,8 +16,8 @@ import useSidePanelState from '@hooks/useSidePanelState'; import useTheme from '@hooks/useTheme'; import useThemePreference from '@hooks/useThemePreference'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import getNavigationBaseTheme from '@libs/Navigation/getNavigationBaseTheme'; -import Navigation from '@libs/Navigation/Navigation'; import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator'; import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation'; import Presentation from '@libs/Navigation/PlatformStackNavigation/navigationOptions/presentation'; @@ -87,6 +88,9 @@ function MultifactorAuthenticationModalNavigator() { const [phase, setPhase] = useState(isModalOpen ? 'open' : 'closed'); const backdropProgress = useSharedValue(0); + const panelTranslateX = useSharedValue(0); + const {windowWidth} = useWindowDimensions(); + const panelWidth = shouldUseNarrowLayout ? windowWidth : variables.sideBarWidth; const modalCardStyleInterpolator = useModalCardStyleInterpolator(); const CancelConfirmModal = scenario?.modals.cancelConfirmation ?? DefaultCancelConfirmModal; @@ -108,30 +112,44 @@ function MultifactorAuthenticationModalNavigator() { }, }; + const finishClose = useCallback(() => { + resetMfaNavigation(); + setPhase('closed'); + dispatch({type: 'RESET'}); + }, [dispatch]); + useEffect(() => { if (phase === 'open') { + panelTranslateX.set(0); backdropProgress.set(withTiming(1, {duration: CONST.ANIMATED_TRANSITION})); return; } if (phase !== 'closing') { return; } - if (mfaNavigationRef.isReady() && mfaNavigationRef.canGoBack()) { - mfaNavigationRef.goBack(); - } + // Slide the whole overlay out as one unit and fade the backdrop in parallel. Animating a single + // screen (e.g. via goBack) would reveal screens still on the stack beneath the outcome screen + // before the overlay unmounts. backdropProgress.set(withTiming(0, {duration: CONST.ANIMATED_TRANSITION})); - const handle = Navigation.runAfterUpcomingTransition(() => { - resetMfaNavigation(); - setPhase('closed'); - dispatch({type: 'RESET'}); - }); - return () => handle.cancel(); - }, [phase, backdropProgress, dispatch]); + panelTranslateX.set( + withTiming(panelWidth, {duration: CONST.ANIMATED_TRANSITION}, (finished) => { + if (!finished) { + return; + } + scheduleOnRN(finishClose); + }), + ); + return () => cancelAnimation(panelTranslateX); + }, [phase, backdropProgress, panelTranslateX, panelWidth, finishClose]); const backdropAnimatedStyle = useAnimatedStyle(() => ({ opacity: backdropProgress.get() * variables.overlayOpacity, })); + const panelAnimatedStyle = useAnimatedStyle(() => ({ + transform: [{translateX: panelTranslateX.get()}], + })); + if (phase === 'closed') { return null; } @@ -153,7 +171,9 @@ function MultifactorAuthenticationModalNavigator() { /> )} - + {isStackReadyToMount && ( )} - + Date: Tue, 23 Jun 2026 16:26:03 -0700 Subject: [PATCH 2/5] Revert "Animate entire MFA panel out on finishClose" This reverts commit 3223b42f11edef8afdc6f398d4afd51b1bf8c55a. --- ...ultifactorAuthenticationModalNavigator.tsx | 50 ++++++------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx index f097769760df..33021747593c 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx @@ -1,9 +1,8 @@ import {BaseNavigationContainer, NavigationIndependentTree} from '@react-navigation/core'; import type {StackCardInterpolationProps} from '@react-navigation/stack'; -import React, {useCallback, useEffect, useState} from 'react'; +import React, {useEffect, useState} from 'react'; import {StyleSheet, View} from 'react-native'; -import Animated, {cancelAnimation, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; -import {scheduleOnRN} from 'react-native-worklets'; +import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import {DefaultCancelConfirmModal} from '@components/MultifactorAuthentication/components/Modals'; import {useMultifactorAuthentication, useMultifactorAuthenticationActions, useMultifactorAuthenticationState} from '@components/MultifactorAuthentication/Context'; import type {MultifactorAuthenticationModalNavigatorInternalParamList} from '@components/MultifactorAuthentication/mfaNavigation'; @@ -16,8 +15,8 @@ import useSidePanelState from '@hooks/useSidePanelState'; import useTheme from '@hooks/useTheme'; import useThemePreference from '@hooks/useThemePreference'; import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; import getNavigationBaseTheme from '@libs/Navigation/getNavigationBaseTheme'; +import Navigation from '@libs/Navigation/Navigation'; import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator'; import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation'; import Presentation from '@libs/Navigation/PlatformStackNavigation/navigationOptions/presentation'; @@ -88,9 +87,6 @@ function MultifactorAuthenticationModalNavigator() { const [phase, setPhase] = useState(isModalOpen ? 'open' : 'closed'); const backdropProgress = useSharedValue(0); - const panelTranslateX = useSharedValue(0); - const {windowWidth} = useWindowDimensions(); - const panelWidth = shouldUseNarrowLayout ? windowWidth : variables.sideBarWidth; const modalCardStyleInterpolator = useModalCardStyleInterpolator(); const CancelConfirmModal = scenario?.modals.cancelConfirmation ?? DefaultCancelConfirmModal; @@ -112,44 +108,30 @@ function MultifactorAuthenticationModalNavigator() { }, }; - const finishClose = useCallback(() => { - resetMfaNavigation(); - setPhase('closed'); - dispatch({type: 'RESET'}); - }, [dispatch]); - useEffect(() => { if (phase === 'open') { - panelTranslateX.set(0); backdropProgress.set(withTiming(1, {duration: CONST.ANIMATED_TRANSITION})); return; } if (phase !== 'closing') { return; } - // Slide the whole overlay out as one unit and fade the backdrop in parallel. Animating a single - // screen (e.g. via goBack) would reveal screens still on the stack beneath the outcome screen - // before the overlay unmounts. + if (mfaNavigationRef.isReady() && mfaNavigationRef.canGoBack()) { + mfaNavigationRef.goBack(); + } backdropProgress.set(withTiming(0, {duration: CONST.ANIMATED_TRANSITION})); - panelTranslateX.set( - withTiming(panelWidth, {duration: CONST.ANIMATED_TRANSITION}, (finished) => { - if (!finished) { - return; - } - scheduleOnRN(finishClose); - }), - ); - return () => cancelAnimation(panelTranslateX); - }, [phase, backdropProgress, panelTranslateX, panelWidth, finishClose]); + const handle = Navigation.runAfterUpcomingTransition(() => { + resetMfaNavigation(); + setPhase('closed'); + dispatch({type: 'RESET'}); + }); + return () => handle.cancel(); + }, [phase, backdropProgress, dispatch]); const backdropAnimatedStyle = useAnimatedStyle(() => ({ opacity: backdropProgress.get() * variables.overlayOpacity, })); - const panelAnimatedStyle = useAnimatedStyle(() => ({ - transform: [{translateX: panelTranslateX.get()}], - })); - if (phase === 'closed') { return null; } @@ -171,9 +153,7 @@ function MultifactorAuthenticationModalNavigator() { /> )} - + {isStackReadyToMount && ( )} - + Date: Tue, 23 Jun 2026 16:27:01 -0700 Subject: [PATCH 3/5] Remove override for OUTCOME_SCREENs in mfaNavigation `navigate` --- .../MultifactorAuthentication/mfaNavigation.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/components/MultifactorAuthentication/mfaNavigation.ts b/src/components/MultifactorAuthentication/mfaNavigation.ts index 127f341845a8..6fa1fee65b13 100644 --- a/src/components/MultifactorAuthentication/mfaNavigation.ts +++ b/src/components/MultifactorAuthentication/mfaNavigation.ts @@ -71,16 +71,6 @@ function navigate Date: Wed, 24 Jun 2026 08:26:32 -0700 Subject: [PATCH 4/5] Fix lint mfaNavigation.ts --- src/components/MultifactorAuthentication/mfaNavigation.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/MultifactorAuthentication/mfaNavigation.ts b/src/components/MultifactorAuthentication/mfaNavigation.ts index 6fa1fee65b13..6b009b1fca3c 100644 --- a/src/components/MultifactorAuthentication/mfaNavigation.ts +++ b/src/components/MultifactorAuthentication/mfaNavigation.ts @@ -1,6 +1,5 @@ import {createNavigationContainerRef, StackActions} from '@react-navigation/native'; import type {MultifactorAuthenticationModalNavigatorParamList} from '@libs/Navigation/types'; -import CONFIG from '@src/CONFIG'; import SCREENS from '@src/SCREENS'; /** @@ -16,12 +15,6 @@ type MultifactorAuthenticationModalNavigatorInternalParamList = MultifactorAuthe const mfaNavigationRef = createNavigationContainerRef(); -// Outcome screens are terminal states the flow ends on. -const OUTCOME_SCREENS = new Set([ - SCREENS.MULTIFACTOR_AUTHENTICATION.OUTCOME_SUCCESS, - SCREENS.MULTIFACTOR_AUTHENTICATION.OUTCOME_FAILURE, -]); - // Screens that live inside this independent overlay navigator. REVOKE and AUTHORIZE_TRANSACTION are intentionally excluded: they // render in the main RHP modal stack, not this tree. const MFA_OVERLAY_SCREENS = new Set([ From 737496509ac10b44a1aa0ce817139d6f38b646c9 Mon Sep 17 00:00:00 2001 From: Chuck Dries Date: Wed, 24 Jun 2026 08:37:30 -0700 Subject: [PATCH 5/5] MFA Navigator: use popToTop for dismissal --- .../Navigators/MultifactorAuthenticationModalNavigator.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx index 33021747593c..1bb34094ed90 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx @@ -1,4 +1,4 @@ -import {BaseNavigationContainer, NavigationIndependentTree} from '@react-navigation/core'; +import {BaseNavigationContainer, NavigationIndependentTree, StackActions} from '@react-navigation/core'; import type {StackCardInterpolationProps} from '@react-navigation/stack'; import React, {useEffect, useState} from 'react'; import {StyleSheet, View} from 'react-native'; @@ -117,7 +117,7 @@ function MultifactorAuthenticationModalNavigator() { return; } if (mfaNavigationRef.isReady() && mfaNavigationRef.canGoBack()) { - mfaNavigationRef.goBack(); + mfaNavigationRef.dispatch(StackActions.popToTop()); } backdropProgress.set(withTiming(0, {duration: CONST.ANIMATED_TRANSITION})); const handle = Navigation.runAfterUpcomingTransition(() => {