diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index f34f1562a2fc..2528add01bbb 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1,3 +1,4 @@ +import type {ParamListBase, StackNavigationState} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; import {format} from 'date-fns'; import fastMerge from 'expensify-common/lib/fastMerge'; @@ -43,7 +44,7 @@ import type {OptimisticChatReport, OptimisticCreatedReportAction, OptimisticIOUR import * as TransactionUtils from '@libs/TransactionUtils'; import * as UserUtils from '@libs/UserUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; -import type {MoneyRequestNavigatorParamList} from '@navigation/types'; +import type {MoneyRequestNavigatorParamList, NavigationPartialRoute} from '@navigation/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -260,6 +261,28 @@ function clearMoneyRequest(transactionID: string) { Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, null); } +/** + * Update money request-related pages IOU type params + */ +function updateMoneyRequestTypeParams(routes: StackNavigationState['routes'] | NavigationPartialRoute[], newIouType: string, tab: string) { + routes.forEach((route) => { + const tabList = [CONST.TAB_REQUEST.DISTANCE, CONST.TAB_REQUEST.MANUAL, CONST.TAB_REQUEST.SCAN] as string[]; + if (!route.name.startsWith('Money_Request_') && !tabList.includes(route.name)) { + return; + } + const newParams: Record = {iouType: newIouType}; + if (route.name === 'Money_Request_Create') { + // Both screen and nested params are needed to properly update the nested tab navigator + newParams.params = {...newParams}; + newParams.screen = tab; + } + Navigation.setParams(newParams, route.key ?? ''); + + // Recursively update nested money request tab params + updateMoneyRequestTypeParams(route.state?.routes ?? [], newIouType, tab); + }); +} + // eslint-disable-next-line @typescript-eslint/naming-convention function startMoneyRequest_temporaryForRefactor(iouType: ValueOf, reportID: string) { clearMoneyRequest(CONST.IOU.OPTIMISTIC_TRANSACTION_ID); @@ -4234,6 +4257,8 @@ export { initMoneyRequest, startMoneyRequest_temporaryForRefactor, resetMoneyRequestInfo, + clearMoneyRequest, + updateMoneyRequestTypeParams, setMoneyRequestAmount_temporaryForRefactor, setMoneyRequestBillable_temporaryForRefactor, setMoneyRequestCreated, diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js index 92a1ded0e9d6..8e50577ede1f 100644 --- a/src/pages/iou/request/IOURequestStartPage.js +++ b/src/pages/iou/request/IOURequestStartPage.js @@ -1,4 +1,4 @@ -import {useFocusEffect} from '@react-navigation/native'; +import {useFocusEffect, useNavigation} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useRef, useState} from 'react'; @@ -71,6 +71,7 @@ function IOURequestStartPage({ }) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const navigation = useNavigation(); const [isDraggingOver, setIsDraggingOver] = useState(false); const tabTitles = { [CONST.IOU.TYPE.REQUEST]: translate('iou.requestMoney'), @@ -120,10 +121,13 @@ function IOURequestStartPage({ if (newIouType === previousIOURequestType) { return; } + if (iouType === CONST.IOU.TYPE.SPLIT && transaction.isFromGlobalCreate) { + IOU.updateMoneyRequestTypeParams(navigation.getState().routes, CONST.IOU.TYPE.REQUEST, newIouType); + } IOU.initMoneyRequest(reportID, isFromGlobalCreate, newIouType); transactionRequestType.current = newIouType; }, - [previousIOURequestType, reportID, isFromGlobalCreate], + [previousIOURequestType, reportID, isFromGlobalCreate, iouType, navigation, transaction.isFromGlobalCreate], ); if (!transaction.transactionID) { diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 238b66c0e727..2865316b7fd5 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -234,7 +234,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ ]; } - onParticipantsAdded(newSelectedOptions); + onParticipantsAdded(newSelectedOptions, newSelectedOptions.length !== 0 ? CONST.IOU.TYPE.SPLIT : undefined); }, [participants, onParticipantsAdded], ); @@ -263,7 +263,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ return; } - onFinish(); + onFinish(CONST.IOU.TYPE.SPLIT); }, [shouldShowSplitBillErrorMessage, onFinish]); const footerContent = useMemo( diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 1ec793c96244..9904f64d6833 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -155,11 +155,6 @@ function IOURequestStepConfirmation({ // If there is not a report attached to the IOU with a reportID, then the participants were manually selected and the user needs taken // back to the participants step if (!transaction.participantsAutoAssigned) { - // When going back to the participants step, if the iou is a "request" (not a split), then the participants need to be cleared from the - // transaction so that the participant can be selected again. - if (iouType === CONST.IOU.TYPE.REQUEST) { - IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, []); - } Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(iouType, transactionID, reportID)); return; } diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js index 675eb0d03448..b2f5cbb68cd1 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.js +++ b/src/pages/iou/request/step/IOURequestStepParticipants.js @@ -1,5 +1,7 @@ +import {useNavigation} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useRef} from 'react'; +import _ from 'underscore'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; @@ -36,13 +38,16 @@ function IOURequestStepParticipants({ transaction: {participants = []}, }) { const {translate} = useLocalize(); + const navigation = useNavigation(); const selectedReportID = useRef(reportID); const numberOfParticipants = useRef(participants.length); const iouRequestType = TransactionUtils.getRequestType(transaction); - const headerTitle = translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); + const isSplitRequest = iouType === CONST.IOU.TYPE.SPLIT; + const headerTitle = isSplitRequest ? translate('iou.split') : translate(TransactionUtils.getHeaderTitleTranslationKey(transaction)); const receiptFilename = lodashGet(transaction, 'filename'); const receiptPath = lodashGet(transaction, 'receipt.source'); const receiptType = lodashGet(transaction, 'receipt.type'); + const newIouType = useRef(); // When the component mounts, if there is a receipt, see if the image can be read from the disk. If not, redirect the user to the starting step of the flow. // This is because until the request is saved, the receipt file is only stored in the browsers memory as a blob:// and if the browser is refreshed, then @@ -51,8 +56,40 @@ function IOURequestStepParticipants({ IOU.navigateToStartStepIfScanFileCannotBeRead(receiptFilename, receiptPath, () => {}, iouRequestType, iouType, transactionID, reportID, receiptType); }, [receiptType, receiptPath, receiptFilename, iouRequestType, iouType, transactionID, reportID]); + const updateRouteParams = useCallback(() => { + IOU.updateMoneyRequestTypeParams(navigation.getState().routes, newIouType.current); + }, [navigation]); + + useEffect(() => { + if (!newIouType.current) { + return; + } + // Participants can be added as normal or split participants. We want to wait for the participants' data to be updated before + // updating the money request type route params reducing the overhead of the thread and preventing possible jitters in UI. + updateRouteParams(); + newIouType.current = null; + }, [participants, updateRouteParams]); + const addParticipant = useCallback( - (val) => { + (val, selectedIouType) => { + const isSplit = selectedIouType === CONST.IOU.TYPE.SPLIT; + // It's only possible to switch between REQUEST and SPLIT. + // We want to update the IOU type only if it's not updated yet to prevent unnecessary updates. + if (isSplit && iouType !== CONST.IOU.TYPE.SPLIT) { + newIouType.current = CONST.IOU.TYPE.SPLIT; + } else if (!isSplit && iouType === CONST.IOU.TYPE.SPLIT) { + // Non-split can be either REQUEST or SEND. Instead of checking whether + // the current IOU type is not a REQUEST (true for SEND), we check whether the current IOU type is a SPLIT. + newIouType.current = CONST.IOU.TYPE.REQUEST; + } + + // If the Onyx participants has the same items as the selected participants (val), Onyx won't update it + // thus this component won't rerender, so we can immediately update the route params. + if (newIouType.current && _.isEqual(participants, val)) { + updateRouteParams(); + newIouType.current = null; + } + IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, val); numberOfParticipants.current = val.length; @@ -66,15 +103,19 @@ function IOURequestStepParticipants({ // When a participant is selected, the reportID needs to be saved because that's the reportID that will be used in the confirmation step. selectedReportID.current = lodashGet(val, '[0].reportID', reportID); }, - [reportID, transactionID], + [reportID, transactionID, iouType, participants, updateRouteParams], ); - const goToNextStep = useCallback(() => { - const nextStepIOUType = numberOfParticipants.current === 1 ? iouType : CONST.IOU.TYPE.SPLIT; - IOU.setMoneyRequestTag(transactionID, ''); - IOU.setMoneyRequestCategory(transactionID, ''); - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(nextStepIOUType, transactionID, selectedReportID.current || reportID)); - }, [iouType, transactionID, reportID]); + const goToNextStep = useCallback( + (selectedIouType) => { + const isSplit = selectedIouType === CONST.IOU.TYPE.SPLIT; + const nextStepIOUType = !isSplit && iouType !== CONST.IOU.TYPE.REQUEST ? CONST.IOU.TYPE.REQUEST : iouType; + IOU.setMoneyRequestTag(transactionID, ''); + IOU.setMoneyRequestCategory(transactionID, ''); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(nextStepIOUType, transactionID, selectedReportID.current || reportID)); + }, + [iouType, transactionID, reportID], + ); const navigateBack = useCallback(() => { IOUUtils.navigateToStartMoneyRequestStep(iouRequestType, iouType, transactionID, reportID); @@ -90,7 +131,7 @@ function IOURequestStepParticipants({ > {({didScreenTransitionEnd}) => (