diff --git a/src/CONST.ts b/src/CONST.ts index 448754030182..bf4b30ff74df 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -902,6 +902,7 @@ const CONST = { CLOUDFRONT_URL, EMPTY_ARRAY, EMPTY_OBJECT, + EMPTY_STRING: '', DEFAULT_NUMBER_ID: 0, USE_EXPENSIFY_URL, EXPENSIFY_URL, diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 701042b93aae..82211eabe00f 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -2,7 +2,7 @@ import {findFocusedRoute} from '@react-navigation/native'; import React, {memo, useEffect, useMemo, useRef, useState} from 'react'; import {NativeModules, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import Onyx, {withOnyx} from 'react-native-onyx'; +import Onyx, {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import ActiveGuidesEventListener from '@components/ActiveGuidesEventListener'; import ComposeProviders from '@components/ComposeProviders'; @@ -60,6 +60,7 @@ import SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import type {SelectedTimezone, Timezone} from '@src/types/onyx/PersonalDetails'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import type ReactComponentModule from '@src/types/utils/ReactComponentModule'; import beforeRemoveReportOpenedFromSearchRHP from './beforeRemoveReportOpenedFromSearchRHP'; import CENTRAL_PANE_SCREENS from './CENTRAL_PANE_SCREENS'; @@ -77,17 +78,6 @@ import RightModalNavigator from './Navigators/RightModalNavigator'; import WelcomeVideoModalNavigator from './Navigators/WelcomeVideoModalNavigator'; import useRootNavigatorOptions from './useRootNavigatorOptions'; -type AuthScreensProps = { - /** Session of currently logged in user */ - session: OnyxEntry; - - /** The report ID of the last opened public room as anonymous user */ - lastOpenedPublicRoomID: OnyxEntry; - - /** The last Onyx update ID was applied to the client */ - initialLastUpdateIDAppliedToClient: OnyxEntry; -}; - const loadReportAttachments = () => require('../../../pages/home/report/ReportAttachments').default; const loadValidateLoginPage = () => require('../../../pages/ValidateLoginPage').default; const loadLogOutPreviousUserPage = () => require('../../../pages/LogOutPreviousUserPage').default; @@ -239,7 +229,10 @@ const modalScreenListenersWithCancelSearch = { }, }; -function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDAppliedToClient}: AuthScreensProps) { +function AuthScreens() { + const [session] = useOnyx(ONYXKEYS.SESSION); + const [lastOpenedPublicRoomID, lastOpenedPublicRoomIDStatus] = useOnyx(ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID); + const [initialLastUpdateIDAppliedToClient, initialLastUpdateIDAppliedToClientStatus] = useOnyx(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT); const theme = useTheme(); const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -251,6 +244,10 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie const modal = useRef({}); const {isOnboardingCompleted} = useOnboardingFlowRouter(); + const isLastOpenedPublicRoomIDLoading = isLoadingOnyxValue(lastOpenedPublicRoomIDStatus); + const isInitialLastUpdateIDAppliedToClientLoading = isLoadingOnyxValue(initialLastUpdateIDAppliedToClientStatus); + const isLastOpenedPublicRoomIDLoadedRef = useRef(false); + const isInitialLastUpdateIDAppliedToClientLoadedRef = useRef(false); // On HybridApp we need to prevent flickering during transition to OldDot const shouldRenderOnboardingExclusivelyOnHybridApp = useMemo(() => { @@ -276,6 +273,37 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie }; }, [theme]); + useEffect(() => { + if (isLastOpenedPublicRoomIDLoading || isLastOpenedPublicRoomIDLoadedRef.current) { + return; + } + + isLastOpenedPublicRoomIDLoadedRef.current = true; + if (lastOpenedPublicRoomID) { + // Re-open the last opened public room if the user logged in from a public room link + Report.openLastOpenedPublicRoom(lastOpenedPublicRoomID); + } + }, [isLastOpenedPublicRoomIDLoading, lastOpenedPublicRoomID]); + + useEffect(() => { + if (isInitialLastUpdateIDAppliedToClientLoading || isInitialLastUpdateIDAppliedToClientLoadedRef.current) { + return; + } + + isInitialLastUpdateIDAppliedToClientLoadedRef.current = true; + // In Hybrid App we decide to call one of those method when booting ND and we don't want to duplicate calls + if (!NativeModules.HybridAppModule) { + // If we are on this screen then we are "logged in", but the user might not have "just logged in". They could be reopening the app + // or returning from background. If so, we'll assume they have some app data already and we can call reconnectApp() instead of openApp(). + if (SessionUtils.didUserLogInDuringSession()) { + App.openApp(); + } else { + Log.info('[AuthScreens] Sending ReconnectApp'); + App.reconnectApp(initialLastUpdateIDAppliedToClient); + } + } + }, [initialLastUpdateIDAppliedToClient, isInitialLastUpdateIDAppliedToClientLoading]); + useEffect(() => { const shortcutsOverviewShortcutConfig = CONST.KEYBOARD_SHORTCUTS.SHORTCUTS; const searchShortcutConfig = CONST.KEYBOARD_SHORTCUTS.SEARCH; @@ -296,28 +324,12 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie PusherConnectionManager.init(); initializePusher(); - // In Hybrid App we decide to call one of those method when booting ND and we don't want to duplicate calls - if (!NativeModules.HybridAppModule) { - // If we are on this screen then we are "logged in", but the user might not have "just logged in". They could be reopening the app - // or returning from background. If so, we'll assume they have some app data already and we can call reconnectApp() instead of openApp(). - if (SessionUtils.didUserLogInDuringSession()) { - App.openApp(); - } else { - Log.info('[AuthScreens] Sending ReconnectApp'); - App.reconnectApp(initialLastUpdateIDAppliedToClient); - } - } - PriorityMode.autoSwitchToFocusMode(); App.setUpPoliciesAndNavigate(session); App.redirectThirdPartyDesktopSignIn(); - if (lastOpenedPublicRoomID) { - // Re-open the last opened public room if the user logged in from a public room link - Report.openLastOpenedPublicRoom(lastOpenedPublicRoomID); - } Download.clearDownloads(); const unsubscribeOnyxModal = onyxSubscribe({ @@ -643,20 +655,5 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie AuthScreens.displayName = 'AuthScreens'; -const AuthScreensMemoized = memo(AuthScreens, () => true); - -// Migration to useOnyx cause re-login if logout from deeplinked report in desktop app -// Further analysis required and more details can be seen here: -// https://github.com/Expensify/App/issues/50560 -// eslint-disable-next-line -export default withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - lastOpenedPublicRoomID: { - key: ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID, - }, - initialLastUpdateIDAppliedToClient: { - key: ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, - }, -})(AuthScreensMemoized); +// This memo is needed because is one of the main components in the app and navigation root and is relevant for the app performance. +export default memo(AuthScreens); diff --git a/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/CustomRouter.ts b/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/CustomRouter.ts index 3cbb5acb81e5..1928630c8559 100644 --- a/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/CustomRouter.ts +++ b/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/CustomRouter.ts @@ -13,6 +13,7 @@ import {isCentralPaneName, isOnboardingFlowName} from '@libs/NavigationUtils'; import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; +import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import syncBrowserHistory from './syncBrowserHistory'; @@ -40,13 +41,8 @@ function insertRootRoute(state: State, routeToInsert: Naviga } function compareAndAdaptState(state: StackNavigationState) { - // If the state of the last path is not defined the getPathFromState won't work correctly. - if (!state?.routes.at(-1)?.state) { - return; - } - // We need to be sure that the bottom tab state is defined. - const topmostBottomTabRoute = getTopmostBottomTabRoute(state); + const topmostBottomTabRoute = getTopmostBottomTabRoute(state) ?? {name: SCREENS.HOME}; const isNarrowLayout = getIsNarrowLayout(); // This solutions is heuristics and will work for our cases. We may need to improve it in the future if we will have more cases to handle. @@ -61,7 +57,9 @@ function compareAndAdaptState(state: StackNavigationState) { // We will generate a template state and compare the current state with it. // If there is a difference in the screens that should be visible under the overlay, we will add the screen from templateState to the current state. const pathFromCurrentState = getPathFromState(state, linkingConfig.config); - const {adaptedState: templateState} = getAdaptedStateFromPath(pathFromCurrentState, linkingConfig.config); + // If the state of the bottom tab has not been fully initialized, the generated path must be adjusted + const adjustedPath = pathFromCurrentState === `/${ROUTES.ROOT}` ? `/${ROUTES.REPORT}` : pathFromCurrentState; + const {adaptedState: templateState} = getAdaptedStateFromPath(adjustedPath, linkingConfig.config); if (!templateState) { return; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 10544aef9e8e..3d25dcb924c0 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1,5 +1,6 @@ import type {LinkingOptions} from '@react-navigation/native'; import type {RootStackParamList} from '@navigation/types'; +import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ROUTES from '@src/ROUTES'; import type {Screen} from '@src/SCREENS'; @@ -30,7 +31,17 @@ const config: LinkingOptions['config'] = { [SCREENS.REPORT_AVATAR]: ROUTES.REPORT_AVATAR.route, [SCREENS.TRANSACTION_RECEIPT]: ROUTES.TRANSACTION_RECEIPT.route, [SCREENS.WORKSPACE_JOIN_USER]: ROUTES.WORKSPACE_JOIN_USER.route, - [SCREENS.REPORT]: ROUTES.REPORT_WITH_ID.route, + [SCREENS.REPORT]: { + path: ROUTES.REPORT_WITH_ID.route, + // If params are defined, but reportID is explicitly undefined, we will get the url /r/undefined. + // We want to avoid that situation, so we will return an empty string instead. + parse: { + reportID: (reportID: string | undefined) => reportID ?? CONST.EMPTY_STRING, + }, + stringify: { + reportID: (reportID: string | undefined) => reportID ?? CONST.EMPTY_STRING, + }, + }, [SCREENS.SETTINGS.PROFILE.ROOT]: { path: ROUTES.SETTINGS_PROFILE, exact: true, diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 7900102c79d2..ea764c24c2ad 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -54,7 +54,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {HybridAppRoute, Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; import type Credentials from '@src/types/onyx/Credentials'; import type Session from '@src/types/onyx/Session'; import type {AutoAuthState} from '@src/types/onyx/Session'; @@ -826,20 +825,11 @@ function clearSignInData() { } /** - * Reset all current params of the Home route + * Reset navigation state after logout */ -function resetHomeRouteParams() { +function resetNavigationState() { Navigation.isNavigationReady().then(() => { - const routes = navigationRef.current?.getState()?.routes; - const homeRoute = routes?.find((route) => route.name === SCREENS.HOME); - - const emptyParams: Record = {}; - Object.keys(homeRoute?.params ?? {}).forEach((paramKey) => { - emptyParams[paramKey] = undefined; - }); - - Navigation.setParams(emptyParams, homeRoute?.key ?? ''); - Onyx.set(ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, false); + navigationRef.resetRoot(navigationRef.getRootState()); }); } @@ -858,7 +848,7 @@ function cleanupSession() { PersistedRequests.clear(); NetworkConnection.clearReconnectionCallbacks(); SessionUtils.resetDidUserLogInDuringSession(); - resetHomeRouteParams(); + resetNavigationState(); clearCache().then(() => { Log.info('Cleared all cache data', true, {}, true); });