Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6825,7 +6825,7 @@ const CONST = {
CASH_BACK: 'earnedCashback',
},

EXCLUDE_FROM_LAST_VISITED_PATH: [SCREENS.NOT_FOUND, SCREENS.SAML_SIGN_IN, SCREENS.VALIDATE_LOGIN, SCREENS.MIGRATED_USER_WELCOME_MODAL.ROOT] as string[],
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[],

CANCELLATION_TYPE: {
MANUAL: 'manual',
Expand Down
28 changes: 17 additions & 11 deletions src/libs/Navigation/NavigationRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: N

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

// If we have a transition URL, don't restore last visited path - let React Navigation handle it
// This prevents reusing deep links after logout regardless of authentication status
if (isTransitioning) {
return undefined;
}

// If the user haven't completed the flow, we want to always redirect them to the onboarding flow.
// 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.
if (!CONFIG.IS_HYBRID_APP && !hasNonPersonalPolicy && !isOnboardingCompleted && !wasInvitedToNewDot && authenticated && !isTransitioning) {
Expand All @@ -146,20 +152,20 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: N
);
}

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

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

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

// The initialState value is relevant only on the first render.
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
Expand Down
30 changes: 29 additions & 1 deletion src/libs/actions/App.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Issue - https://github.com/Expensify/App/issues/26719
import {getPathFromState} from '@react-navigation/native';
import {Str} from 'expensify-common';
import type {AppStateStatus} from 'react-native';
import {AppState} from 'react-native';
Expand All @@ -11,7 +12,8 @@ import * as Browser from '@libs/Browser';
import DateUtils from '@libs/DateUtils';
import Log from '@libs/Log';
import getCurrentUrl from '@libs/Navigation/currentUrl';
import Navigation from '@libs/Navigation/Navigation';
import {linkingConfig} from '@libs/Navigation/linkingConfig';
import Navigation, {navigationRef} from '@libs/Navigation/Navigation';
import Performance from '@libs/Performance';
import {isPublicRoom, isValidReport} from '@libs/ReportUtils';
import {isLoggingInAsNewUser as isLoggingInAsNewUserSessionUtils} from '@libs/SessionUtils';
Expand Down Expand Up @@ -207,10 +209,36 @@ function setAppLoading(isLoading: boolean) {
Onyx.set(ONYXKEYS.IS_LOADING_APP, isLoading);
}

/**
* Saves the current navigation path to lastVisitedPath before app goes to background
*/
function saveCurrentPathBeforeBackground() {
try {
if (!navigationRef.isReady()) {
return;
}

const currentState = navigationRef.getRootState();
if (!currentState) {
return;
}

const currentPath = getPathFromState(currentState, linkingConfig.config);

if (currentPath) {
Log.info('Saving current path before background', false, {currentPath});
updateLastVisitedPath(currentPath);
}
} catch (error) {
Log.warn('Failed to save current path before background', {error});
}
}

let appState: AppStateStatus;
AppState.addEventListener('change', (nextAppState) => {
if (nextAppState.match(/inactive|background/) && appState === 'active') {
Log.info('Flushing logs as app is going inactive', true, {}, true);
saveCurrentPathBeforeBackground();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey, I'd like to make sure about this part :)

Why do we need to save it here? Can't we save it when we open this page like it's done in parseAndLogRoute?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Its added because we are trying to fix the issue when you are going to change some permissions in the OS settings then app navigation state is reset and user is navigated to Inbox. this is for saving the navigation state before doing that to be able then to navigate to the screen which user was before.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

OK, I understand that part. I'm just wondering if we can save this information earlier. Now, saveCurrentPathBeforeBackground will be called every time the app goes to the background. Is the navigation state different when we go to the background and when we're on the scanned receipt screen?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see CONST.EXCLUDE_FROM_LAST_VISITED_PATH has been modified, so updateLastVisitedPath won't be called in parseAndLogRoute when we're on this page

}
appState = nextAppState;
});
Expand Down
6 changes: 0 additions & 6 deletions src/libs/shouldOpenLastVisitedPath/index.native.ts

This file was deleted.

18 changes: 16 additions & 2 deletions src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import HapticFeedback from '@libs/HapticFeedback';
import {navigateToParticipantPage} from '@libs/IOUUtils';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import navigationRef from '@libs/Navigation/navigationRef';
import {getManagerMcTestParticipant, getParticipantsOption, getReportOption} from '@libs/OptionsListUtils';
import {isPaidGroupPolicy} from '@libs/PolicyUtils';
import {findSelfDMReportID, generateReportID, getPolicyExpenseChat, isArchivedReport, isPolicyExpenseChat} from '@libs/ReportUtils';
Expand All @@ -74,6 +75,7 @@ import type {GpsPoint} from '@userActions/IOU';
import {buildOptimisticTransactionAndCreateDraft, removeDraftTransactions, removeTransactionReceipt} from '@userActions/TransactionEdit';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import ROUTES from '@src/ROUTES';
import type {Policy} from '@src/types/onyx';
import type {Participant} from '@src/types/onyx/IOU';
Expand Down Expand Up @@ -531,10 +533,22 @@ function IOURequestStepScan({

const updateScanAndNavigate = useCallback(
(file: FileObject, source: string) => {
navigateBack();
// Fix for the issue where the navigation state is lost after returning from device settings https://github.com/Expensify/App/issues/65992
const navigationState = navigationRef.current?.getState();
const reportsSplitNavigator = navigationState?.routes?.findLast((route) => route.name === 'ReportsSplitNavigator');
const hasLostNavigationsState = reportsSplitNavigator && !reportsSplitNavigator.state;
if (hasLostNavigationsState) {
if (backTo) {
Navigation.navigate(backTo as Route);
} else {
Navigation.navigate(ROUTES.HOME);
}
} else {
navigateBack();
}
replaceReceipt({transactionID: initialTransactionID, file: file as File, source});
},
[initialTransactionID],
[initialTransactionID, backTo],
);

/**
Expand Down
Loading