diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 8f59b828bdd3..1342fb620e17 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -36,6 +36,7 @@ import RELATIONS from './linkingConfig/RELATIONS'; import navigationRef from './navigationRef'; import type {NavigationPartialRoute, NavigationStateRoute, RootStackParamList, State} from './types'; +// Get the sidebar screen parameters from the split navigator passed as a param function getSidebarScreenParams(splitNavigatorRoute: NavigationStateRoute) { if (splitNavigatorRoute.name === NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR) { return splitNavigatorRoute.state?.routes?.at(0)?.params; @@ -68,18 +69,16 @@ function canNavigate(methodName: string, params: Record = {}): return false; } -// Re-exporting the getTopmostReportId here to fill in default value for state. The getTopmostReportId isn't defined in this file to avoid cyclic dependencies. +// Extracts from the topmost report its id. const getTopmostReportId = (state = navigationRef.getState()) => getTopmostReportParam(state, 'reportID'); -// Re-exporting the getTopmostReportActionID here to fill in default value for state. The getTopmostReportActionID isn't defined in this file to avoid cyclic dependencies. +// Extracts from the topmost report its action id. const getTopmostReportActionId = (state = navigationRef.getState()) => getTopmostReportParam(state, 'reportActionID'); // Re-exporting the closeRHPFlow here to fill in default value for navigationRef. The closeRHPFlow isn't defined in this file to avoid cyclic dependencies. const closeRHPFlow = (ref = navigationRef) => originalCloseRHPFlow(ref); -/** - * Function that generates dynamic urls from paths passed from OldDot - */ +// Function that generates dynamic urls from paths passed from OldDot. function parseHybridAppUrl(url: HybridAppRoute | Route): Route { switch (url) { case HYBRID_APP_ROUTES.MONEY_REQUEST_CREATE_TAB_MANUAL: @@ -94,7 +93,7 @@ function parseHybridAppUrl(url: HybridAppRoute | Route): Route { } } -/** Returns the current active route */ +// Returns the current active route. function getActiveRoute(): string { const currentRoute = navigationRef.current && navigationRef.current.getCurrentRoute(); if (!currentRoute?.name) { @@ -109,7 +108,7 @@ function getActiveRoute(): string { return ''; } - +// Returns the route of a report opened in RHP. function getReportRHPActiveRoute(): string { if (isReportOpenInRHP(navigationRef.getRootState())) { return getActiveRoute(); @@ -146,10 +145,14 @@ function navigate(route: Route = ROUTES.HOME, type?: string) { pendingRoute = route; return; } - // linkTo(navigationRef.current, route, type, isActiveRoute(route)); + linkTo(navigationRef.current, route, type); } +/** + * When routes are compared to determine whether the fallback route passed to the goUp function is in the state, + * these parameters shouldn't be included in the comparison. + */ const routeParamsIgnore = ['path', 'initial', 'params', 'state', 'screen', 'policyID']; // If we use destructuring, we will get an error if any of the ignored properties are not present in the object. @@ -186,13 +189,24 @@ function doesRouteMatchToMinimalActionPayload(route: NavigationStateRoute | Navi return shallowCompare(routeParams, minimalActionParams); } +// Checks whether the given state is the root navigator state +function isRootNavigatorState(state: State): state is State { + return state.key === navigationRef.current?.getRootState().key; +} + type GoBackOptions = { - /** If we should compare params when searching for a route in state to go up to. + /** + * If we should compare params when searching for a route in state to go up to. * There are situations where we want to compare params when going up e.g. goUp to a specific report. * Sometimes we want to go up and update params of screen e.g. country picker. - * In that case we want to goUp to a country picker with any params so we don't compare them. */ + * In that case we want to goUp to a country picker with any params so we don't compare them. + */ compareParams?: boolean; + /** + * Specifies whether goBack should pop to top when invoked. + * Additionaly, to execute popToTop, set the value of the global variable ShouldPopAllStateOnUP to true using the setShouldPopAllStateOnUP function. + */ shouldPopToTop?: boolean; }; @@ -201,14 +215,17 @@ const defaultGoBackOptions: Required = { shouldPopToTop: false, }; -function isRootNavigatorState(state: State): state is State { - return state.key === navigationRef.current?.getRootState().key; -} - +/** + * Navigate to the given fallbackRoute taking into account whether it is possible to go back to this screen. Within one nested navigator, we can go back by any number + * of screens, but if as a result of going back we would have to remove more than one screen from the rootState, + * replace is performed so as not to lose the visited pages. + * If fallbackRoute is not found in the state, replace is also called then. + * + * @param fallbackRoute - The route to go up. + * @param options - Optional configuration that affects navigation logic, such as parameter comparison. + */ function goUp(fallbackRoute: Route, options?: GoBackOptions) { - const compareParams = options?.compareParams ?? defaultGoBackOptions.compareParams; - - if (!canNavigate('goBack')) { + if (!canNavigate('goUp')) { return; } @@ -231,6 +248,7 @@ function goUp(fallbackRoute: Route, options?: GoBackOptions) { return; } + const compareParams = options?.compareParams ?? defaultGoBackOptions.compareParams; const indexOfFallbackRoute = targetState.routes.findLastIndex((route) => doesRouteMatchToMinimalActionPayload(route, minimalAction, compareParams)); const distanceToPop = targetState.routes.length - indexOfFallbackRoute - 1; @@ -242,8 +260,10 @@ function goUp(fallbackRoute: Route, options?: GoBackOptions) { return; } - // If we are not comparing params, we want to use navigate action because it will replace params in the route already existing in the state if necessary. - // This part will need refactor after migrating to react-navigation 7. We will use popTo instead. + /** + * If we are not comparing params, we want to use navigate action because it will replace params in the route already existing in the state if necessary. + * This part will need refactor after migrating to react-navigation 7. We will use popTo instead. + */ if (!compareParams) { navigationRef.current.dispatch(minimalAction); return; @@ -254,16 +274,14 @@ function goUp(fallbackRoute: Route, options?: GoBackOptions) { /** * @param fallbackRoute - Fallback route if pop/goBack action should, but is not possible within RHP - * @param shouldPopToTop - Should we navigate to LHN on back press + * @param options - Optional configuration that affects navigation logic, e.g. whether goBack should popToTop. */ function goBack(fallbackRoute?: Route, options?: GoBackOptions) { if (!canNavigate('goBack')) { return; } - const shouldPopToTop = options?.shouldPopToTop ?? false; - - if (shouldPopToTop) { + if (options?.shouldPopToTop) { if (shouldPopAllStateOnUP) { shouldPopAllStateOnUP = false; navigationRef.current?.dispatch(StackActions.popToTop()); @@ -302,9 +320,7 @@ function goBack(fallbackRoute?: Route, options?: GoBackOptions) { navigationRef.current?.goBack(); } -/** - * Reset the navigation state to Home page - */ +// Reset the navigation state to Home page function resetToHome() { const isNarrowLayout = getIsNarrowLayout(); const rootState = navigationRef.getRootState(); @@ -318,9 +334,7 @@ function resetToHome() { navigationRef.dispatch({payload, type: 'REPLACE', target: rootState.key}); } -/** - * Update route params for the specified route. - */ +// Update route params for the specified route. function setParams(params: Record, routeKey = '') { navigationRef.current?.dispatch({ ...CommonActions.setParams(params), @@ -328,14 +342,12 @@ function setParams(params: Record, routeKey = '') { }); } -/** - * Returns the current active route without the URL params - */ +// Returns the current active route without the URL params. function getActiveRouteWithoutParams(): string { return getActiveRoute().replace(/\?.*/, ''); } -/** Returns the active route name from a state event from the navigationRef */ +// Returns the active route name from a state event from the navigationRef. function getRouteNameFromStateEvent(event: EventArg<'state', false, NavigationContainerEventMap['state']['data']>): string | undefined { if (!event.data.state) { return; @@ -415,12 +427,17 @@ function waitForProtectedRoutes() { }); } +// Changes the currently selected policy in the app. function switchPolicyID(policyID?: string) { navigationRef.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.SWITCH_POLICY_ID, payload: {policyID}}); } type NavigateToReportWithPolicyCheckPayload = {report?: OnyxEntry; reportID?: string; reportActionID?: string; referrer?: string; policyIDToCheck?: string}; +/** + * Navigates to a report passed as a param (as an id or report object) and checks whether the target object belongs to the currently selected workspace. + * If not, the current workspace is set to global. + */ function navigateToReportWithPolicyCheck({report, reportID, reportActionID, referrer, policyIDToCheck}: NavigateToReportWithPolicyCheckPayload, ref = navigationRef) { const targetReport = reportID ? {reportID, ...ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]} : report; const policyID = policyIDToCheck ?? getPolicyIDFromState(navigationRef.getRootState() as State); @@ -453,8 +470,7 @@ function navigateToReportWithPolicyCheck({report, reportID, reportActionID, refe ); } -// @TODO In places where we use dismissModal with report arg we should do dismiss modal and then navigate to the report. -// We left it here to limit the number of changed files. +// Closes the modal navigator (RHP, LHP, onboarding). const dismissModal = (reportID?: string, ref = navigationRef) => { ref.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL}); if (!reportID) { @@ -462,6 +478,8 @@ const dismissModal = (reportID?: string, ref = navigationRef) => { } isNavigationReady().then(() => navigateToReportWithPolicyCheck({reportID})); }; + +// Dismisses the modal and opens the given report. const dismissModalWithReport = (report: OnyxEntry) => { dismissModal(); isNavigationReady().then(() => navigateToReportWithPolicyCheck({report})); diff --git a/src/libs/Navigation/helpers/customGetPathFromState.ts b/src/libs/Navigation/helpers/customGetPathFromState.ts index 0a42aa67a1ce..24fa3dbe1321 100644 --- a/src/libs/Navigation/helpers/customGetPathFromState.ts +++ b/src/libs/Navigation/helpers/customGetPathFromState.ts @@ -2,6 +2,7 @@ import {getPathFromState} from '@react-navigation/native'; import NAVIGATORS from '@src/NAVIGATORS'; import {isFullScreenName} from './isNavigatorName'; +// This function adds the policyID param to the url. const customGetPathFromState: typeof getPathFromState = (state, options) => { const path = getPathFromState(state, options); const fullScreenRoute = state.routes.findLast((route) => isFullScreenName(route.name)); diff --git a/src/libs/Navigation/helpers/getOnboardingAdaptedState.ts b/src/libs/Navigation/helpers/getOnboardingAdaptedState.ts index eee3f9f5e52d..cbb0ac71b8c4 100644 --- a/src/libs/Navigation/helpers/getOnboardingAdaptedState.ts +++ b/src/libs/Navigation/helpers/getOnboardingAdaptedState.ts @@ -1,6 +1,10 @@ import type {NavigationState, PartialState} from '@react-navigation/native'; import SCREENS from '@src/SCREENS'; +/** + * When we open the application via deeplink to a specific onboarding screen, we want the previous onboarding screens to be able to go back to them. + * Therefore, the paths of the previous screens are added here. + */ export default function getOnboardingAdaptedState(state: PartialState): PartialState { const onboardingRoute = state.routes.at(0); if (!onboardingRoute || onboardingRoute.name === SCREENS.ONBOARDING.PURPOSE) { diff --git a/src/libs/Navigation/helpers/getPolicyIDFromState.ts b/src/libs/Navigation/helpers/getPolicyIDFromState.ts index b4b81ee15db8..808455834247 100644 --- a/src/libs/Navigation/helpers/getPolicyIDFromState.ts +++ b/src/libs/Navigation/helpers/getPolicyIDFromState.ts @@ -7,7 +7,7 @@ import extractPolicyIDFromQuery from './extractPolicyIDFromQuery'; * returns policyID value if one exists in navigation state * * PolicyID in this app can be stored in two ways: - * - on most screens but NOT Search as `policyID` param (on bottom tab screens) + * - on NAVIGATORS.REPORTS_SPLIT_NAVIGATOR as `policyID` param * - on Search related screens as policyID filter inside `q` (SearchQuery) param (only for SEARCH_CENTRAL_PANE) */ const getPolicyIDFromState = (state: State): string | undefined => { diff --git a/src/libs/Navigation/helpers/getTopmostReportParam.ts b/src/libs/Navigation/helpers/getTopmostReportParam.ts index 026cd379aa1a..40955d719755 100644 --- a/src/libs/Navigation/helpers/getTopmostReportParam.ts +++ b/src/libs/Navigation/helpers/getTopmostReportParam.ts @@ -6,9 +6,10 @@ import SCREENS from '@src/SCREENS'; // This function is in a separate file than Navigation.ts to avoid cyclic dependency. /** - * Find the last visited report screen in the navigation state and get the id of it. + * Find the last visited report screen in the navigation state and get its specific param (id or action id). * * @param state - The react-navigation state + * @param reportParam - param to get from the report route params * @returns - It's possible that there is no report screen */ diff --git a/src/libs/Navigation/helpers/isNavigatorName.ts b/src/libs/Navigation/helpers/isNavigatorName.ts index ae5118b9833e..db8b103309ec 100644 --- a/src/libs/Navigation/helpers/isNavigatorName.ts +++ b/src/libs/Navigation/helpers/isNavigatorName.ts @@ -18,6 +18,10 @@ const FULL_SCREENS_SET = new Set(FULL_SCREENS); const SIDEBARS_SET = new Set(SIDEBARS); const ONBOARDING_SCREENS_SET = new Set(ONBOARDING_SCREENS); +/** + * Functions defined below are used to check whether a screen belongs to a specific group. + * It is mainly used to filter routes in the navigation state. + */ function checkIfScreenHasMatchingNameToSetValues(screen: string | undefined, set: Set): screen is T { if (!screen) { return false; diff --git a/src/libs/Navigation/helpers/isReportOpenInRHP.ts b/src/libs/Navigation/helpers/isReportOpenInRHP.ts index 51e8a95bb66b..6158c3ec9d04 100644 --- a/src/libs/Navigation/helpers/isReportOpenInRHP.ts +++ b/src/libs/Navigation/helpers/isReportOpenInRHP.ts @@ -2,6 +2,7 @@ import type {NavigationState} from '@react-navigation/native'; import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; +// Determines whether the report page is opened in RHP. const isReportOpenInRHP = (state: NavigationState | undefined): boolean => { const lastRoute = state?.routes?.at(-1); if (!lastRoute) { diff --git a/src/libs/Navigation/helpers/setupCustomAndroidBackHandler/index.android.ts b/src/libs/Navigation/helpers/setupCustomAndroidBackHandler/index.android.ts index 04c1791145ca..54b16e09947e 100644 --- a/src/libs/Navigation/helpers/setupCustomAndroidBackHandler/index.android.ts +++ b/src/libs/Navigation/helpers/setupCustomAndroidBackHandler/index.android.ts @@ -1,7 +1,7 @@ import {BackHandler, NativeModules} from 'react-native'; import navigationRef from '@navigation/navigationRef'; -// We need to do some custom handling for the back button on Android for actions related to the search page. +// We need to do some custom handling for the back button on Android for actions related to the hybrid app. function setupCustomAndroidBackHandler() { const onBackPress = () => { const rootState = navigationRef.getRootState();