diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 4988c33ed8c..ac3bb386916 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -256,7 +256,7 @@ function AttachmentModal(props) { const deleteAndCloseModal = useCallback(() => { IOU.detachReceipt(props.transaction.transactionID, props.report.reportID); setIsDeleteReceiptConfirmModalVisible(false); - Navigation.dismissModal(props.report.reportID); + Navigation.dismissModalWithReportID(props.report.reportID); }, [props.transaction, props.report]); /** diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 4dd46aeb34c..7f5cd18238d 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -2,12 +2,16 @@ import {findFocusedRoute} from '@react-navigation/core'; import type {EventArg, NavigationContainerEventMap} from '@react-navigation/native'; import {CommonActions, getPathFromState, StackActions} from '@react-navigation/native'; import Log from '@libs/Log'; +import {getReport} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import {PROTECTED_SCREENS} from '@src/SCREENS'; +import type {Report} from '@src/types/onyx'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; import originalDismissModal from './dismissModal'; +import originalDismissModalWithReport from './dismissModalWithReport'; import originalGetTopmostReportActionId from './getTopmostReportActionID'; import originalGetTopmostReportId from './getTopmostReportId'; import linkingConfig from './linkingConfig'; @@ -47,8 +51,17 @@ const getTopmostReportId = (state = navigationRef.getState()) => originalGetTopm const getTopmostReportActionId = (state = navigationRef.getState()) => originalGetTopmostReportActionId(state); // Re-exporting the dismissModal here to fill in default value for navigationRef. The dismissModal isn't defined in this file to avoid cyclic dependencies. -const dismissModal = (targetReportId = '', ref = navigationRef) => originalDismissModal(targetReportId, ref); +const dismissModal = (ref = navigationRef) => originalDismissModal(ref); +// Re-exporting the dismissModalWithReport here to fill in default value for navigationRef. The dismissModalWithReport isn't defined in this file to avoid cyclic dependencies. +// This method is needed because it allows to dismiss the modal and then open the report. Within this method is checked whether the report belongs to a specific workspace. Sometimes the report we want to check, hasn't been added to the Onyx yet. +// Then we can pass the report as a param without getting it from the Onyx. +const dismissModalWithReport = (report: Report | EmptyObject, ref = navigationRef) => originalDismissModalWithReport(report, ref); + +const dismissModalWithReportID = (reportID: string, ref = navigationRef) => { + const report = getReport(reportID); + originalDismissModalWithReport({reportID, ...report}, ref); +}; /** Method for finding on which index in stack we are. */ function getActiveRouteIndex(stateOrRoute: StateOrRoute, index?: number): number | undefined { if ('routes' in stateOrRoute && stateOrRoute.routes) { @@ -323,6 +336,8 @@ export default { navigate, setParams, dismissModal, + dismissModalWithReport, + dismissModalWithReportID, isActiveRoute, getActiveRoute, getActiveRouteWithoutParams, diff --git a/src/libs/Navigation/dismissModal.ts b/src/libs/Navigation/dismissModal.ts index d1dea558181..c80b2545e86 100644 --- a/src/libs/Navigation/dismissModal.ts +++ b/src/libs/Navigation/dismissModal.ts @@ -1,20 +1,9 @@ -import {getActionFromState} from '@react-navigation/core'; import type {NavigationContainerRef} from '@react-navigation/native'; import {StackActions} from '@react-navigation/native'; -import {findLastIndex} from 'lodash'; import Log from '@libs/Log'; -import getPolicyMemberAccountIDs from '@libs/PolicyMembersUtils'; -import {doesReportBelongToWorkspace, getReport} from '@libs/ReportUtils'; import NAVIGATORS from '@src/NAVIGATORS'; -import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import getPolicyIdFromState from './getPolicyIdFromState'; -import getStateFromPath from './getStateFromPath'; -import getTopmostReportId from './getTopmostReportId'; -import linkingConfig from './linkingConfig'; -import switchPolicyID from './switchPolicyID'; -import type {RootStackParamList, StackNavigationAction, State} from './types'; +import type {RootStackParamList} from './types'; // This function is in a separate file than Navigation.js to avoid cyclic dependency. @@ -23,7 +12,7 @@ import type {RootStackParamList, StackNavigationAction, State} from './types'; * * @param targetReportID - The reportID to navigate to after dismissing the modal */ -function dismissModal(targetReportID: string, navigationRef: NavigationContainerRef) { +function dismissModal(navigationRef: NavigationContainerRef) { if (!navigationRef.isReady()) { return; } @@ -36,33 +25,7 @@ function dismissModal(targetReportID: string, navigationRef: NavigationContainer case NAVIGATORS.RIGHT_MODAL_NAVIGATOR: case SCREENS.NOT_FOUND: case SCREENS.REPORT_ATTACHMENTS: - // if we are not in the target report, we need to navigate to it after dismissing the modal - if (targetReportID && targetReportID !== getTopmostReportId(state)) { - const reportState = getStateFromPath(ROUTES.REPORT_WITH_ID.getRoute(targetReportID)); - const policyID = getPolicyIdFromState(state as State); - const policyMemberAccountIDs = getPolicyMemberAccountIDs(policyID); - const targetReport = getReport(targetReportID); - // If targetReport is an empty object, it means that it's a new report, so it can't belong to any workspace - const shouldOpenAllWorkspace = isEmptyObject(targetReport) ? true : !doesReportBelongToWorkspace(targetReport, policyMemberAccountIDs, policyID); - if (shouldOpenAllWorkspace) { - switchPolicyID(navigationRef, {route: ROUTES.HOME}); - } else { - switchPolicyID(navigationRef, {policyID, route: ROUTES.HOME}); - } - - const action: StackNavigationAction = getActionFromState(reportState, linkingConfig.config); - if (action) { - action.type = 'REPLACE'; - navigationRef.dispatch(action); - } - // If not-found page is in the route stack, we need to close it - } else if (targetReportID && state.routes.some((route) => route.name === SCREENS.NOT_FOUND)) { - const lastRouteIndex = state.routes.length - 1; - const centralRouteIndex = findLastIndex(state.routes, (route) => route.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR); - navigationRef.dispatch({...StackActions.pop(lastRouteIndex - centralRouteIndex), target: state.key}); - } else { - navigationRef.dispatch({...StackActions.pop(), target: state.key}); - } + navigationRef.dispatch({...StackActions.pop(), target: state.key}); break; default: { Log.hmmm('[Navigation] dismissModal failed because there is no modal stack to dismiss'); diff --git a/src/libs/Navigation/dismissModalWithReport.ts b/src/libs/Navigation/dismissModalWithReport.ts new file mode 100644 index 00000000000..00217452c5f --- /dev/null +++ b/src/libs/Navigation/dismissModalWithReport.ts @@ -0,0 +1,74 @@ +import {getActionFromState} from '@react-navigation/core'; +import type {NavigationContainerRef} from '@react-navigation/native'; +import {StackActions} from '@react-navigation/native'; +import {findLastIndex} from 'lodash'; +import Log from '@libs/Log'; +import getPolicyMemberAccountIDs from '@libs/PolicyMembersUtils'; +import {doesReportBelongToWorkspace} from '@libs/ReportUtils'; +import NAVIGATORS from '@src/NAVIGATORS'; +import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; +import type {Report} from '@src/types/onyx'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import getPolicyIdFromState from './getPolicyIdFromState'; +import getStateFromPath from './getStateFromPath'; +import getTopmostReportId from './getTopmostReportId'; +import linkingConfig from './linkingConfig'; +import switchPolicyID from './switchPolicyID'; +import type {RootStackParamList, StackNavigationAction, State} from './types'; + +// This function is in a separate file than Navigation.js to avoid cyclic dependency. + +/** + * Dismisses the last modal stack if there is any + * + * @param targetReportID - The reportID to navigate to after dismissing the modal + */ +function dismissModalWithReport(targetReport: Report | EmptyObject, navigationRef: NavigationContainerRef) { + if (!navigationRef.isReady()) { + return; + } + + const state = navigationRef.getState(); + const lastRoute = state.routes.at(-1); + switch (lastRoute?.name) { + case NAVIGATORS.FULL_SCREEN_NAVIGATOR: + case NAVIGATORS.LEFT_MODAL_NAVIGATOR: + case NAVIGATORS.RIGHT_MODAL_NAVIGATOR: + case SCREENS.NOT_FOUND: + case SCREENS.REPORT_ATTACHMENTS: + // if we are not in the target report, we need to navigate to it after dismissing the modal + if (targetReport.reportID !== getTopmostReportId(state)) { + const reportState = getStateFromPath(ROUTES.REPORT_WITH_ID.getRoute(targetReport.reportID)); + const policyID = getPolicyIdFromState(state as State); + const policyMemberAccountIDs = getPolicyMemberAccountIDs(policyID); + const shouldOpenAllWorkspace = isEmptyObject(targetReport) ? true : !doesReportBelongToWorkspace(targetReport, policyMemberAccountIDs, policyID); + + if (shouldOpenAllWorkspace) { + switchPolicyID(navigationRef, {route: ROUTES.HOME}); + } else { + switchPolicyID(navigationRef, {policyID, route: ROUTES.HOME}); + } + + const action: StackNavigationAction = getActionFromState(reportState, linkingConfig.config); + if (action) { + action.type = 'REPLACE'; + navigationRef.dispatch(action); + } + // If not-found page is in the route stack, we need to close it + } else if (state.routes.some((route) => route.name === SCREENS.NOT_FOUND)) { + const lastRouteIndex = state.routes.length - 1; + const centralRouteIndex = findLastIndex(state.routes, (route) => route.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR); + navigationRef.dispatch({...StackActions.pop(lastRouteIndex - centralRouteIndex), target: state.key}); + } else { + navigationRef.dispatch({...StackActions.pop(), target: state.key}); + } + break; + default: { + Log.hmmm('[Navigation] dismissModalWithReport failed because there is no modal stack to dismiss'); + } + } +} + +export default dismissModalWithReport; diff --git a/src/libs/Navigation/linkTo.ts b/src/libs/Navigation/linkTo.ts index 5c3758e0595..c9f33865323 100644 --- a/src/libs/Navigation/linkTo.ts +++ b/src/libs/Navigation/linkTo.ts @@ -185,7 +185,7 @@ export default function linkTo(navigation: NavigationContainerRef _.pick(waypoint, 'address')); const addresses = _.mapObject(waypoints, (waypoint) => _.pick(waypoint, 'address')); if (_.isEqual(oldAddresses, addresses)) { - Navigation.dismissModal(report.reportID); + Navigation.dismissModalWithReportID(report.reportID); return; } @@ -84,7 +84,7 @@ function EditRequestDistancePage({report, route, transaction, transactionBackup} // If the client is offline, then the modal can be closed as well (because there are no errors or other feedback to show them // until they come online again and sync with the server). if (isOffline) { - Navigation.dismissModal(report.reportID); + Navigation.dismissModalWithReportID(report.reportID); } }; diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index d0d2a23bb56..76931d73e18 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -155,7 +155,7 @@ function SearchPage({betas, personalDetails, reports, isSearchingForReports, nav return; } if (option.reportID) { - Navigation.dismissModal(option.reportID); + Navigation.dismissModalWithReportID(option.reportID); } else { Report.navigateToAndOpenReport([option.login]); } diff --git a/src/pages/SearchPage/index.js b/src/pages/SearchPage/index.js index 211f3622e06..3fd28a39f64 100644 --- a/src/pages/SearchPage/index.js +++ b/src/pages/SearchPage/index.js @@ -123,7 +123,7 @@ function SearchPage({betas, reports}) { if (option.reportID) { setSearchValue(''); - Navigation.dismissModal(option.reportID); + Navigation.dismissModalWithReportID(option.reportID); } else { Report.navigateToAndOpenReport([option.login]); } diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.js b/src/pages/tasks/TaskAssigneeSelectorModal.js index 1a526a9cdd9..6ab37b1196b 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.js +++ b/src/pages/tasks/TaskAssigneeSelectorModal.js @@ -139,7 +139,7 @@ function TaskAssigneeSelectorModal(props) { if (report && !ReportUtils.isTaskReport(report)) { Navigation.isNavigationReady().then(() => { - Navigation.dismissModal(report.reportID); + Navigation.dismissModalWithReportID(report.reportID); }); } @@ -198,7 +198,7 @@ function TaskAssigneeSelectorModal(props) { // Pass through the selected assignee Task.editTaskAssignee(report, props.session.accountID, option.login, option.accountID, assigneeChatReport); } - Navigation.dismissModal(report.reportID); + Navigation.dismissModalWithReport(report); // If there's no report, we're creating a new task } else if (option.accountID) { Task.setAssigneeValue(option.login, option.accountID, props.task.shareDestination, OptionsListUtils.isCurrentUser(option)); diff --git a/src/pages/tasks/TaskDescriptionPage.js b/src/pages/tasks/TaskDescriptionPage.js index 3a6999d4408..bc2ade3aad0 100644 --- a/src/pages/tasks/TaskDescriptionPage.js +++ b/src/pages/tasks/TaskDescriptionPage.js @@ -60,14 +60,14 @@ function TaskDescriptionPage(props) { Task.editTask(props.report, {description: values.description}); } - Navigation.dismissModal(props.report.reportID); + Navigation.dismissModalWithReportID(props.report.reportID); }, [props], ); if (!ReportUtils.isTaskReport(props.report)) { Navigation.isNavigationReady().then(() => { - Navigation.dismissModal(props.report.reportID); + Navigation.dismissModalWithReportID(props.report.reportID); }); } const inputRef = useRef(null); diff --git a/src/pages/tasks/TaskTitlePage.js b/src/pages/tasks/TaskTitlePage.js index 9b393a8a237..351540b64e3 100644 --- a/src/pages/tasks/TaskTitlePage.js +++ b/src/pages/tasks/TaskTitlePage.js @@ -66,14 +66,14 @@ function TaskTitlePage(props) { Task.editTask(props.report, {title: values.title}); } - Navigation.dismissModal(props.report.reportID); + Navigation.dismissModalWithReportID(props.report.reportID); }, [props], ); if (!ReportUtils.isTaskReport(props.report)) { Navigation.isNavigationReady().then(() => { - Navigation.dismissModal(props.report.reportID); + Navigation.dismissModalWithReportID(props.report.reportID); }); } diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index 35fab36e5d4..a6d18199a78 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -153,7 +153,7 @@ function WorkspaceNewRoomPage(props) { if (!(((wasLoading && !props.formState.isLoading) || (isOffline && props.formState.isLoading)) && _.isEmpty(props.formState.errorFields))) { return; } - Navigation.dismissModal(newRoomReportID); + Navigation.dismissModalWithReportID(newRoomReportID); // eslint-disable-next-line react-hooks/exhaustive-deps -- we just want this to update on changing the form State }, [props.formState]);