Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b16562d
fix: WS - Not here page appears when trying to navigate to Workspaces.
Krishna2323 May 25, 2025
120cf9b
minor update.
Krishna2323 May 26, 2025
0c0ce91
Merge branch 'main' into krishna2323/issue/62569
Krishna2323 May 27, 2025
bfe5797
Merge branch 'Expensify:main' into krishna2323/issue/62569
Krishna2323 Jun 3, 2025
1bdb138
refactor showworkspaces navigation logic.
Krishna2323 Jun 3, 2025
9ca7e16
add tests for getWorkspaceTabNavigationAction.
Krishna2323 Jun 3, 2025
2b1056c
fix ESLint.
Krishna2323 Jun 3, 2025
0355863
fix ESLint.
Krishna2323 Jun 3, 2025
1bef299
update getWorkspaceTabNavigationAction.ts to navigate directly.
Krishna2323 Jun 8, 2025
41b1665
update unit tests.
Krishna2323 Jun 9, 2025
8df92a3
fix ESLint.
Krishna2323 Jun 9, 2025
0b98650
add comment.
Krishna2323 Jun 9, 2025
87121d7
update navigateToWorkspacesPage unit test file name.
Krishna2323 Jun 12, 2025
2013827
Merge branch 'Expensify:main' into krishna2323/issue/62569
Krishna2323 Jun 19, 2025
f0903eb
remove the use of depreacted getPolicy util.
Krishna2323 Jun 19, 2025
027683b
fix esLint.
Krishna2323 Jun 19, 2025
f4e037a
use selector function to extract policy,
Krishna2323 Jun 20, 2025
c092747
refactor navigateToWorkspacesPage.
Krishna2323 Jun 22, 2025
b51b1ab
Merge branch 'Expensify:main' into krishna2323/issue/62569
Krishna2323 Jun 22, 2025
5f0829f
fix unit tests.
Krishna2323 Jun 22, 2025
5fbe03b
add navigation state in the dependency array of useOnyx.
Krishna2323 Jun 24, 2025
2ad2350
fix ios crash.
Krishna2323 Jun 26, 2025
dcf64db
Merge branch 'Expensify:main' into krishna2323/issue/62569
Krishna2323 Jun 26, 2025
f35dfa0
Merge branch 'Expensify:main' into krishna2323/issue/62569
Krishna2323 Jun 29, 2025
54730d3
minor update.
Krishna2323 Jun 30, 2025
f81dc62
minor refactor.
Krishna2323 Jul 1, 2025
347d8cb
Merge branch 'Expensify:main' into krishna2323/issue/62569
Krishna2323 Jul 4, 2025
1656fdb
add comments.
Krishna2323 Jul 4, 2025
61d2645
add tests for shouldShowPolicy returning false.
Krishna2323 Jul 4, 2025
36da4e0
move getWorkspaceNavigationRouteState function call outside of the on…
Krishna2323 Jul 4, 2025
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
91 changes: 37 additions & 54 deletions src/components/Navigation/NavigationTabBar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {findFocusedRoute} from '@react-navigation/native';
import {findFocusedRoute, useNavigationState} from '@react-navigation/native';
import React, {memo, useCallback, useEffect, useState} from 'react';
import {View} from 'react-native';
import type {ValueOf} from 'type-fest';
Expand All @@ -11,6 +11,7 @@ import {PressableWithFeedback} from '@components/Pressable';
import {useProductTrainingContext} from '@components/ProductTrainingContext';
import Text from '@components/Text';
import EducationalTooltip from '@components/Tooltip/EducationalTooltip';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
Expand All @@ -24,16 +25,11 @@ import clearSelectedText from '@libs/clearSelectedText/clearSelectedText';
import getPlatform from '@libs/getPlatform';
import interceptAnonymousUser from '@libs/interceptAnonymousUser';
import {getPreservedNavigatorState} from '@libs/Navigation/AppNavigator/createSplitNavigator/usePreserveNavigatorState';
import {
getLastVisitedTabPath,
getLastVisitedWorkspaceTabScreen,
getSettingsTabStateFromSessionStorage,
getWorkspacesTabStateFromSessionStorage,
} from '@libs/Navigation/helpers/lastVisitedTabPathUtils';
import {getLastVisitedTabPath, getSettingsTabStateFromSessionStorage} from '@libs/Navigation/helpers/lastVisitedTabPathUtils';
import navigateToWorkspacesPage, {getWorkspaceNavigationRouteState} from '@libs/Navigation/helpers/navigateToWorkspacesPage';
import {buildCannedSearchQuery, buildSearchQueryJSON, buildSearchQueryString} from '@libs/SearchQueryUtils';
import type {BrickRoad} from '@libs/WorkspacesSettingsUtils';
import {getChatTabBrickRoad} from '@libs/WorkspacesSettingsUtils';
import {isFullScreenName, isWorkspacesTabScreenName} from '@navigation/helpers/isNavigatorName';
import Navigation from '@navigation/Navigation';
import navigationRef from '@navigation/navigationRef';
import type {RootNavigatorParamList, SearchFullscreenNavigatorParamList, State, WorkspaceSplitNavigatorParamList} from '@navigation/types';
Expand Down Expand Up @@ -61,7 +57,29 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
const {orderedReports} = useSidebarOrderedReports();
const subscriptionPlan = useSubscriptionPlan();
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false});
const navigationState = useNavigationState(findFocusedRoute);
const initialNavigationRouteState = getWorkspaceNavigationRouteState();
const [lastWorkspacesTabNavigatorRoute, setLastWorkspacesTabNavigatorRoute] = useState(initialNavigationRouteState.lastWorkspacesTabNavigatorRoute);
const [workspacesTabState, setWorkspacesTabState] = useState(initialNavigationRouteState.workspacesTabState);
const params = workspacesTabState?.routes?.at(0)?.params as WorkspaceSplitNavigatorParamList[typeof SCREENS.WORKSPACE.INITIAL];

const [lastViewedPolicy] = useOnyx(
ONYXKEYS.COLLECTION.POLICY,
{
canBeMissing: true,
selector: (val) => {
if (!lastWorkspacesTabNavigatorRoute || lastWorkspacesTabNavigatorRoute.name !== NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR || !params?.policyID) {
return undefined;
}

return val?.[`${ONYXKEYS.COLLECTION.POLICY}${params.policyID}`];
},
},
[navigationState],
);

const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (value) => value?.reports, canBeMissing: true});
const {login: currentUserLogin} = useCurrentUserPersonalDetails();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const [chatTabBrickRoad, setChatTabBrickRoad] = useState<BrickRoad>(undefined);
const platform = getPlatform();
Expand All @@ -74,6 +92,15 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar

const StyleUtils = useStyleUtils();

useEffect(() => {
const newWorkspacesTabState = getWorkspaceNavigationRouteState();
const newLastRoute = newWorkspacesTabState.lastWorkspacesTabNavigatorRoute;
const newTabState = newWorkspacesTabState.workspacesTabState;

setLastWorkspacesTabNavigatorRoute(newLastRoute);
setWorkspacesTabState(newTabState);
}, [navigationState]);

// On a wide layout DebugTabView should be rendered only within the navigation tab bar displayed directly on screens.
const shouldRenderDebugTabViewOnWideLayout = !!account?.isDebugModeEnabled && !isTopLevelBar;

Expand Down Expand Up @@ -151,52 +178,8 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
* If the user clicks on the settings tab while on this tab, this button should go back to the previous screen within the tab.
*/
const showWorkspaces = useCallback(() => {
const rootState = navigationRef.getRootState();
const topmostFullScreenRoute = rootState.routes.findLast((route) => isFullScreenName(route.name));
if (!topmostFullScreenRoute) {
return;
}

if (topmostFullScreenRoute.name === NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR) {
Navigation.goBack(ROUTES.WORKSPACES_LIST.route);
return;
}

interceptAnonymousUser(() => {
const state = getWorkspacesTabStateFromSessionStorage() ?? rootState;
const lastWorkspacesTabNavigatorRoute = state.routes.findLast((route) => isWorkspacesTabScreenName(route.name));
// If there is no settings or workspace navigator route, then we should open the settings navigator.
if (!lastWorkspacesTabNavigatorRoute) {
Navigation.navigate(ROUTES.WORKSPACES_LIST.route);
return;
}

let workspacesTabState = lastWorkspacesTabNavigatorRoute.state;
if (!workspacesTabState && lastWorkspacesTabNavigatorRoute.key) {
workspacesTabState = getPreservedNavigatorState(lastWorkspacesTabNavigatorRoute.key);
}

// If there is a workspace navigator route, then we should open the workspace initial screen as it should be "remembered".
if (lastWorkspacesTabNavigatorRoute.name === NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR) {
const params = workspacesTabState?.routes.at(0)?.params as WorkspaceSplitNavigatorParamList[typeof SCREENS.WORKSPACE.INITIAL];
// Screens of this navigator should always have policyID
if (params.policyID) {
const workspaceScreenName = !shouldUseNarrowLayout ? getLastVisitedWorkspaceTabScreen() : SCREENS.WORKSPACE.INITIAL;
// This action will put settings split under the workspace split to make sure that we can swipe back to settings split.
navigationRef.dispatch({
type: CONST.NAVIGATION.ACTION_TYPE.OPEN_WORKSPACE_SPLIT,
payload: {
policyID: params.policyID,
screenName: workspaceScreenName,
},
});
}
return;
}

Navigation.navigate(ROUTES.WORKSPACES_LIST.route);
});
}, [shouldUseNarrowLayout]);
navigateToWorkspacesPage({shouldUseNarrowLayout, currentUserLogin, policy: lastViewedPolicy});
}, [shouldUseNarrowLayout, currentUserLogin, lastViewedPolicy]);

if (!shouldUseNarrowLayout) {
return (
Expand Down
94 changes: 94 additions & 0 deletions src/libs/Navigation/helpers/navigateToWorkspacesPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import interceptAnonymousUser from '@libs/interceptAnonymousUser';
import {getPreservedNavigatorState} from '@libs/Navigation/AppNavigator/createSplitNavigator/usePreserveNavigatorState';
import Navigation from '@libs/Navigation/Navigation';
import navigationRef from '@libs/Navigation/navigationRef';
import {isPendingDeletePolicy, shouldShowPolicy as shouldShowPolicyUtil} from '@libs/PolicyUtils';
import CONST from '@src/CONST';
import NAVIGATORS from '@src/NAVIGATORS';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import type {Policy} from '@src/types/onyx';
import {isFullScreenName, isWorkspacesTabScreenName} from './isNavigatorName';
import {getLastVisitedWorkspaceTabScreen, getWorkspacesTabStateFromSessionStorage} from './lastVisitedTabPathUtils';

type Params = {
currentUserLogin?: string;
shouldUseNarrowLayout: boolean;
policy?: Policy;
};

// Gets the latest workspace navigation state, restoring from session or preserved state if needed.
const getWorkspaceNavigationRouteState = () => {
const rootState = navigationRef.getRootState();

// Only consider main (fullscreen) routes for top-level navigation context.
const topmostFullScreenRoute = rootState?.routes?.findLast((route) => isFullScreenName(route.name));
if (!topmostFullScreenRoute) {
// No fullscreen route: not in a workspace context.
return {};
}

// Prefer restoring workspace tab state from sessionStorage for accurate restoration.
const workspacesTabStateFromSessionStorage = getWorkspacesTabStateFromSessionStorage() ?? rootState;
const lastWorkspacesTabNavigatorRoute = workspacesTabStateFromSessionStorage?.routes.findLast((route) => isWorkspacesTabScreenName(route.name));
let workspacesTabState = lastWorkspacesTabNavigatorRoute?.state;

// Use preserved state if live state is missing (e.g. after a pop).
if (!workspacesTabState && lastWorkspacesTabNavigatorRoute?.key) {
workspacesTabState = getPreservedNavigatorState(lastWorkspacesTabNavigatorRoute.key);
}

return {lastWorkspacesTabNavigatorRoute, workspacesTabState, topmostFullScreenRoute};
};

// Navigates to the appropriate workspace tab or workspace list page.
const navigateToWorkspacesPage = ({currentUserLogin, shouldUseNarrowLayout, policy}: Params) => {
const {lastWorkspacesTabNavigatorRoute, topmostFullScreenRoute} = getWorkspaceNavigationRouteState();

if (!topmostFullScreenRoute) {

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.

Can you please add some comments to the code that clearly explains WHY each of these alternate navigation paths are necessary?

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.

comments added.

// Not in a main workspace navigation context, so do nothing.
return;
}

if (topmostFullScreenRoute.name === NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR) {
// Already inside a workspace: go back to the list.
Navigation.goBack(ROUTES.WORKSPACES_LIST.route);
return;
}

interceptAnonymousUser(() => {
// No workspace found in nav state: go to list.
if (!lastWorkspacesTabNavigatorRoute) {
Navigation.navigate(ROUTES.WORKSPACES_LIST.route);
return;
}

// Workspace route found: try to restore last workspace screen.
if (lastWorkspacesTabNavigatorRoute.name === NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR) {
const shouldShowPolicy = shouldShowPolicyUtil(policy, false, currentUserLogin);
const isPendingDelete = isPendingDeletePolicy(policy);

// Workspace is not accessible or is being deleted: go to list.
if (!shouldShowPolicy || isPendingDelete) {
Navigation.navigate(ROUTES.WORKSPACES_LIST.route);
return;
}

// Restore to last-visited workspace tab or show initial tab
if (policy?.id) {
const workspaceScreenName = !shouldUseNarrowLayout ? getLastVisitedWorkspaceTabScreen() : SCREENS.WORKSPACE.INITIAL;
navigationRef.dispatch({
type: CONST.NAVIGATION.ACTION_TYPE.OPEN_WORKSPACE_SPLIT,
payload: {policyID: policy.id, screenName: workspaceScreenName},
});
}
return;
}

// Fallback: any other state, go to the list.
Navigation.navigate(ROUTES.WORKSPACES_LIST.route);
});
};

export default navigateToWorkspacesPage;
export {getWorkspaceNavigationRouteState};
Loading
Loading