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