Skip to content

Commit 6e2edad

Browse files
authored
Merge pull request #58191 from software-mansion-labs/nav/adjust-dismiss-modal
Remove reportID param from dismissModal function
2 parents 75cb085 + 6a631c8 commit 6e2edad

21 files changed

+194
-80
lines changed

contributingGuides/NAVIGATION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ When creating RHP flows, you have to remember a couple of things:
3434

3535
- We use a custom `goBack` function to handle the browser and the `react-navigation` history stack. Under the hood, it resolves to either replacing the current screen with the one we navigate to (deeplinking scenario) or just going back if we reached the current page by navigating in App (pops the screen). It ensures the requested behaviors on web, which is navigating back to the place from where you deeplinked when going into the RHP flow by it.
3636

37-
- If you want to navigate to a certain report after completing a flow related to it, e.g. `RequestMoney` flow with a certain group/user, you should use `Navigation.dismissModal` with this `reportID` as an argument. If, in the future, we would like to navigate to something different than the report after such flows, the API should be rather easy to change. We do it like that in order to replace the RHP flow with the new report instead of pushing it, so pressing the back button does not navigate back to the ending page of the flow. If we were to navigate to the same report, we just pop the RHP modal.
37+
- If you want to navigate to a certain report after completing a flow related to it, e.g. `RequestMoney` flow with a certain group/user, you should use `Navigation.dismissModalWithReport` with this `reportID` as an argument. If, in the future, we would like to navigate to something different than the report after such flows, the API should be rather easy to change. We do it like that in order to replace the RHP flow with the new report instead of pushing it, so pressing the back button does not navigate back to the ending page of the flow. If we were to navigate to the same report, we just pop the RHP modal.
3838

3939
### Example of usage
4040

src/libs/Navigation/AppNavigator/createRootStackNavigator/GetStateForActionHandlers.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {RootNavigatorParamList, State} from '@libs/Navigation/types';
77
import * as SearchQueryUtils from '@libs/SearchQueryUtils';
88
import NAVIGATORS from '@src/NAVIGATORS';
99
import SCREENS from '@src/SCREENS';
10-
import type {OpenWorkspaceSplitActionType, PushActionType, SwitchPolicyIdActionType} from './types';
10+
import type {OpenWorkspaceSplitActionType, PushActionType, ReplaceActionType, SwitchPolicyIdActionType} from './types';
1111

1212
const MODAL_ROUTES_TO_DISMISS: string[] = [
1313
NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR,
@@ -237,6 +237,29 @@ function handlePushSearchPageAction(
237237
return stackRouter.getStateForAction(state, updatedAction, configOptions);
238238
}
239239

240+
function handleReplaceReportsSplitNavigatorAction(
241+
state: StackNavigationState<ParamListBase>,
242+
action: ReplaceActionType,
243+
configOptions: RouterConfigOptions,
244+
stackRouter: Router<StackNavigationState<ParamListBase>, CommonActions.Action | StackActionType>,
245+
) {
246+
const stateWithReportsSplitNavigator = stackRouter.getStateForAction(state, action, configOptions);
247+
248+
if (!stateWithReportsSplitNavigator) {
249+
Log.hmmm('[handleReplaceReportsSplitNavigatorAction] ReportsSplitNavigator has not been found in the navigation state.');
250+
return null;
251+
}
252+
253+
const lastReportsSplitNavigator = stateWithReportsSplitNavigator.routes.at(-1);
254+
255+
// ReportScreen should always be opened with an animation when replacing the navigator
256+
if (lastReportsSplitNavigator?.key) {
257+
reportsSplitsWithEnteringAnimation.add(lastReportsSplitNavigator.key);
258+
}
259+
260+
return stateWithReportsSplitNavigator;
261+
}
262+
240263
/**
241264
* Handles the DISMISS_MODAL action.
242265
* If the last route is a modal route, it has to be popped from the navigation stack.
@@ -275,6 +298,7 @@ export {
275298
handleDismissModalAction,
276299
handlePushReportSplitAction,
277300
handlePushSearchPageAction,
301+
handleReplaceReportsSplitNavigatorAction,
278302
handleSwitchPolicyIDAction,
279303
handleSwitchPolicyIDFromSearchAction,
280304
handleNavigatingToModalFromModal,

src/libs/Navigation/AppNavigator/createRootStackNavigator/RootStackRouter.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,19 @@ import {
1414
handleOpenWorkspaceSplitAction,
1515
handlePushReportSplitAction,
1616
handlePushSearchPageAction,
17+
handleReplaceReportsSplitNavigatorAction,
1718
handleSwitchPolicyIDAction,
1819
} from './GetStateForActionHandlers';
1920
import syncBrowserHistory from './syncBrowserHistory';
20-
import type {DismissModalActionType, OpenWorkspaceSplitActionType, PushActionType, RootStackNavigatorAction, RootStackNavigatorRouterOptions, SwitchPolicyIdActionType} from './types';
21+
import type {
22+
DismissModalActionType,
23+
OpenWorkspaceSplitActionType,
24+
PushActionType,
25+
ReplaceActionType,
26+
RootStackNavigatorAction,
27+
RootStackNavigatorRouterOptions,
28+
SwitchPolicyIdActionType,
29+
} from './types';
2130

2231
function isOpenWorkspaceSplitAction(action: RootStackNavigatorAction): action is OpenWorkspaceSplitActionType {
2332
return action.type === CONST.NAVIGATION.ACTION_TYPE.OPEN_WORKSPACE_SPLIT;
@@ -31,6 +40,10 @@ function isPushAction(action: RootStackNavigatorAction): action is PushActionTyp
3140
return action.type === CONST.NAVIGATION.ACTION_TYPE.PUSH;
3241
}
3342

43+
function isReplaceAction(action: RootStackNavigatorAction): action is ReplaceActionType {
44+
return action.type === CONST.NAVIGATION.ACTION_TYPE.REPLACE;
45+
}
46+
3447
function isDismissModalAction(action: RootStackNavigatorAction): action is DismissModalActionType {
3548
return action.type === CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL;
3649
}
@@ -82,6 +95,10 @@ function RootStackRouter(options: RootStackNavigatorRouterOptions) {
8295
return handleDismissModalAction(state, configOptions, stackRouter);
8396
}
8497

98+
if (isReplaceAction(action) && action.payload.name === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR) {
99+
return handleReplaceReportsSplitNavigatorAction(state, action, configOptions, stackRouter);
100+
}
101+
85102
if (isPushAction(action)) {
86103
if (action.payload.name === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR) {
87104
return handlePushReportSplitAction(state, action, configOptions, stackRouter, setActiveWorkspaceID);

src/libs/Navigation/AppNavigator/createRootStackNavigator/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ type SwitchPolicyIdActionType = RootStackNavigatorActionType & {
2929

3030
type PushActionType = StackActionType & {type: typeof CONST.NAVIGATION.ACTION_TYPE.PUSH};
3131

32+
type ReplaceActionType = StackActionType & {type: typeof CONST.NAVIGATION.ACTION_TYPE.REPLACE};
33+
3234
type DismissModalActionType = RootStackNavigatorActionType & {
3335
type: typeof CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL;
3436
};
@@ -49,6 +51,7 @@ export type {
4951
OpenWorkspaceSplitActionType,
5052
SwitchPolicyIdActionType,
5153
PushActionType,
54+
ReplaceActionType,
5255
DismissModalActionType,
5356
RootStackNavigatorAction,
5457
RootStackNavigatorActionType,

src/libs/Navigation/Navigation.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {CommonActions, getPathFromState, StackActions} from '@react-navigation/n
55
import omit from 'lodash/omit';
66
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
77
import Onyx from 'react-native-onyx';
8-
import type {Writable} from 'type-fest';
8+
import type {MergeExclusive, Writable} from 'type-fest';
99
import getIsNarrowLayout from '@libs/getIsNarrowLayout';
1010
import Log from '@libs/Log';
1111
import {shallowCompare} from '@libs/ObjectUtils';
@@ -24,6 +24,7 @@ import originalCloseRHPFlow from './helpers/closeRHPFlow';
2424
import getPolicyIDFromState from './helpers/getPolicyIDFromState';
2525
import getStateFromPath from './helpers/getStateFromPath';
2626
import getTopmostReportParams from './helpers/getTopmostReportParams';
27+
import {isFullScreenName} from './helpers/isNavigatorName';
2728
import isReportOpenInRHP from './helpers/isReportOpenInRHP';
2829
import isSideModalNavigator from './helpers/isSideModalNavigator';
2930
import linkTo from './helpers/linkTo';
@@ -469,20 +470,25 @@ function waitForProtectedRoutes() {
469470
});
470471
}
471472

472-
type NavigateToReportWithPolicyCheckPayload = {report?: OnyxEntry<Report>; reportID?: string; reportActionID?: string; referrer?: string; policyIDToCheck?: string};
473+
// It should not be possible to pass a report and a reportID at the same time.
474+
type NavigateToReportWithPolicyCheckPayload = MergeExclusive<{report: OnyxEntry<Report>}, {reportID: string}> & {
475+
reportActionID?: string;
476+
referrer?: string;
477+
policyIDToCheck?: string;
478+
};
473479

474480
/**
475481
* 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.
476482
* If not, the current workspace is set to global.
477483
*/
478-
function navigateToReportWithPolicyCheck({report, reportID, reportActionID, referrer, policyIDToCheck}: NavigateToReportWithPolicyCheckPayload, ref = navigationRef) {
484+
function navigateToReportWithPolicyCheck({report, reportID, reportActionID, referrer, policyIDToCheck}: NavigateToReportWithPolicyCheckPayload, forceReplace = false, ref = navigationRef) {
479485
const targetReport = reportID ? {reportID, ...allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]} : report;
480486
const policyID = policyIDToCheck ?? getPolicyIDFromState(navigationRef.getRootState() as State<RootNavigatorParamList>);
481487
const policyMemberAccountIDs = getPolicyEmployeeAccountIDs(policyID);
482488
const shouldOpenAllWorkspace = isEmptyObject(targetReport) ? true : !doesReportBelongToWorkspace(targetReport, policyMemberAccountIDs, policyID);
483489

484490
if ((shouldOpenAllWorkspace && !policyID) || !shouldOpenAllWorkspace) {
485-
linkTo(ref.current, ROUTES.REPORT_WITH_ID.getRoute(targetReport?.reportID, reportActionID, referrer));
491+
linkTo(ref.current, ROUTES.REPORT_WITH_ID.getRoute(targetReport?.reportID, reportActionID, referrer), {forceReplace: !!forceReplace});
486492
return;
487493
}
488494

@@ -498,6 +504,17 @@ function navigateToReportWithPolicyCheck({report, reportID, reportActionID, refe
498504
params.referrer = referrer;
499505
}
500506

507+
if (forceReplace) {
508+
ref.dispatch(
509+
StackActions.replace(NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, {
510+
policyID: undefined,
511+
screen: SCREENS.REPORT,
512+
params,
513+
}),
514+
);
515+
return;
516+
}
517+
501518
ref.dispatch(
502519
StackActions.push(NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, {
503520
policyID: undefined,
@@ -528,23 +545,31 @@ function getReportRouteByID(reportID?: string, routes: NavigationRoute[] = navig
528545
/**
529546
* Closes the modal navigator (RHP, LHP, onboarding).
530547
*/
531-
const dismissModal = (reportID?: string, ref = navigationRef) => {
548+
const dismissModal = (ref = navigationRef) => {
532549
isNavigationReady().then(() => {
533550
ref.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL});
534-
if (!reportID) {
535-
return;
536-
}
537-
navigateToReportWithPolicyCheck({reportID});
538551
});
539552
};
540553

541554
/**
542555
* Dismisses the modal and opens the given report.
543556
*/
544-
const dismissModalWithReport = (report: OnyxEntry<Report>, ref = navigationRef) => {
557+
const dismissModalWithReport = (navigateToReportPayload: NavigateToReportWithPolicyCheckPayload, ref = navigationRef) => {
545558
isNavigationReady().then(() => {
546-
ref.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL});
547-
navigateToReportWithPolicyCheck({report});
559+
if (getIsNarrowLayout()) {
560+
const topmostReportID = getTopmostReportId();
561+
const areReportsIDsDefined = !!topmostReportID && !!navigateToReportPayload.reportID;
562+
const isReportsSplitTopmostFullScreen = ref.getRootState().routes.findLast((route) => isFullScreenName(route.name))?.name === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR;
563+
if (topmostReportID === navigateToReportPayload.reportID && areReportsIDsDefined && isReportsSplitTopmostFullScreen) {
564+
dismissModal();
565+
return;
566+
}
567+
const forceReplace = true;
568+
navigateToReportWithPolicyCheck(navigateToReportPayload, forceReplace);
569+
return;
570+
}
571+
dismissModal();
572+
navigateToReportWithPolicyCheck(navigateToReportPayload);
548573
});
549574
};
550575

src/libs/actions/IOU.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,19 @@ Onyx.connect({
679679
callback: (value) => (personalDetailsList = value),
680680
});
681681

682+
/**
683+
* @private
684+
* After finishing the action in RHP from the Inbox tab, besides dismissing the modal, we should open the report.
685+
* It is a helper function used only in this file.
686+
*/
687+
function dismissModalAndOpenReportInInboxTab(reportID?: string) {
688+
if (isSearchTopmostFullScreenRoute() || !reportID) {
689+
Navigation.dismissModal();
690+
return;
691+
}
692+
Navigation.dismissModalWithReport({reportID});
693+
}
694+
682695
/**
683696
* Find the report preview action from given chat report and iou report
684697
*/
@@ -4758,7 +4771,7 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation) {
47584771
}
47594772

47604773
InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID));
4761-
Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : activeReportID);
4774+
dismissModalAndOpenReportInInboxTab(activeReportID);
47624775

47634776
const trackReport = Navigation.getReportRouteByID(linkedTrackedExpenseReportAction?.childReportID);
47644777
if (trackReport?.key) {
@@ -4842,7 +4855,8 @@ function submitPerDiemExpense(submitPerDiemExpenseInformation: PerDiemExpenseInf
48424855
API.write(WRITE_COMMANDS.CREATE_PER_DIEM_REQUEST, parameters, onyxData);
48434856

48444857
InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID));
4845-
Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : activeReportID);
4858+
dismissModalAndOpenReportInInboxTab(activeReportID);
4859+
48464860
if (activeReportID) {
48474861
notifyNewAction(activeReportID, payeeAccountID);
48484862
}
@@ -4909,7 +4923,7 @@ function sendInvoice(
49094923
if (isSearchTopmostFullScreenRoute()) {
49104924
Navigation.dismissModal();
49114925
} else {
4912-
Navigation.dismissModalWithReport(invoiceRoom);
4926+
Navigation.dismissModalWithReport({report: invoiceRoom});
49134927
}
49144928

49154929
notifyNewAction(invoiceRoom.reportID, receiver.accountID);
@@ -5135,7 +5149,7 @@ function trackExpense(params: CreateTrackExpenseParams) {
51355149
}
51365150
}
51375151
InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID));
5138-
Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : activeReportID);
5152+
dismissModalAndOpenReportInInboxTab(activeReportID);
51395153

51405154
if (action === CONST.IOU.ACTION.SHARE) {
51415155
if (isSearchTopmostFullScreenRoute() && activeReportID) {
@@ -5718,7 +5732,8 @@ function splitBill({
57185732
API.write(WRITE_COMMANDS.SPLIT_BILL, parameters, onyxData);
57195733
InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID));
57205734

5721-
Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : existingSplitChatReportID);
5735+
dismissModalAndOpenReportInInboxTab(existingSplitChatReportID);
5736+
57225737
notifyNewAction(splitData.chatReportID, currentUserAccountID);
57235738
}
57245739

@@ -5791,7 +5806,7 @@ function splitBillAndOpenReport({
57915806
API.write(WRITE_COMMANDS.SPLIT_BILL_AND_OPEN_REPORT, parameters, onyxData);
57925807
InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID));
57935808

5794-
Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : splitData.chatReportID);
5809+
dismissModalAndOpenReportInInboxTab(splitData.chatReportID);
57955810
notifyNewAction(splitData.chatReportID, currentUserAccountID);
57965811
}
57975812

@@ -6110,7 +6125,7 @@ function startSplitBill({
61106125

61116126
API.write(WRITE_COMMANDS.START_SPLIT_BILL, parameters, {optimisticData, successData, failureData});
61126127

6113-
Navigation.dismissModalWithReport(splitChatReport);
6128+
Navigation.dismissModalWithReport({report: splitChatReport});
61146129
notifyNewAction(splitChatReport.reportID, currentUserAccountID);
61156130
}
61166131

@@ -6372,7 +6387,7 @@ function completeSplitBill(
63726387

63736388
API.write(WRITE_COMMANDS.COMPLETE_SPLIT_BILL, parameters, {optimisticData, successData, failureData});
63746389
InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID));
6375-
Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : chatReportID);
6390+
dismissModalAndOpenReportInInboxTab(chatReportID);
63766391
notifyNewAction(chatReportID, sessionAccountID);
63776392
}
63786393

@@ -6555,7 +6570,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest
65556570
API.write(WRITE_COMMANDS.CREATE_DISTANCE_REQUEST, parameters, onyxData);
65566571
InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID));
65576572
const activeReportID = isMoneyRequestReport && report?.reportID ? report.reportID : parameters.chatReportID;
6558-
Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : activeReportID);
6573+
dismissModalAndOpenReportInInboxTab(activeReportID);
65596574
notifyNewAction(activeReportID, userAccountID);
65606575
}
65616576

@@ -8256,7 +8271,7 @@ function sendMoneyElsewhere(report: OnyxEntry<OnyxTypes.Report>, amount: number,
82568271

82578272
API.write(WRITE_COMMANDS.SEND_MONEY_ELSEWHERE, params, {optimisticData, successData, failureData});
82588273

8259-
Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : params.chatReportID);
8274+
dismissModalAndOpenReportInInboxTab(params.chatReportID);
82608275
notifyNewAction(params.chatReportID, managerID);
82618276
}
82628277

@@ -8269,7 +8284,7 @@ function sendMoneyWithWallet(report: OnyxEntry<OnyxTypes.Report>, amount: number
82698284

82708285
API.write(WRITE_COMMANDS.SEND_MONEY_WITH_WALLET, params, {optimisticData, successData, failureData});
82718286

8272-
Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : params.chatReportID);
8287+
dismissModalAndOpenReportInInboxTab(params.chatReportID);
82738288
notifyNewAction(params.chatReportID, managerID);
82748289
}
82758290

src/libs/actions/Report.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import type EnvironmentType from '@libs/Environment/getEnvironment/types';
6666
import * as ErrorUtils from '@libs/ErrorUtils';
6767
import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils';
6868
import fileDownload from '@libs/fileDownload';
69+
import getIsNarrowLayout from '@libs/getIsNarrowLayout';
6970
import HttpUtils from '@libs/HttpUtils';
7071
import isPublicScreenRoute from '@libs/isPublicScreenRoute';
7172
import * as Localize from '@libs/Localize';
@@ -1200,6 +1201,11 @@ function navigateToAndOpenReport(
12001201
// We want to pass newChat here because if anything is passed in that param (even an existing chat), we will try to create a chat on the server
12011202
openReport(report?.reportID, '', userLogins, newChat, undefined, undefined, undefined, avatarFile);
12021203
if (shouldDismissModal) {
1204+
if (getIsNarrowLayout()) {
1205+
Navigation.dismissModalWithReport({report});
1206+
return;
1207+
}
1208+
12031209
Navigation.dismissModal();
12041210
}
12051211

@@ -2400,7 +2406,7 @@ function navigateToConciergeChat(shouldDismissModal = false, checkIfCurrentPageA
24002406
navigateToAndOpenReport([CONST.EMAIL.CONCIERGE], shouldDismissModal);
24012407
});
24022408
} else if (shouldDismissModal) {
2403-
Navigation.dismissModal(conciergeChatReportID);
2409+
Navigation.dismissModalWithReport({reportID: conciergeChatReportID});
24042410
} else {
24052411
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(conciergeChatReportID), linkToOptions);
24062412
}
@@ -2656,7 +2662,7 @@ function addPolicyReport(policyReport: OptimisticChatReport) {
26562662
};
26572663

26582664
API.write(WRITE_COMMANDS.ADD_WORKSPACE_ROOM, parameters, {optimisticData, successData, failureData});
2659-
Navigation.dismissModalWithReport(policyReport);
2665+
Navigation.dismissModalWithReport({report: policyReport});
26602666
}
26612667

26622668
/** Deletes a report, along with its reportActions, any linked reports, and any linked IOU report. */

src/libs/actions/Task.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ function createTaskAndNavigate(
340340
InteractionManager.runAfterInteractions(() => {
341341
clearOutTaskInfo();
342342
});
343-
Navigation.dismissModal(parentReportID);
343+
Navigation.dismissModalWithReport({reportID: parentReportID});
344344
}
345345
notifyNewAction(parentReportID, currentUserAccountID);
346346
}

0 commit comments

Comments
 (0)