diff --git a/src/CONST.ts b/src/CONST.ts index c6849db630f20..fc4a3729bd01d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1180,6 +1180,10 @@ const CONST = { EXPENSIFY: 'Expensify', VBBA: 'ACH', }, + ACTION: { + EDIT: 'edit', + CREATE: 'create', + }, DEFAULT_AMOUNT: 0, TYPE: { SEND: 'send', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index e8a860582bb15..7538a16d1a2c2 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -368,9 +368,9 @@ const ROUTES = { getUrlWithBackToParam(`create/${iouType}/participants/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_SCAN: { - route: 'create/:iouType/scan/:transactionID/:reportID', - getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`create/${iouType}/scan/${transactionID}/${reportID}`, backTo), + route: ':action/:iouType/scan/:transactionID/:reportID', + getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action}/${iouType}/scan/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_TAG: { route: 'create/:iouType/tag/:transactionID/:reportID', diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 1b4d350f7d4fb..149dd70391514 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -383,7 +383,15 @@ function AttachmentModal(props) { text: props.translate('common.replace'), onSelected: () => { closeModal(); - Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)); + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute( + CONST.IOU.ACTION.EDIT, + CONST.IOU.TYPE.REQUEST, + props.transaction.transactionID, + props.report.reportID, + Navigation.getActiveRouteWithoutParams(), + ), + ); }, }); } diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 488630dd0590b..be42abc797ddf 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -100,7 +100,16 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, threeDotsMenuItems.push({ icon: Expensicons.Receipt, text: translate('receipt.addReceipt'), - onSelected: () => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)), + onSelected: () => + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute( + CONST.IOU.ACTION.EDIT, + CONST.IOU.TYPE.REQUEST, + transaction.transactionID, + report.reportID, + Navigation.getActiveRouteWithoutParams(), + ), + ), }); } threeDotsMenuItems.push({ diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index b9ecb647f437b..fd0d5e6f6e7a8 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -249,7 +249,17 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate {!hasReceipt && canEditReceipt && canUseViolations && ( Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT))} + onPress={() => + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute( + CONST.IOU.ACTION.EDIT, + CONST.IOU.TYPE.REQUEST, + transaction.transactionID, + report.reportID, + Navigation.getActiveRouteWithoutParams(), + ), + ) + } /> )} {canUseViolations && } diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts index cd95e3c398bf9..53c6f6af476c8 100644 --- a/src/libs/IOUUtils.ts +++ b/src/libs/IOUUtils.ts @@ -40,9 +40,9 @@ function navigateToStartStepIfScanFileCannotBeRead( } const onFailure = () => { - IOU.setMoneyRequestReceipt_temporaryForRefactor(transactionID, '', ''); + IOU.setMoneyRequestReceipt(transactionID, '', '', true); if (requestType === CONST.IOU.REQUEST_TYPE.MANUAL) { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams())); return; } navigateToStartMoneyRequestStep(requestType, iouType, transactionID, reportID); diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 75ae60970451b..55bdcb7bbd633 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -264,9 +264,13 @@ function setMoneyRequestParticipants_temporaryForRefactor(transactionID, partici * @param {String} transactionID * @param {String} source * @param {String} filename + * @param {Boolean} isDraft */ -function setMoneyRequestReceipt_temporaryForRefactor(transactionID, source, filename) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {receipt: {source}, filename}); +function setMoneyRequestReceipt(transactionID, source, filename, isDraft) { + Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { + receipt: {source}, + filename, + }); } /** @@ -3333,10 +3337,10 @@ function detachReceipt(transactionID) { /** * @param {String} transactionID - * @param {Object} receipt - * @param {String} filePath + * @param {Object} file + * @param {String} source */ -function replaceReceipt(transactionID, receipt, filePath) { +function replaceReceipt(transactionID, file, source) { const transaction = lodashGet(allTransactions, 'transactionID', {}); const oldReceipt = lodashGet(transaction, 'receipt', {}); @@ -3346,10 +3350,10 @@ function replaceReceipt(transactionID, receipt, filePath) { key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, value: { receipt: { - source: filePath, + source, state: CONST.IOU.RECEIPT_STATE.OPEN, }, - filename: receipt.name, + filename: file.name, }, }, ]; @@ -3365,7 +3369,7 @@ function replaceReceipt(transactionID, receipt, filePath) { }, ]; - API.write('ReplaceReceipt', {transactionID, receipt}, {optimisticData, failureData}); + API.write('ReplaceReceipt', {transactionID, receipt: file}, {optimisticData, failureData}); } /** @@ -3491,14 +3495,6 @@ function setMoneyRequestParticipants(participants, isSplitRequest) { Onyx.merge(ONYXKEYS.IOU, {participants, isSplitRequest}); } -/** - * @param {String} receiptPath - * @param {String} receiptFilename - */ -function setMoneyRequestReceipt(receiptPath, receiptFilename) { - Onyx.merge(ONYXKEYS.IOU, {receiptPath, receiptFilename, merchant: ''}); -} - function setUpDistanceTransaction() { const transactionID = NumberUtils.rand64(); Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { @@ -3602,7 +3598,7 @@ export { setMoneyRequestDescription_temporaryForRefactor, setMoneyRequestMerchant_temporaryForRefactor, setMoneyRequestParticipants_temporaryForRefactor, - setMoneyRequestReceipt_temporaryForRefactor, + setMoneyRequestReceipt, setMoneyRequestTag_temporaryForRefactor, setMoneyRequestAmount, setMoneyRequestBillable, @@ -3613,7 +3609,6 @@ export { setMoneyRequestId, setMoneyRequestMerchant, setMoneyRequestParticipantsFromReport, - setMoneyRequestReceipt, setMoneyRequestTag, setMoneyRequestTaxAmount, setMoneyRequestTaxRate, diff --git a/src/pages/EditRequestReceiptPage.js b/src/pages/EditRequestReceiptPage.js index 1525ec1629633..40fe64da7eed9 100644 --- a/src/pages/EditRequestReceiptPage.js +++ b/src/pages/EditRequestReceiptPage.js @@ -7,7 +7,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import ReceiptSelector from './iou/ReceiptSelector'; +import IOURequestStepScan from './iou/request/step/IOURequestStepScan'; const propTypes = { /** React Navigation route */ @@ -21,16 +21,9 @@ const propTypes = { reportID: PropTypes.string, }), }).isRequired, - - /** The id of the transaction we're editing */ - transactionID: PropTypes.string, -}; - -const defaultProps = { - transactionID: '', }; -function EditRequestReceiptPage({route, transactionID}) { +function EditRequestReceiptPage({route}) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [isDraggingOver, setIsDraggingOver] = useState(false); @@ -49,11 +42,7 @@ function EditRequestReceiptPage({route, transactionID}) { title={translate('common.receipt')} onBackButtonPress={Navigation.goBack} /> - + )} @@ -62,7 +51,6 @@ function EditRequestReceiptPage({route, transactionID}) { } EditRequestReceiptPage.propTypes = propTypes; -EditRequestReceiptPage.defaultProps = defaultProps; EditRequestReceiptPage.displayName = 'EditRequestReceiptPage'; export default EditRequestReceiptPage; diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index 7b87b50bb7f3a..0a0efc38313a2 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -24,7 +24,7 @@ import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import NewDistanceRequestPage from './NewDistanceRequestPage'; -import ReceiptSelector from './ReceiptSelector'; +import IOURequestStepScan from './request/step/IOURequestStepScan'; import NewRequestAmountPage from './steps/NewRequestAmountPage'; const propTypes = { @@ -132,11 +132,7 @@ function MoneyRequestSelectorPage(props) { component={NewRequestAmountPage} initialParams={{reportID, iouType}} /> - + {() => } {shouldDisplayDistanceRequest && ( { - const trackRef = useRef(null); - const shouldShowCamera = useTabNavigatorFocus({ - tabIndex: cameraTabIndex, - }); - - const handleOnUserMedia = (stream) => { - if (props.onUserMedia) { - props.onUserMedia(stream); - } - - const [track] = stream.getVideoTracks(); - const capabilities = track.getCapabilities(); - if (capabilities.torch) { - trackRef.current = track; - } - if (onTorchAvailability) { - onTorchAvailability(!!capabilities.torch); - } - }; - - useEffect(() => { - if (!trackRef.current) { - return; - } - - trackRef.current.applyConstraints({ - advanced: [{torch: torchOn}], - }); - }, [torchOn]); - - if (!shouldShowCamera) { - return null; - } - return ( - - - - ); -}); - -NavigationAwareCamera.propTypes = propTypes; -NavigationAwareCamera.displayName = 'NavigationAwareCamera'; -NavigationAwareCamera.defaultProps = defaultProps; - -export default NavigationAwareCamera; diff --git a/src/pages/iou/ReceiptSelector/NavigationAwareCamera/index.native.js b/src/pages/iou/ReceiptSelector/NavigationAwareCamera/index.native.js deleted file mode 100644 index 65c17d3cb7abb..0000000000000 --- a/src/pages/iou/ReceiptSelector/NavigationAwareCamera/index.native.js +++ /dev/null @@ -1,28 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {Camera} from 'react-native-vision-camera'; -import useTabNavigatorFocus from '@hooks/useTabNavigatorFocus'; - -const propTypes = { - /* The index of the tab that contains this camera */ - cameraTabIndex: PropTypes.number.isRequired, -}; - -// Wraps a camera that will only be active when the tab is focused or as soon as it starts to become focused. -const NavigationAwareCamera = React.forwardRef(({cameraTabIndex, ...props}, ref) => { - const isCameraActive = useTabNavigatorFocus({tabIndex: cameraTabIndex}); - - return ( - - ); -}); - -NavigationAwareCamera.propTypes = propTypes; -NavigationAwareCamera.displayName = 'NavigationAwareCamera'; - -export default NavigationAwareCamera; diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js deleted file mode 100644 index ae871260b03e7..0000000000000 --- a/src/pages/iou/ReceiptSelector/index.js +++ /dev/null @@ -1,340 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useCallback, useContext, useReducer, useRef, useState} from 'react'; -import {ActivityIndicator, PanResponder, PixelRatio, Text, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import Hand from '@assets/images/hand.svg'; -import ReceiptUpload from '@assets/images/receipt-upload.svg'; -import Shutter from '@assets/images/shutter.svg'; -import AttachmentPicker from '@components/AttachmentPicker'; -import Button from '@components/Button'; -import ConfirmModal from '@components/ConfirmModal'; -import CopyTextToClipboard from '@components/CopyTextToClipboard'; -import {DragAndDropContext} from '@components/DragAndDrop/Provider'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import ImageSVG from '@components/ImageSVG'; -import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; -import useLocalize from '@hooks/useLocalize'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; -import * as Browser from '@libs/Browser'; -import * as FileUtils from '@libs/fileDownload/FileUtils'; -import Navigation from '@libs/Navigation/Navigation'; -import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes'; -import ReceiptDropUI from '@pages/iou/ReceiptDropUI'; -import reportPropTypes from '@pages/reportPropTypes'; -import * as IOU from '@userActions/IOU'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import NavigationAwareCamera from './NavigationAwareCamera'; - -const propTypes = { - /** The report on which the request is initiated on */ - report: reportPropTypes, - - /** React Navigation route */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.string, - - /** The report ID of the IOU */ - reportID: PropTypes.string, - }), - - /** The current route path */ - path: PropTypes.string, - }).isRequired, - - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: iouPropTypes, - - /** The id of the transaction we're editing */ - transactionID: PropTypes.string, -}; - -const defaultProps = { - report: {}, - iou: iouDefaultProps, - transactionID: '', -}; - -function ReceiptSelector({route, transactionID, iou, report}) { - const theme = useTheme(); - const styles = useThemeStyles(); - const iouType = lodashGet(route, 'params.iouType', ''); - const pageIndex = lodashGet(route, 'params.pageIndex', 1); - - // Grouping related states - const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); - const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState(''); - const [attachmentInvalidReason, setAttachmentValidReason] = useState(''); - - const [receiptImageTopPosition, setReceiptImageTopPosition] = useState(0); - const {isSmallScreenWidth} = useWindowDimensions(); - const {translate} = useLocalize(); - const {isDraggingOver} = useContext(DragAndDropContext); - - const [cameraPermissionState, setCameraPermissionState] = useState('prompt'); - const [isFlashLightOn, toggleFlashlight] = useReducer((state) => !state, false); - const [isTorchAvailable, setIsTorchAvailable] = useState(false); - const cameraRef = useRef(null); - - const hideReciptModal = () => { - setIsAttachmentInvalid(false); - }; - - /** - * Sets the upload receipt error modal content when an invalid receipt is uploaded - * @param {*} isInvalid - * @param {*} title - * @param {*} reason - */ - const setUploadReceiptError = (isInvalid, title, reason) => { - setIsAttachmentInvalid(isInvalid); - setAttachmentInvalidReasonTitle(title); - setAttachmentValidReason(reason); - }; - - function validateReceipt(file) { - const {fileExtension} = FileUtils.splitExtensionFromFileName(lodashGet(file, 'name', '')); - if (!CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS.includes(fileExtension.toLowerCase())) { - setUploadReceiptError(true, 'attachmentPicker.wrongFileType', 'attachmentPicker.notAllowedExtension'); - return false; - } - - if (lodashGet(file, 'size', 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { - setUploadReceiptError(true, 'attachmentPicker.attachmentTooLarge', 'attachmentPicker.sizeExceeded'); - return false; - } - - if (lodashGet(file, 'size', 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { - setUploadReceiptError(true, 'attachmentPicker.attachmentTooSmall', 'attachmentPicker.sizeNotMet'); - return false; - } - - return true; - } - - /** - * Sets the Receipt objects and navigates the user to the next page - * @param {Object} file - * @param {Object} iouObject - * @param {Object} reportObject - */ - const setReceiptAndNavigate = (file, iouObject, reportObject) => { - if (!validateReceipt(file)) { - return; - } - - const filePath = URL.createObjectURL(file); - IOU.setMoneyRequestReceipt(filePath, file.name); - - if (transactionID) { - IOU.replaceReceipt(transactionID, file, filePath); - Navigation.dismissModal(); - return; - } - - IOU.navigateToNextPage(iouObject, iouType, reportObject, route.path); - }; - - const capturePhoto = useCallback(() => { - if (!cameraRef.current.getScreenshot) { - return; - } - const imageBase64 = cameraRef.current.getScreenshot(); - const filename = `receipt_${Date.now()}.png`; - const imageFile = FileUtils.base64ToFile(imageBase64, filename); - const filePath = URL.createObjectURL(imageFile); - IOU.setMoneyRequestReceipt(filePath, imageFile.name); - - if (transactionID) { - IOU.replaceReceipt(transactionID, imageFile, filePath); - Navigation.dismissModal(); - return; - } - - IOU.navigateToNextPage(iou, iouType, report, route.path); - }, [cameraRef, iou, report, iouType, transactionID, route.path]); - - const panResponder = useRef( - PanResponder.create({ - onPanResponderTerminationRequest: () => false, - }), - ).current; - - const mobileCameraView = () => ( - <> - - {(cameraPermissionState === 'prompt' || !cameraPermissionState) && ( - - )} - - {cameraPermissionState === 'denied' && ( - - - {translate('receipt.takePhoto')} - {translate('receipt.cameraAccess')} - - )} - setCameraPermissionState('granted')} - onUserMediaError={() => setCameraPermissionState('denied')} - style={{...styles.videoContainer, display: cameraPermissionState !== 'granted' ? 'none' : 'block'}} - ref={cameraRef} - screenshotFormat="image/png" - videoConstraints={{facingMode: {exact: 'environment'}}} - torchOn={isFlashLightOn} - onTorchAvailability={setIsTorchAvailable} - forceScreenshotSourceSize - cameraTabIndex={pageIndex} - /> - - - - - {({openPicker}) => ( - { - openPicker({ - onPicked: (file) => { - setReceiptAndNavigate(file, iou, report); - }, - }); - }} - > - - - )} - - - - - - - - - - ); - - const desktopUploadView = () => ( - <> - setReceiptImageTopPosition(PixelRatio.roundToNearestPixel(nativeEvent.layout.top))}> - - - - - {translate('receipt.upload')} - - {isSmallScreenWidth ? translate('receipt.chooseReceipt') : translate('receipt.dragReceiptBeforeEmail')} - - {isSmallScreenWidth ? null : translate('receipt.dragReceiptAfterEmail')} - - - - - {({openPicker}) => ( -