Skip to content

Commit

Permalink
extract helpers from linkTo
Browse files Browse the repository at this point in the history
  • Loading branch information
adamgrzybowski committed Nov 18, 2024
1 parent 9410348 commit 8549d41
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 98 deletions.
11 changes: 7 additions & 4 deletions src/libs/Navigation/Navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ import originalGetTopmostReportId from './getTopmostReportId';
import isReportOpenInRHP from './isReportOpenInRHP';
import linkingConfig from './linkingConfig';
import createSplitNavigator from './linkingConfig/createSplitNavigator';
import type {LinkToOptions} from './linkTo';
import linkTo, {convertReportPath, shouldConvertReportPath} from './linkTo';
import getMinimalAction from './linkTo/getMinimalAction';
import linkTo from './linkTo';
import convertReportPath from './linkTo/helpers/convertReportPath';
import getMinimalAction from './linkTo/helpers/getMinimalAction';
import shouldConvertReportPath from './linkTo/helpers/shouldConvertReportPath';
import type {LinkToOptions} from './linkTo/types';
import navigationRef from './navigationRef';
import setNavigationActionToMicrotaskQueue from './setNavigationActionToMicrotaskQueue';
import type {NavigationPartialRoute, NavigationStateRoute, RootStackParamList, SplitNavigatorLHNScreen, SplitNavigatorParamListType, State} from './types';
Expand Down Expand Up @@ -464,9 +466,10 @@ 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.
// @TODO There should be a way to not use as string.
// We left it here to limit the number of changed files.
const dismissModal = (reportID?: string, ref = navigationRef) => {
ref.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL});
ref.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL as string});
if (!reportID) {
return;
}
Expand Down
16 changes: 16 additions & 0 deletions src/libs/Navigation/linkTo/helpers/areNamesAndParamsEqual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {findFocusedRoute} from '@react-navigation/native';
import type {NavigationState, PartialState} from '@react-navigation/native';
import type {RootStackParamList} from '@libs/Navigation/types';
import {shallowCompare} from '@libs/ObjectUtils';

// Compare this and next state to determine if focused route names and params are equal.
function areNamesAndParamsEqual(currentState: NavigationState<RootStackParamList>, stateFromPath: PartialState<NavigationState<RootStackParamList>>) {
const currentFocusedRoute = findFocusedRoute(currentState);
const targetFocusedRoute = findFocusedRoute(stateFromPath);

const areNamesEqual = currentFocusedRoute?.name === targetFocusedRoute?.name;
const areParamsEqual = shallowCompare(currentFocusedRoute?.params as Record<string, unknown> | undefined, targetFocusedRoute?.params as Record<string, unknown> | undefined);

return areNamesEqual && areParamsEqual;
}
export default areNamesAndParamsEqual;
16 changes: 16 additions & 0 deletions src/libs/Navigation/linkTo/helpers/convertReportPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type {NavigationPartialRoute, ReportsSplitNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';

// We have two types of report screens. One is in the search central pane and the other is in the right hand pane.
// This functions converts the path to the opposite form.
function convertReportPath(focusedRouteFromPath: NavigationPartialRoute) {
const params = focusedRouteFromPath.params as ReportsSplitNavigatorParamList[typeof SCREENS.REPORT] | SearchReportParamList[typeof SCREENS.SEARCH.REPORT_RHP];
if (focusedRouteFromPath.name === SCREENS.REPORT) {
return ROUTES.SEARCH_REPORT.getRoute({reportID: params.reportID, reportActionID: params.reportActionID});
}

return ROUTES.REPORT_WITH_ID.getRoute(params.reportID, params.reportActionID);
}

export default convertReportPath;
21 changes: 21 additions & 0 deletions src/libs/Navigation/linkTo/helpers/createActionWithPolicyID.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type {StackActionType} from '@react-navigation/native';

// Add policyID to the action payload
function createActionWithPolicyID(action: StackActionType, policyID: string): StackActionType | undefined {
if (action.type !== 'PUSH' && action.type !== 'REPLACE') {
return;
}

return {
...action,
payload: {
...action.payload,
params: {
...action.payload.params,
policyID,
},
},
};
}

export default createActionWithPolicyID;
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type {NavigationAction, NavigationState} from '@react-navigation/native';
import type {Writable} from 'type-fest';
import type {ActionPayloadParams} from '@libs/Navigation/linkTo/types';
import type {RootStackParamList, StackNavigationAction} from '@libs/Navigation/types';
import getTopmostBottomTabRoute from '@navigation/getTopmostBottomTabRoute';
import CONST from '@src/CONST';
import type {ActionPayloadParams} from './types';

// Because we need to change the type to push, we also need to set target for this action to the bottom tab navigator.
function getActionForBottomTabNavigator(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {NavigationAction, NavigationState} from '@react-navigation/native';
import type {Writable} from 'type-fest';
import type {ActionPayload} from '@libs/Navigation/linkTo/types';
import type {State} from '@navigation/types';
import type {ActionPayload} from './types';

type MinimalAction = {
action: Writable<NavigationAction>;
Expand All @@ -28,6 +28,7 @@ function getMinimalAction(action: NavigationAction, state: NavigationState): Min
currentState = currentState?.routes[currentState.index ?? -1].state;
currentTargetKey = currentState?.key;

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const payload = currentAction.payload as ActionPayload;

// Creating new smaller action
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import SCREENS from '@src/SCREENS';

function isNavigatingToAttachmentScreen(focusedRouteName?: string) {
return focusedRouteName === SCREENS.ATTACHMENTS;
}

export default isNavigatingToAttachmentScreen;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type {NavigationPartialRoute, ReportsSplitNavigatorParamList} from '@libs/Navigation/types';
import SCREENS from '@src/SCREENS';

// Report screen is a specific case. We want to PUSH if the reportID is different.
// But if only the reportActionID is different we want to NAVIGATE.
// We don't want to push report with the same reportID on the browser history stack.
function isNavigatingToReportWithSameReportID(currentRoute: NavigationPartialRoute, newRoute: NavigationPartialRoute) {
if (currentRoute.name !== SCREENS.REPORT || newRoute.name !== SCREENS.REPORT) {
return false;
}

const currentParams = currentRoute.params as ReportsSplitNavigatorParamList[typeof SCREENS.REPORT];
type NewType = ReportsSplitNavigatorParamList;

const newParams = newRoute?.params as NewType[typeof SCREENS.REPORT];

return currentParams.reportID === newParams.reportID;
}

export default isNavigatingToReportWithSameReportID;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {StackNavigationAction} from '@libs/Navigation/types';
import NAVIGATORS from '@src/NAVIGATORS';

// We need to check if the screen displayed under the overlay matches the contend of modal navigator if we navigate to a modal navigator screen.
// Currently it's only for the RHP because screens in LHP matches to any content.
function shouldCheckFullScreenRouteMatching(action: StackNavigationAction): action is StackNavigationAction & {type: 'PUSH'; payload: {name: typeof NAVIGATORS.RIGHT_MODAL_NAVIGATOR}} {
return action !== undefined && action.type === 'PUSH' && action.payload.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR;
}

export default shouldCheckFullScreenRouteMatching;
18 changes: 18 additions & 0 deletions src/libs/Navigation/linkTo/helpers/shouldConvertReportPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type {NavigationPartialRoute} from '@libs/Navigation/types';
import SCREENS from '@src/SCREENS';

// Determine if we should convert the report route from fullscreen to rhp version or the other way around.
// It's necessary to stay in RHP if we are in RHP report or in fullscreen if we are in fullscreen report.
function shouldConvertReportPath(currentFocusedRoute: NavigationPartialRoute, focusedRouteFromPath: NavigationPartialRoute) {
// @TODO: Navigating from search central pane could be handled with explicit convert: false option. We would need to add it as option to linkTo.
if (focusedRouteFromPath.name === SCREENS.REPORT && (currentFocusedRoute.name === SCREENS.SEARCH.REPORT_RHP || currentFocusedRoute.name === SCREENS.SEARCH.CENTRAL_PANE)) {
return true;
}

if (focusedRouteFromPath.name === SCREENS.SEARCH.REPORT_RHP && currentFocusedRoute.name === SCREENS.REPORT) {
return true;
}
return false;
}

export default shouldConvertReportPath;
100 changes: 10 additions & 90 deletions src/libs/Navigation/linkTo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,107 +3,27 @@ import type {NavigationContainerRef, NavigationState, PartialState, StackActionT
import {findFocusedRoute, StackActions} from '@react-navigation/native';
import {getMatchingFullScreenRoute, isFullScreenName} from '@libs/Navigation/linkingConfig/getAdaptedStateFromPath';
import normalizePath from '@libs/Navigation/linkingConfig/normalizePath';
import {shallowCompare} from '@libs/ObjectUtils';
import {extractPolicyIDFromPath, getPathWithoutPolicyID} from '@libs/PolicyUtils';
import getStateFromPath from '@navigation/getStateFromPath';
import linkingConfig from '@navigation/linkingConfig';
import type {NavigationPartialRoute, ReportsSplitNavigatorParamList, RootStackParamList, SearchReportParamList, StackNavigationAction} from '@navigation/types';
import type {RootStackParamList, StackNavigationAction} from '@navigation/types';
import CONST from '@src/CONST';
import NAVIGATORS from '@src/NAVIGATORS';
import type {Route} from '@src/ROUTES';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import getMinimalAction from './getMinimalAction';

// TODO: move to helpers
// eslint-disable-next-line rulesdir/no-inline-named-export
export type LinkToOptions = Partial<{
// To explicitly set the action type to replace.
forceReplace: boolean;

// If true, the report route will be converted to the opposite form (fullscreen to rhp or rhp to fullscreen) if necessary.
// Check shouldConvertReportPath to see when it will be converted.
reportPathConversionEnabled: boolean;
}>;
import areNamesAndParamsEqual from './helpers/areNamesAndParamsEqual';
import convertReportPath from './helpers/convertReportPath';
import createActionWithPolicyID from './helpers/createActionWithPolicyID';
import getMinimalAction from './helpers/getMinimalAction';
import isNavigatingToAttachmentScreen from './helpers/isNavigatingToAttachmentScreen';
import isNavigatingToReportWithSameReportID from './helpers/isNavigatingToReportWithSameReportID';
import shouldCheckFullScreenRouteMatching from './helpers/shouldCheckFullScreenRouteMatching';
import shouldConvertReportPath from './helpers/shouldConvertReportPath';
import type {LinkToOptions} from './types';

const defaultLinkToOptions: LinkToOptions = {
forceReplace: false,
reportPathConversionEnabled: true,
};

function createActionWithPolicyID(action: StackActionType, policyID: string): StackActionType | undefined {
if (action.type !== 'PUSH' && action.type !== 'REPLACE') {
return;
}

return {
...action,
payload: {
...action.payload,
params: {
...action.payload.params,
policyID,
},
},
};
}

function areNamesAndParamsEqual(currentState: NavigationState<RootStackParamList>, stateFromPath: PartialState<NavigationState<RootStackParamList>>) {
const currentFocusedRoute = findFocusedRoute(currentState);
const targetFocusedRoute = findFocusedRoute(stateFromPath);

const areNamesEqual = currentFocusedRoute?.name === targetFocusedRoute?.name;
const areParamsEqual = shallowCompare(currentFocusedRoute?.params as Record<string, unknown> | undefined, targetFocusedRoute?.params as Record<string, unknown> | undefined);

return areNamesEqual && areParamsEqual;
}

function shouldCheckFullScreenRouteMatching(action: StackNavigationAction): action is StackNavigationAction & {type: 'PUSH'; payload: {name: typeof NAVIGATORS.RIGHT_MODAL_NAVIGATOR}} {
return action !== undefined && action.type === 'PUSH' && action.payload.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR;
}

function isNavigatingToAttachmentScreen(focusedRouteName?: string) {
return focusedRouteName === SCREENS.ATTACHMENTS;
}

function isNavigatingToReportWithSameReportID(currentRoute: NavigationPartialRoute, newRoute: NavigationPartialRoute) {
if (currentRoute.name !== SCREENS.REPORT || newRoute.name !== SCREENS.REPORT) {
return false;
}

const currentParams = currentRoute.params as ReportsSplitNavigatorParamList[typeof SCREENS.REPORT];
const newParams = newRoute?.params as ReportsSplitNavigatorParamList[typeof SCREENS.REPORT];

return currentParams.reportID === newParams.reportID;
}

// TODO: move to helpers
// Determine if we should convert the report route from fullscreen to rhp version or the other way around.
// It's necessary to stay in RHP if we are in RHP report or in fullscreen if we are in fullscreen report.
// eslint-disable-next-line rulesdir/no-inline-named-export
export function shouldConvertReportPath(currentFocusedRoute: NavigationPartialRoute, focusedRouteFromPath: NavigationPartialRoute) {
// @TODO: Navigating from search central pane could be handled with explicit convert: false option. We would need to add it as option to linkTo.
if (focusedRouteFromPath.name === SCREENS.REPORT && (currentFocusedRoute.name === SCREENS.SEARCH.REPORT_RHP || currentFocusedRoute.name === SCREENS.SEARCH.CENTRAL_PANE)) {
return true;
}

if (focusedRouteFromPath.name === SCREENS.SEARCH.REPORT_RHP && currentFocusedRoute.name === SCREENS.REPORT) {
return true;
}
return false;
}

// TODO: move to helpers
// eslint-disable-next-line rulesdir/no-inline-named-export
export function convertReportPath(focusedRouteFromPath: NavigationPartialRoute) {
const params = focusedRouteFromPath.params as ReportsSplitNavigatorParamList[typeof SCREENS.REPORT] | SearchReportParamList[typeof SCREENS.SEARCH.REPORT_RHP];
if (focusedRouteFromPath.name === SCREENS.REPORT) {
return ROUTES.SEARCH_REPORT.getRoute({reportID: params.reportID, reportActionID: params.reportActionID});
}

return ROUTES.REPORT_WITH_ID.getRoute(params.reportID, params.reportActionID);
}

export default function linkTo(navigation: NavigationContainerRef<RootStackParamList> | null, path: Route, options?: LinkToOptions) {
if (!navigation) {
throw new Error("Couldn't find a navigation object. Is your component inside a screen in a navigator?");
Expand Down
11 changes: 10 additions & 1 deletion src/libs/Navigation/linkTo/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,13 @@ type ActionPayload = {
params?: ActionPayloadParams;
};

export type {ActionPayload, ActionPayloadParams};
type LinkToOptions = Partial<{
// To explicitly set the action type to replace.
forceReplace: boolean;

// If true, the report route will be converted to the opposite form (fullscreen to rhp or rhp to fullscreen) if necessary.
// Check shouldConvertReportPath to see when it will be converted.
reportPathConversionEnabled: boolean;
}>;

export type {ActionPayload, ActionPayloadParams, LinkToOptions};
2 changes: 1 addition & 1 deletion src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ import isPublicScreenRoute from '@libs/isPublicScreenRoute';
import * as Localize from '@libs/Localize';
import Log from '@libs/Log';
import {registerPaginationConfig} from '@libs/Middleware/Pagination';
import type {LinkToOptions} from '@libs/Navigation/linkTo';
import type {LinkToOptions} from '@libs/Navigation/linkTo/types';
import Navigation, {navigationRef} from '@libs/Navigation/Navigation';
import {isOnboardingFlowName} from '@libs/NavigationUtils';
import enhanceParameters from '@libs/Network/enhanceParameters';
Expand Down

0 comments on commit 8549d41

Please sign in to comment.