diff --git a/src/App.tsx b/src/App.tsx index 6cefbca7b48b..66ad1d767888 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,6 +11,7 @@ import ComposeProviders from './components/ComposeProviders'; import CustomStatusBarAndBackground from './components/CustomStatusBarAndBackground'; import CustomStatusBarAndBackgroundContextProvider from './components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContextProvider'; import ErrorBoundary from './components/ErrorBoundary'; +import FullScreenBlockingViewContextProvider from './components/FullScreenBlockingViewContextProvider'; import HTMLEngineProvider from './components/HTMLEngineProvider'; import InitialURLContextProvider from './components/InitialURLContextProvider'; import {InputBlurContextProvider} from './components/InputBlurContext'; @@ -96,6 +97,7 @@ function App({url}: AppProps) { SearchRouterContextProvider, ProductTrainingContextProvider, InputBlurContextProvider, + FullScreenBlockingViewContextProvider, ]} > diff --git a/src/components/BlockingViews/ForceFullScreenView/index.tsx b/src/components/BlockingViews/ForceFullScreenView/index.tsx index 8a02028168fa..54a83ce58028 100644 --- a/src/components/BlockingViews/ForceFullScreenView/index.tsx +++ b/src/components/BlockingViews/ForceFullScreenView/index.tsx @@ -1,10 +1,22 @@ -import React from 'react'; +import {useRoute} from '@react-navigation/native'; +import React, {useContext, useEffect} from 'react'; import {View} from 'react-native'; +import {FullScreenBlockingViewContext} from '@components/FullScreenBlockingViewContextProvider'; import useThemeStyles from '@hooks/useThemeStyles'; import type ForceFullScreenViewProps from './types'; function ForceFullScreenView({children, shouldForceFullScreen = false}: ForceFullScreenViewProps) { + const route = useRoute(); const styles = useThemeStyles(); + const {addRouteKey, removeRouteKey} = useContext(FullScreenBlockingViewContext); + + useEffect(() => { + if (!shouldForceFullScreen) { + addRouteKey(route.key); + } + + return () => removeRouteKey(route.key); + }, [addRouteKey, removeRouteKey, route, shouldForceFullScreen]); if (shouldForceFullScreen) { return {children}; diff --git a/src/components/BlockingViews/FullPageNotFoundView.tsx b/src/components/BlockingViews/FullPageNotFoundView.tsx index ad1a659e6d9f..a94ba0b8ea64 100644 --- a/src/components/BlockingViews/FullPageNotFoundView.tsx +++ b/src/components/BlockingViews/FullPageNotFoundView.tsx @@ -56,7 +56,7 @@ function FullPageNotFoundView({ onBackButtonPress = () => Navigation.goBack(), shouldShowLink = true, shouldShowBackButton = true, - onLinkPress = () => Navigation.dismissModal(), + onLinkPress = () => Navigation.goBackToHome(), shouldForceFullScreen = false, }: FullPageNotFoundViewProps) { const styles = useThemeStyles(); diff --git a/src/components/FullScreenBlockingViewContextProvider.tsx b/src/components/FullScreenBlockingViewContextProvider.tsx new file mode 100644 index 000000000000..78126dcbde1d --- /dev/null +++ b/src/components/FullScreenBlockingViewContextProvider.tsx @@ -0,0 +1,54 @@ +import React, {createContext, useCallback, useMemo, useState} from 'react'; + +type FullScreenBlockingViewContextValue = { + addRouteKey: (key: string) => void; + removeRouteKey: (key: string) => void; + isBlockingViewVisible: boolean; +}; + +type FullScreenBlockingViewContextProviderProps = { + children: React.ReactNode; +}; + +const defaultValue: FullScreenBlockingViewContextValue = { + addRouteKey: () => {}, + removeRouteKey: () => {}, + isBlockingViewVisible: false, +}; + +const FullScreenBlockingViewContext = createContext(defaultValue); + +function FullScreenBlockingViewContextProvider({children}: FullScreenBlockingViewContextProviderProps) { + const [routeKeys, setRouteKeys] = useState>(new Set()); + + const addRouteKey = useCallback((key: string) => { + setRouteKeys((prevKeys) => new Set(prevKeys).add(key)); + }, []); + + const removeRouteKey = useCallback((key: string) => { + setRouteKeys((prevKeys) => { + const newKeys = new Set(prevKeys); + newKeys.delete(key); + return newKeys; + }); + }, []); + + const isBlockingViewVisible = useMemo(() => routeKeys.size > 0, [routeKeys]); + + const contextValue = useMemo( + () => ({ + addRouteKey, + removeRouteKey, + isBlockingViewVisible, + }), + [addRouteKey, removeRouteKey, isBlockingViewVisible], + ); + + return {children}; +} + +export default FullScreenBlockingViewContextProvider; + +export {FullScreenBlockingViewContext}; + +export type {FullScreenBlockingViewContextProviderProps, FullScreenBlockingViewContextValue}; diff --git a/src/components/Navigation/TopLevelBottomTabBar/index.tsx b/src/components/Navigation/TopLevelBottomTabBar/index.tsx index a200102b1008..a379221782bc 100644 --- a/src/components/Navigation/TopLevelBottomTabBar/index.tsx +++ b/src/components/Navigation/TopLevelBottomTabBar/index.tsx @@ -1,6 +1,7 @@ import {findFocusedRoute, useNavigationState} from '@react-navigation/native'; -import React, {useEffect, useRef, useState} from 'react'; +import React, {useContext, useEffect, useRef, useState} from 'react'; import {InteractionManager, View} from 'react-native'; +import {FullScreenBlockingViewContext} from '@components/FullScreenBlockingViewContextProvider'; import BottomTabBar from '@components/Navigation/BottomTabBar'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets'; @@ -29,6 +30,7 @@ function TopLevelBottomTabBar() { const {paddingBottom} = useStyledSafeAreaInsets(); const [isAfterClosingTransition, setIsAfterClosingTransition] = useState(false); const cancelAfterInteractions = useRef | undefined>(); + const {isBlockingViewVisible} = useContext(FullScreenBlockingViewContext); const selectedTab = useNavigationState((state) => { const topmostFullScreenRoute = state?.routes.findLast((route) => isFullScreenName(route.name)); @@ -48,7 +50,7 @@ function TopLevelBottomTabBar() { const isBottomTabVisibleDirectly = useIsBottomTabVisibleDirectly(); const shouldDisplayBottomBar = shouldUseNarrowLayout ? isScreenWithBottomTabFocused : isBottomTabVisibleDirectly; - const isReadyToDisplayBottomBar = isAfterClosingTransition && shouldDisplayBottomBar; + const isReadyToDisplayBottomBar = isAfterClosingTransition && shouldDisplayBottomBar && !isBlockingViewVisible; useEffect(() => { cancelAfterInteractions.current?.cancel(); diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index db522ba729df..5733b1a2b4d0 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -250,32 +250,31 @@ const defaultGoBackOptions: Required = { * @param options - Optional configuration that affects navigation logic, such as parameter comparison. */ function goUp(fallbackRoute: Route, options?: GoBackOptions) { - if (!canNavigate('goUp')) { + if (!canNavigate('goUp') || !navigationRef.current) { + Log.hmmm(`[Navigation] Unable to go up. Can't navigate.`); return; } - if (!navigationRef.current) { - Log.hmmm('[Navigation] Unable to go up'); - return; - } + const compareParams = options?.compareParams ?? defaultGoBackOptions.compareParams; const rootState = navigationRef.current.getRootState(); const stateFromPath = getStateFromPath(fallbackRoute); + const action = getActionFromState(stateFromPath, linkingConfig.config); if (!action) { + Log.hmmm(`[Navigation] Unable to go up. Action is undefined.`); return; } const {action: minimalAction, targetState} = getMinimalAction(action, rootState); if (minimalAction.type !== CONST.NAVIGATION.ACTION_TYPE.NAVIGATE || !targetState) { + Log.hmmm('[Navigation] Unable to go up. Minimal action type is wrong.'); return; } - const compareParams = options?.compareParams ?? defaultGoBackOptions.compareParams; const indexOfFallbackRoute = targetState.routes.findLastIndex((route) => doesRouteMatchToMinimalActionPayload(route, minimalAction, compareParams)); - const distanceToPop = targetState.routes.length - indexOfFallbackRoute - 1; // If we need to pop more than one route from rootState, we replace the current route to not lose visited routes from the navigation state @@ -343,6 +342,27 @@ function resetToHome() { navigationRef.dispatch({payload, type: CONST.NAVIGATION.ACTION_TYPE.REPLACE, target: rootState.key}); } +/** + * The goBack function doesn't support recursive pop e.g. pop route from root and then from nested navigator. + * There is only one case where recursive pop is needed which is going back to home. + * This function will cover this case. + * We will implement recursive pop if more use cases will appear. + */ +function goBackToHome() { + const isNarrowLayout = getIsNarrowLayout(); + + // This set the right split navigator. + goBack(ROUTES.HOME); + + // We want to keep the report screen in the split navigator on wide layout. + if (!isNarrowLayout) { + return; + } + + // This set the right route in this split navigator. + goBack(ROUTES.HOME); +} + /** * Update route params for the specified route. */ @@ -550,6 +570,7 @@ export default { waitForProtectedRoutes, parseHybridAppUrl, resetToHome, + goBackToHome, closeRHPFlow, setNavigationActionToMicrotaskQueue, navigateToReportWithPolicyCheck, diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 4083bbe6ee66..30a66ee22ae4 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -58,7 +58,7 @@ function CardSection() { const requestRefund = useCallback(() => { requestRefundByUser(); setIsRequestRefundModalVisible(false); - Navigation.goBack(ROUTES.HOME); + Navigation.goBackToHome(); }, []); const viewPurchases = useCallback(() => { diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 2517321658b4..e15872481a40 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -395,7 +395,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac > Navigation.goBack(ROUTES.HOME)} + onLinkPress={Navigation.goBackToHome} shouldShow={shouldShowNotFoundPage} subtitleKey={shouldShowPolicy ? 'workspace.common.notAuthorized' : undefined} > diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index 6bfb735fae02..153daaf653e7 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -174,7 +174,7 @@ function WorkspacePageWithSections({ > Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} - onLinkPress={() => Navigation.goBack(ROUTES.HOME)} + onLinkPress={Navigation.goBackToHome} shouldShow={shouldShow} subtitleKey={shouldShowPolicy ? 'workspace.common.notAuthorized' : undefined} shouldForceFullScreen