Skip to content

Commit 1b30a48

Browse files
committed
Add usePreloadFullScreenNavigators
1 parent 89a489a commit 1b30a48

File tree

10 files changed

+194
-70
lines changed

10 files changed

+194
-70
lines changed

src/components/Navigation/NavigationTabBar/index.tsx

Lines changed: 7 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import {findFocusedRoute} from '@react-navigation/native';
21
import React, {memo, useCallback, useEffect, useState} from 'react';
32
import {View} from 'react-native';
43
import type {ValueOf} from 'type-fest';
@@ -16,27 +15,20 @@ import useOnyx from '@hooks/useOnyx';
1615
import useResponsiveLayout from '@hooks/useResponsiveLayout';
1716
import {useSidebarOrderedReports} from '@hooks/useSidebarOrderedReports';
1817
import useStyleUtils from '@hooks/useStyleUtils';
19-
import useSubscriptionPlan from '@hooks/useSubscriptionPlan';
2018
import useTheme from '@hooks/useTheme';
2119
import useThemeStyles from '@hooks/useThemeStyles';
2220
import useWorkspacesTabIndicatorStatus from '@hooks/useWorkspacesTabIndicatorStatus';
2321
import clearSelectedText from '@libs/clearSelectedText/clearSelectedText';
2422
import getPlatform from '@libs/getPlatform';
2523
import interceptAnonymousUser from '@libs/interceptAnonymousUser';
2624
import {getPreservedNavigatorState} from '@libs/Navigation/AppNavigator/createSplitNavigator/usePreserveNavigatorState';
27-
import {
28-
getLastVisitedTabPath,
29-
getLastVisitedWorkspaceTabScreen,
30-
getSettingsTabStateFromSessionStorage,
31-
getWorkspacesTabStateFromSessionStorage,
32-
} from '@libs/Navigation/helpers/lastVisitedTabPathUtils';
33-
import {buildCannedSearchQuery, buildSearchQueryJSON, buildSearchQueryString} from '@libs/SearchQueryUtils';
25+
import {getLastVisitedWorkspaceTabScreen, getWorkspacesTabStateFromSessionStorage} from '@libs/Navigation/helpers/lastVisitedTabPathUtils';
3426
import type {BrickRoad} from '@libs/WorkspacesSettingsUtils';
3527
import {getChatTabBrickRoad} from '@libs/WorkspacesSettingsUtils';
3628
import {isFullScreenName, isWorkspacesTabScreenName} from '@navigation/helpers/isNavigatorName';
3729
import Navigation from '@navigation/Navigation';
3830
import navigationRef from '@navigation/navigationRef';
39-
import type {RootNavigatorParamList, SearchFullscreenNavigatorParamList, State, WorkspaceSplitNavigatorParamList} from '@navigation/types';
31+
import type {WorkspaceSplitNavigatorParamList} from '@navigation/types';
4032
import NavigationTabBarAvatar from '@pages/home/sidebar/NavigationTabBarAvatar';
4133
import NavigationTabBarFloatingActionButton from '@pages/home/sidebar/NavigationTabBarFloatingActionButton';
4234
import variables from '@styles/variables';
@@ -59,7 +51,7 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
5951
const {translate, preferredLocale} = useLocalize();
6052
const {indicatorColor: workspacesTabIndicatorColor, status: workspacesTabIndicatorStatus} = useWorkspacesTabIndicatorStatus();
6153
const {orderedReports} = useSidebarOrderedReports();
62-
const subscriptionPlan = useSubscriptionPlan();
54+
6355
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false});
6456
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (value) => value?.reports, canBeMissing: true});
6557
const {shouldUseNarrowLayout} = useResponsiveLayout();
@@ -89,7 +81,7 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
8981
}
9082

9183
hideInboxTooltip();
92-
Navigation.navigate(ROUTES.HOME);
84+
navigationRef.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.PUSH, payload: {name: NAVIGATORS.REPORTS_SPLIT_NAVIGATOR}});
9385
}, [hideInboxTooltip, selectedTab]);
9486

9587
const navigateToSearch = useCallback(() => {
@@ -98,27 +90,7 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
9890
}
9991
clearSelectedText();
10092
interceptAnonymousUser(() => {
101-
const rootState = navigationRef.getRootState() as State<RootNavigatorParamList>;
102-
const lastSearchNavigator = rootState.routes.findLast((route) => route.name === NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR);
103-
const lastSearchNavigatorState = lastSearchNavigator && lastSearchNavigator.key ? getPreservedNavigatorState(lastSearchNavigator?.key) : undefined;
104-
const lastSearchRoute = lastSearchNavigatorState?.routes.findLast((route) => route.name === SCREENS.SEARCH.ROOT);
105-
106-
if (lastSearchRoute) {
107-
const {q, ...rest} = lastSearchRoute.params as SearchFullscreenNavigatorParamList[typeof SCREENS.SEARCH.ROOT];
108-
const queryJSON = buildSearchQueryJSON(q);
109-
if (queryJSON) {
110-
const query = buildSearchQueryString(queryJSON);
111-
Navigation.navigate(
112-
ROUTES.SEARCH_ROOT.getRoute({
113-
query,
114-
...rest,
115-
}),
116-
);
117-
return;
118-
}
119-
}
120-
121-
Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: buildCannedSearchQuery()}));
93+
navigationRef.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.PUSH, payload: {name: NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR}});
12294
});
12395
}, [selectedTab]);
12496

@@ -127,22 +99,9 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar
12799
return;
128100
}
129101
interceptAnonymousUser(() => {
130-
const settingsTabState = getSettingsTabStateFromSessionStorage();
131-
if (settingsTabState && !shouldUseNarrowLayout) {
132-
const stateRoute = findFocusedRoute(settingsTabState);
133-
if (!subscriptionPlan && stateRoute?.name === SCREENS.SETTINGS.SUBSCRIPTION.ROOT) {
134-
Navigation.navigate(ROUTES.SETTINGS_PROFILE.route);
135-
return;
136-
}
137-
const lastVisitedSettingsRoute = getLastVisitedTabPath(settingsTabState);
138-
if (lastVisitedSettingsRoute) {
139-
Navigation.navigate(lastVisitedSettingsRoute);
140-
return;
141-
}
142-
}
143-
Navigation.navigate(ROUTES.SETTINGS);
102+
navigationRef.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.PUSH, payload: {name: NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR}});
144103
});
145-
}, [selectedTab, subscriptionPlan, shouldUseNarrowLayout]);
104+
}, [selectedTab]);
146105

147106
/**
148107
* The settings tab is related to SettingsSplitNavigator and WorkspaceSplitNavigator.

src/libs/Navigation/AppNavigator/Navigators/ReportsSplitNavigator.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React, {useState} from 'react';
2+
import NAVIGATION_TABS from '@components/Navigation/NavigationTabBar/NAVIGATION_TABS';
23
import usePermissions from '@hooks/usePermissions';
34
import createSplitNavigator from '@libs/Navigation/AppNavigator/createSplitNavigator';
45
import FreezeWrapper from '@libs/Navigation/AppNavigator/FreezeWrapper';
6+
import usePreloadFullScreenNavigators from '@libs/Navigation/AppNavigator/usePreloadFullScreenNavigators';
57
import useSplitNavigatorScreenOptions from '@libs/Navigation/AppNavigator/useSplitNavigatorScreenOptions';
68
import getCurrentUrl from '@libs/Navigation/currentUrl';
79
import shouldOpenOnAdminRoom from '@libs/Navigation/helpers/shouldOpenOnAdminRoom';
@@ -21,7 +23,7 @@ const Split = createSplitNavigator<ReportsSplitNavigatorParamList>();
2123
* This SplitNavigator includes the HOME screen (<BaseSidebarScreen /> component) with a list of reports as a sidebar screen and the REPORT screen displayed as a central one.
2224
* There can be multiple report screens in the stack with different report IDs.
2325
*/
24-
function ReportsSplitNavigator({route}: PlatformStackScreenProps<AuthScreensParamList, typeof NAVIGATORS.REPORTS_SPLIT_NAVIGATOR>) {
26+
function ReportsSplitNavigator({route, navigation}: PlatformStackScreenProps<AuthScreensParamList, typeof NAVIGATORS.REPORTS_SPLIT_NAVIGATOR>) {
2527
const {isBetaEnabled} = usePermissions();
2628
const splitNavigatorScreenOptions = useSplitNavigatorScreenOptions();
2729

@@ -37,6 +39,8 @@ function ReportsSplitNavigator({route}: PlatformStackScreenProps<AuthScreensPara
3739
return initialReport?.reportID ?? '';
3840
});
3941

42+
usePreloadFullScreenNavigators(navigation, NAVIGATION_TABS.HOME);
43+
4044
return (
4145
<FreezeWrapper>
4246
<Split.Navigator

src/libs/Navigation/AppNavigator/Navigators/SearchFullscreenNavigator.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import React from 'react';
2+
import NAVIGATION_TABS from '@components/Navigation/NavigationTabBar/NAVIGATION_TABS';
3+
import usePreloadFullScreenNavigators from '@libs/Navigation/AppNavigator/usePreloadFullScreenNavigators';
24
import useSplitNavigatorScreenOptions from '@libs/Navigation/AppNavigator/useSplitNavigatorScreenOptions';
35
import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation';
46
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
@@ -15,9 +17,12 @@ const loadSearchMoneyReportPage = () => require<ReactComponentModule>('@pages/Se
1517

1618
const Stack = createSearchFullscreenNavigator<SearchFullscreenNavigatorParamList>();
1719

18-
function SearchFullscreenNavigator({route}: PlatformStackScreenProps<AuthScreensParamList, typeof NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR>) {
20+
function SearchFullscreenNavigator({route, navigation}: PlatformStackScreenProps<AuthScreensParamList, typeof NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR>) {
1921
// These options can be used here because the full screen navigator has the same structure as the split navigator in terms of the central screens, but it does not have a sidebar.
2022
const {centralScreen: centralScreenOptions} = useSplitNavigatorScreenOptions();
23+
24+
usePreloadFullScreenNavigators(navigation, NAVIGATION_TABS.SEARCH);
25+
2126
return (
2227
<FreezeWrapper>
2328
<Stack.Navigator

src/libs/Navigation/AppNavigator/Navigators/SettingsSplitNavigator.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ import {useRoute} from '@react-navigation/native';
22
import React from 'react';
33
import {View} from 'react-native';
44
import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen';
5+
import NAVIGATION_TABS from '@components/Navigation/NavigationTabBar/NAVIGATION_TABS';
56
import createSplitNavigator from '@libs/Navigation/AppNavigator/createSplitNavigator';
7+
import usePreloadFullScreenNavigators from '@libs/Navigation/AppNavigator/usePreloadFullScreenNavigators';
68
import useSplitNavigatorScreenOptions from '@libs/Navigation/AppNavigator/useSplitNavigatorScreenOptions';
7-
import type {SettingsSplitNavigatorParamList} from '@libs/Navigation/types';
9+
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
10+
import type {AuthScreensParamList, SettingsSplitNavigatorParamList} from '@libs/Navigation/types';
11+
import type NAVIGATORS from '@src/NAVIGATORS';
812
import SCREENS from '@src/SCREENS';
913
import type ReactComponentModule from '@src/types/utils/ReactComponentModule';
1014

@@ -25,10 +29,12 @@ const CENTRAL_PANE_SETTINGS_SCREENS = {
2529

2630
const Split = createSplitNavigator<SettingsSplitNavigatorParamList>();
2731

28-
function SettingsSplitNavigator() {
32+
function SettingsSplitNavigator({navigation}: PlatformStackScreenProps<AuthScreensParamList, typeof NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR>) {
2933
const route = useRoute();
3034
const splitNavigatorScreenOptions = useSplitNavigatorScreenOptions();
3135

36+
usePreloadFullScreenNavigators(navigation, NAVIGATION_TABS.SETTINGS);
37+
3238
return (
3339
<FocusTrapForScreens>
3440
<View style={{flex: 1}}>

src/libs/Navigation/AppNavigator/Navigators/WorkspaceSplitNavigator.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import React, {useEffect} from 'react';
22
import {View} from 'react-native';
33
import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen';
4+
import NAVIGATION_TABS from '@components/Navigation/NavigationTabBar/NAVIGATION_TABS';
45
import {workspaceSplitsWithoutEnteringAnimation} from '@libs/Navigation/AppNavigator/createRootStackNavigator/GetStateForActionHandlers';
56
import createSplitNavigator from '@libs/Navigation/AppNavigator/createSplitNavigator';
7+
import usePreloadFullScreenNavigators from '@libs/Navigation/AppNavigator/usePreloadFullScreenNavigators';
68
import useSplitNavigatorScreenOptions from '@libs/Navigation/AppNavigator/useSplitNavigatorScreenOptions';
79
import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation';
810
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
@@ -38,6 +40,8 @@ const Split = createSplitNavigator<WorkspaceSplitNavigatorParamList>();
3840
function WorkspaceSplitNavigator({route, navigation}: PlatformStackScreenProps<AuthScreensParamList, typeof NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR>) {
3941
const splitNavigatorScreenOptions = useSplitNavigatorScreenOptions();
4042

43+
usePreloadFullScreenNavigators(navigation, NAVIGATION_TABS.WORKSPACES);
44+
4145
useEffect(() => {
4246
const unsubscribe = navigation.addListener('transitionEnd', () => {
4347
// We want to call this function only once.
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import {findFocusedRoute, useFocusEffect} from '@react-navigation/native';
2+
import {useCallback} from 'react';
3+
import type {ValueOf} from 'type-fest';
4+
import NAVIGATION_TABS from '@components/Navigation/NavigationTabBar/NAVIGATION_TABS';
5+
import useSubscriptionPlan from '@hooks/useSubscriptionPlan';
6+
import {isAnonymousUser} from '@libs/actions/Session';
7+
import getIsNarrowLayout from '@libs/getIsNarrowLayout';
8+
import {getSettingsTabStateFromSessionStorage} from '@libs/Navigation/helpers/lastVisitedTabPathUtils';
9+
import {TAB_TO_FULLSCREEN} from '@libs/Navigation/linkingConfig/RELATIONS';
10+
import Navigation from '@libs/Navigation/Navigation';
11+
import type {PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNavigation/types';
12+
import type {AuthScreensParamList, FullScreenName, SearchFullscreenNavigatorParamList, SettingsSplitNavigatorParamList, WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
13+
import {buildCannedSearchQuery, buildSearchQueryJSON, buildSearchQueryString} from '@libs/SearchQueryUtils';
14+
import type CONST from '@src/CONST';
15+
import NAVIGATORS from '@src/NAVIGATORS';
16+
import SCREENS from '@src/SCREENS';
17+
import {getPreservedNavigatorState} from './createSplitNavigator/usePreserveNavigatorState';
18+
19+
function preloadWorkspacesTab(navigation: PlatformStackNavigationProp<AuthScreensParamList>) {
20+
const lastWorkspacesSplitNavigator = navigation.getState().routes.findLast((route) => route.name === NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR);
21+
22+
if (!lastWorkspacesSplitNavigator) {
23+
navigation.preload(SCREENS.WORKSPACES_LIST, {});
24+
return;
25+
}
26+
27+
if (!lastWorkspacesSplitNavigator?.state) {
28+
return;
29+
}
30+
31+
const focusedWorkspaceRoute = findFocusedRoute(lastWorkspacesSplitNavigator.state);
32+
33+
if (!focusedWorkspaceRoute || !focusedWorkspaceRoute?.params) {
34+
return;
35+
}
36+
37+
if (getIsNarrowLayout()) {
38+
navigation.preload(NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR, {
39+
screen: SCREENS.WORKSPACE.INITIAL,
40+
params: focusedWorkspaceRoute.params as WorkspaceSplitNavigatorParamList[typeof SCREENS.WORKSPACE.INITIAL],
41+
});
42+
return;
43+
}
44+
45+
navigation.preload(NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR, {
46+
screen: focusedWorkspaceRoute.name,
47+
params: focusedWorkspaceRoute.params,
48+
});
49+
}
50+
51+
function preloadReportsTab(navigation: PlatformStackNavigationProp<AuthScreensParamList>) {
52+
const lastSearchNavigator = navigation.getState().routes.findLast((route) => route.name === NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR);
53+
const lastSearchNavigatorState = lastSearchNavigator && lastSearchNavigator.key ? getPreservedNavigatorState(lastSearchNavigator?.key) : undefined;
54+
const lastSearchRoute = lastSearchNavigatorState?.routes.findLast((route) => route.name === SCREENS.SEARCH.ROOT);
55+
56+
if (lastSearchRoute) {
57+
const {q, ...rest} = lastSearchRoute.params as SearchFullscreenNavigatorParamList[typeof SCREENS.SEARCH.ROOT];
58+
const queryJSON = buildSearchQueryJSON(q);
59+
if (queryJSON) {
60+
const query = buildSearchQueryString(queryJSON);
61+
navigation.preload(NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR, {screen: SCREENS.SEARCH.ROOT, params: {q: query, ...rest}});
62+
}
63+
}
64+
65+
navigation.preload(NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR, {screen: SCREENS.SEARCH.ROOT, params: {q: buildCannedSearchQuery()}});
66+
}
67+
68+
function preloadAccountTab(navigation: PlatformStackNavigationProp<AuthScreensParamList>, subscriptionPlan: ValueOf<typeof CONST.POLICY.TYPE> | null) {
69+
if (!getIsNarrowLayout()) {
70+
const settingsTabState = getSettingsTabStateFromSessionStorage();
71+
72+
if (!settingsTabState) {
73+
navigation.preload(NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR, {screen: SCREENS.SETTINGS.PROFILE.ROOT, params: {}});
74+
return;
75+
}
76+
77+
const screenName = findFocusedRoute(settingsTabState)?.name as keyof SettingsSplitNavigatorParamList | undefined;
78+
79+
if ((!subscriptionPlan && screenName === SCREENS.SETTINGS.SUBSCRIPTION.ROOT) || !screenName) {
80+
navigation.preload(NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR, {screen: SCREENS.SETTINGS.PROFILE.ROOT, params: {}});
81+
return;
82+
}
83+
84+
navigation.preload(NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR, {
85+
screen: screenName,
86+
});
87+
return;
88+
}
89+
navigation.preload(NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR, {screen: SCREENS.SETTINGS.ROOT});
90+
}
91+
92+
function preloadInboxTab(navigation: PlatformStackNavigationProp<AuthScreensParamList>) {
93+
const payload = getIsNarrowLayout() ? {screen: SCREENS.HOME} : {screen: SCREENS.REPORT};
94+
navigation.preload(NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, payload);
95+
}
96+
97+
/**
98+
* Hook that preloads all fullscreen navigators except the current one.
99+
* This helps improve performance by preloading navigators that might be needed soon.
100+
*/
101+
function usePreloadFullScreenNavigators(navigation: PlatformStackNavigationProp<AuthScreensParamList>, fullscreenTabName: keyof typeof NAVIGATION_TABS) {
102+
const state = navigation.getState();
103+
const {preloadedRoutes} = state;
104+
const subscriptionPlan = useSubscriptionPlan();
105+
106+
useFocusEffect(
107+
useCallback(() => {
108+
if (isAnonymousUser()) {
109+
return;
110+
}
111+
112+
Navigation.setNavigationActionToMicrotaskQueue(() => {
113+
Object.values(NAVIGATION_TABS)
114+
.filter((tabName) => {
115+
const isCurrentTab = tabName === fullscreenTabName;
116+
const isRouteAlreadyPreloaded = preloadedRoutes.some((preloadedRoute) => TAB_TO_FULLSCREEN[fullscreenTabName].includes(preloadedRoute.name as FullScreenName));
117+
return !isCurrentTab && !isRouteAlreadyPreloaded;
118+
})
119+
.forEach((tabName) => {
120+
switch (tabName) {
121+
case NAVIGATION_TABS.WORKSPACES:
122+
preloadWorkspacesTab(navigation);
123+
return;
124+
case NAVIGATION_TABS.SEARCH:
125+
preloadReportsTab(navigation);
126+
return;
127+
case NAVIGATION_TABS.SETTINGS:
128+
preloadAccountTab(navigation, subscriptionPlan);
129+
return;
130+
case NAVIGATION_TABS.HOME:
131+
preloadInboxTab(navigation);
132+
return;
133+
default:
134+
return undefined;
135+
}
136+
});
137+
});
138+
}, [fullscreenTabName, navigation, preloadedRoutes, subscriptionPlan]),
139+
);
140+
}
141+
142+
export default usePreloadFullScreenNavigators;

src/libs/Navigation/linkingConfig/RELATIONS/FULLSCREEN_TO_TAB.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)