Skip to content

Commit ba8e4c8

Browse files
authored
Merge pull request Expensify#71818 from callstack-internal/fix-70920
Navigate to expense detail page after adding a receipt
2 parents e7a8d34 + 11249bb commit ba8e4c8

5 files changed

Lines changed: 63 additions & 21 deletions

File tree

src/CONST/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6819,7 +6819,7 @@ const CONST = {
68196819
CASH_BACK: 'earnedCashback',
68206820
},
68216821

6822-
EXCLUDE_FROM_LAST_VISITED_PATH: [SCREENS.NOT_FOUND, SCREENS.SAML_SIGN_IN, SCREENS.VALIDATE_LOGIN, SCREENS.MIGRATED_USER_WELCOME_MODAL.ROOT] as string[],
6822+
EXCLUDE_FROM_LAST_VISITED_PATH: [SCREENS.NOT_FOUND, SCREENS.SAML_SIGN_IN, SCREENS.VALIDATE_LOGIN, SCREENS.MIGRATED_USER_WELCOME_MODAL.ROOT, SCREENS.MONEY_REQUEST.STEP_SCAN] as string[],
68236823

68246824
CANCELLATION_TYPE: {
68256825
MANUAL: 'manual',

src/libs/Navigation/NavigationRoot.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: N
131131

132132
const isTransitioning = path?.includes(ROUTES.TRANSITION_BETWEEN_APPS);
133133

134+
// If we have a transition URL, don't restore last visited path - let React Navigation handle it
135+
// This prevents reusing deep links after logout regardless of authentication status
136+
if (isTransitioning) {
137+
return undefined;
138+
}
139+
134140
// If the user haven't completed the flow, we want to always redirect them to the onboarding flow.
135141
// We also make sure that the user is authenticated, isn't part of a group workspace, isn't in the transition flow & wasn't invited to NewDot.
136142
if (!CONFIG.IS_HYBRID_APP && !hasNonPersonalPolicy && !isOnboardingCompleted && !wasInvitedToNewDot && authenticated && !isTransitioning) {
@@ -146,20 +152,20 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: N
146152
);
147153
}
148154

149-
// If there is no lastVisitedPath, we can do early return. We won't modify the default behavior.
150-
// The same applies to HybridApp, as we always define the route to which we want to transition.
151-
if (!shouldOpenLastVisitedPath(lastVisitedPath) || CONFIG.IS_HYBRID_APP) {
152-
return undefined;
153-
}
155+
if (shouldOpenLastVisitedPath(lastVisitedPath) && authenticated) {
156+
// Only skip restoration if there's a specific deep link that's not the root
157+
// This allows restoration when app is killed and reopened without a deep link
158+
const isRootPath = !path || path === '' || path === '/';
159+
const isSpecificDeepLink = path && !isRootPath;
154160

155-
// If the user opens the root of app "/" it will be parsed to empty string "".
156-
// If the path is defined and different that empty string we don't want to modify the default behavior.
157-
if (path) {
158-
return;
161+
if (!isSpecificDeepLink) {
162+
Log.info('Restoring last visited path on app startup', false, {lastVisitedPath, initialUrl, path});
163+
return getAdaptedStateFromPath(lastVisitedPath, linkingConfig.config);
164+
}
159165
}
160166

161-
// Otherwise we want to redirect the user to the last visited path.
162-
return getAdaptedStateFromPath(lastVisitedPath, linkingConfig.config);
167+
// Default behavior - let React Navigation handle the initial state
168+
return undefined;
163169

164170
// The initialState value is relevant only on the first render.
165171
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps

src/libs/actions/App.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Issue - https://github.com/Expensify/App/issues/26719
2+
import {getPathFromState} from '@react-navigation/native';
23
import {Str} from 'expensify-common';
34
import type {AppStateStatus} from 'react-native';
45
import {AppState} from 'react-native';
@@ -11,7 +12,8 @@ import * as Browser from '@libs/Browser';
1112
import DateUtils from '@libs/DateUtils';
1213
import Log from '@libs/Log';
1314
import getCurrentUrl from '@libs/Navigation/currentUrl';
14-
import Navigation from '@libs/Navigation/Navigation';
15+
import {linkingConfig} from '@libs/Navigation/linkingConfig';
16+
import Navigation, {navigationRef} from '@libs/Navigation/Navigation';
1517
import Performance from '@libs/Performance';
1618
import {isPublicRoom, isValidReport} from '@libs/ReportUtils';
1719
import {isLoggingInAsNewUser as isLoggingInAsNewUserSessionUtils} from '@libs/SessionUtils';
@@ -207,10 +209,36 @@ function setAppLoading(isLoading: boolean) {
207209
Onyx.set(ONYXKEYS.IS_LOADING_APP, isLoading);
208210
}
209211

212+
/**
213+
* Saves the current navigation path to lastVisitedPath before app goes to background
214+
*/
215+
function saveCurrentPathBeforeBackground() {
216+
try {
217+
if (!navigationRef.isReady()) {
218+
return;
219+
}
220+
221+
const currentState = navigationRef.getRootState();
222+
if (!currentState) {
223+
return;
224+
}
225+
226+
const currentPath = getPathFromState(currentState, linkingConfig.config);
227+
228+
if (currentPath) {
229+
Log.info('Saving current path before background', false, {currentPath});
230+
updateLastVisitedPath(currentPath);
231+
}
232+
} catch (error) {
233+
Log.warn('Failed to save current path before background', {error});
234+
}
235+
}
236+
210237
let appState: AppStateStatus;
211238
AppState.addEventListener('change', (nextAppState) => {
212239
if (nextAppState.match(/inactive|background/) && appState === 'active') {
213240
Log.info('Flushing logs as app is going inactive', true, {}, true);
241+
saveCurrentPathBeforeBackground();
214242
}
215243
appState = nextAppState;
216244
});

src/libs/shouldOpenLastVisitedPath/index.native.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/pages/iou/request/step/IOURequestStepScan/index.native.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import HapticFeedback from '@libs/HapticFeedback';
5050
import {navigateToParticipantPage} from '@libs/IOUUtils';
5151
import Log from '@libs/Log';
5252
import Navigation from '@libs/Navigation/Navigation';
53+
import navigationRef from '@libs/Navigation/navigationRef';
5354
import {getManagerMcTestParticipant, getParticipantsOption, getReportOption} from '@libs/OptionsListUtils';
5455
import {isPaidGroupPolicy} from '@libs/PolicyUtils';
5556
import {findSelfDMReportID, generateReportID, getPolicyExpenseChat, isArchivedReport, isPolicyExpenseChat} from '@libs/ReportUtils';
@@ -73,6 +74,7 @@ import type {GpsPoint} from '@userActions/IOU';
7374
import {buildOptimisticTransactionAndCreateDraft, removeDraftTransactions, removeTransactionReceipt} from '@userActions/TransactionEdit';
7475
import CONST from '@src/CONST';
7576
import ONYXKEYS from '@src/ONYXKEYS';
77+
import type {Route} from '@src/ROUTES';
7678
import ROUTES from '@src/ROUTES';
7779
import type {Policy} from '@src/types/onyx';
7880
import type {Participant} from '@src/types/onyx/IOU';
@@ -531,10 +533,22 @@ function IOURequestStepScan({
531533

532534
const updateScanAndNavigate = useCallback(
533535
(file: FileObject, source: string) => {
534-
navigateBack();
536+
// Fix for the issue where the navigation state is lost after returning from device settings https://github.com/Expensify/App/issues/65992
537+
const navigationState = navigationRef.current?.getState();
538+
const reportsSplitNavigator = navigationState?.routes?.findLast((route) => route.name === 'ReportsSplitNavigator');
539+
const hasLostNavigationsState = reportsSplitNavigator && !reportsSplitNavigator.state;
540+
if (hasLostNavigationsState) {
541+
if (backTo) {
542+
Navigation.navigate(backTo as Route);
543+
} else {
544+
Navigation.navigate(ROUTES.HOME);
545+
}
546+
} else {
547+
navigateBack();
548+
}
535549
replaceReceipt({transactionID: initialTransactionID, file: file as File, source});
536550
},
537-
[initialTransactionID],
551+
[initialTransactionID, backTo],
538552
);
539553

540554
/**

0 commit comments

Comments
 (0)