-
Notifications
You must be signed in to change notification settings - Fork 3k
/
Copy pathNavigationRoot.tsx
190 lines (159 loc) · 7.06 KB
/
NavigationRoot.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import type {NavigationState} from '@react-navigation/native';
import {DefaultTheme, findFocusedRoute, NavigationContainer} from '@react-navigation/native';
import React, {useContext, useEffect, useMemo, useRef} from 'react';
import {useOnyx} from 'react-native-onyx';
import HybridAppMiddleware from '@components/HybridAppMiddleware';
import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useCurrentReportID from '@hooks/useCurrentReportID';
import useTheme from '@hooks/useTheme';
import useWindowDimensions from '@hooks/useWindowDimensions';
import Firebase from '@libs/Firebase';
import {FSPage} from '@libs/Fullstory';
import hasCompletedGuidedSetupFlowSelector from '@libs/hasCompletedGuidedSetupFlowSelector';
import Log from '@libs/Log';
import {getPathFromURL} from '@libs/Url';
import {updateLastVisitedPath} from '@userActions/App';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import ROUTES from '@src/ROUTES';
import AppNavigator from './AppNavigator';
import getPolicyIDFromState from './getPolicyIDFromState';
import linkingConfig from './linkingConfig';
import customGetPathFromState from './linkingConfig/customGetPathFromState';
import getAdaptedStateFromPath from './linkingConfig/getAdaptedStateFromPath';
import Navigation, {navigationRef} from './Navigation';
import setupCustomAndroidBackHandler from './setupCustomAndroidBackHandler';
import type {RootStackParamList} from './types';
type NavigationRootProps = {
/** Whether the current user is logged in with an authToken */
authenticated: boolean;
/** Stores path of last visited page */
lastVisitedPath: Route;
/** Initial url */
initialUrl: string | null;
/** Fired when react-navigation is ready */
onReady: () => void;
};
/**
* Intercept navigation state changes and log it
*/
function parseAndLogRoute(state: NavigationState) {
if (!state) {
return;
}
const currentPath = customGetPathFromState(state, linkingConfig.config);
const focusedRoute = findFocusedRoute(state);
if (focusedRoute && !CONST.EXCLUDE_FROM_LAST_VISITED_PATH.includes(focusedRoute?.name)) {
updateLastVisitedPath(currentPath);
}
// Don't log the route transitions from OldDot because they contain authTokens
if (currentPath.includes('/transition')) {
Log.info('Navigating from transition link from OldDot using short lived authToken');
} else {
Log.info('Navigating to route', false, {path: currentPath});
}
Navigation.setIsNavigationReady();
// Fullstory Page navigation tracking
const focusedRouteName = focusedRoute?.name;
if (focusedRouteName) {
new FSPage(focusedRouteName, {path: currentPath}).start();
}
}
function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: NavigationRootProps) {
const firstRenderRef = useRef(true);
const theme = useTheme();
const {cleanStaleScrollOffsets} = useContext(ScrollOffsetContext);
const currentReportIDValue = useCurrentReportID();
const {isSmallScreenWidth} = useWindowDimensions();
const {setActiveWorkspaceID} = useActiveWorkspace();
const [user] = useOnyx(ONYXKEYS.USER);
const [hasCompletedGuidedSetupFlow] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {
selector: hasCompletedGuidedSetupFlowSelector,
});
const initialState = useMemo(() => {
if (!user || user.isFromPublicDomain) {
return;
}
// If the user haven't completed the flow, we want to always redirect them to the onboarding flow.
// We also make sure that the user is authenticated.
if (!hasCompletedGuidedSetupFlow && authenticated) {
const {adaptedState} = getAdaptedStateFromPath(ROUTES.ONBOARDING_ROOT.route, linkingConfig.config);
return adaptedState;
}
// If there is no lastVisitedPath, we can do early return. We won't modify the default behavior.
if (!lastVisitedPath) {
return undefined;
}
const path = initialUrl ? getPathFromURL(initialUrl) : null;
// If the user opens the root of app "/" it will be parsed to empty string "".
// If the path is defined and different that empty string we don't want to modify the default behavior.
if (path) {
return;
}
// Otherwise we want to redirect the user to the last visited path.
const {adaptedState} = getAdaptedStateFromPath(lastVisitedPath, linkingConfig.config);
return adaptedState;
// The initialState value is relevant only on the first render.
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, []);
// https://reactnavigation.org/docs/themes
const navigationTheme = useMemo(
() => ({
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: theme.appBG,
},
}),
[theme],
);
useEffect(() => {
if (firstRenderRef.current) {
setupCustomAndroidBackHandler();
// we don't want to make the report back button go back to LHN if the user
// started on the small screen so we don't set it on the first render
// making it only work on consecutive changes of the screen size
firstRenderRef.current = false;
return;
}
Navigation.setShouldPopAllStateOnUP(!isSmallScreenWidth);
}, [isSmallScreenWidth]);
const handleStateChange = (state: NavigationState | undefined) => {
if (!state) {
return;
}
const currentRoute = navigationRef.getCurrentRoute();
Firebase.log(`[NAVIGATION] screen: ${currentRoute?.name}, params: ${JSON.stringify(currentRoute?.params ?? {})}`);
const activeWorkspaceID = getPolicyIDFromState(state as NavigationState<RootStackParamList>);
// Performance optimization to avoid context consumers to delay first render
setTimeout(() => {
currentReportIDValue?.updateCurrentReportID(state);
setActiveWorkspaceID(activeWorkspaceID);
}, 0);
parseAndLogRoute(state);
// We want to clean saved scroll offsets for screens that aren't anymore in the state.
cleanStaleScrollOffsets(state);
};
return (
<NavigationContainer
initialState={initialState}
onStateChange={handleStateChange}
onReady={onReady}
theme={navigationTheme}
ref={navigationRef}
linking={linkingConfig}
documentTitle={{
enabled: false,
}}
>
{/* HybridAppMiddleware needs to have access to navigation ref and SplashScreenHidden context */}
<HybridAppMiddleware authenticated={authenticated}>
<AppNavigator authenticated={authenticated} />
</HybridAppMiddleware>
</NavigationContainer>
);
}
NavigationRoot.displayName = 'NavigationRoot';
export default NavigationRoot;