From c463bf97076452d3cf6272dbfb8044ade8111aea Mon Sep 17 00:00:00 2001 From: ojasjadhav2 <140224450+ojasjadhav2@users.noreply.github.com> Date: Mon, 31 Jul 2023 20:40:40 +0530 Subject: [PATCH 001/201] migrated SwipableView > index.native.js --- src/components/SwipeableView/index.native.js | 36 +++++++++----------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/components/SwipeableView/index.native.js b/src/components/SwipeableView/index.native.js index b4751789fcfe..f4a67ae1c059 100644 --- a/src/components/SwipeableView/index.native.js +++ b/src/components/SwipeableView/index.native.js @@ -1,4 +1,4 @@ -import React, {PureComponent} from 'react'; +import React, {useRef} from 'react'; import {PanResponder, View} from 'react-native'; import PropTypes from 'prop-types'; import CONST from '../../CONST'; @@ -10,33 +10,29 @@ const propTypes = { onSwipeDown: PropTypes.func.isRequired, }; -class SwipeableView extends PureComponent { - constructor(props) { - super(props); - - const minimumPixelDistance = CONST.COMPOSER_MAX_HEIGHT; - this.oldY = 0; - this.panResponder = PanResponder.create({ +function SwipeableView({children, onSwipeDown}) { + const minimumPixelDistance = CONST.COMPOSER_MAX_HEIGHT; + const oldYRef = useRef(0); + const panResponder = useRef( + PanResponder.create({ // The PanResponder gets focus only when the y-axis movement is over minimumPixelDistance // & swip direction is downwards onMoveShouldSetPanResponderCapture: (_event, gestureState) => { - if (gestureState.dy - this.oldY > 0 && gestureState.dy > minimumPixelDistance) { + if (gestureState.dy - oldYRef.current > 0 && gestureState.dy > minimumPixelDistance) { return true; } - this.oldY = gestureState.dy; + oldYRef.current = gestureState.dy; }, // Calls the callback when the swipe down is released; after the completion of the gesture - onPanResponderRelease: this.props.onSwipeDown, - }); - } - - render() { - return ( - // eslint-disable-next-line react/jsx-props-no-spreading - {this.props.children} - ); - } + onPanResponderRelease: onSwipeDown, + }), + ).current; + + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + {children} + ); } SwipeableView.propTypes = propTypes; From 68a6ec66215c6bf3943a2df8252ea69d802c74de Mon Sep 17 00:00:00 2001 From: ojasjadhav2 <140224450+ojasjadhav2@users.noreply.github.com> Date: Tue, 1 Aug 2023 23:28:48 +0530 Subject: [PATCH 002/201] added displayName --- src/components/SwipeableView/index.native.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/SwipeableView/index.native.js b/src/components/SwipeableView/index.native.js index f4a67ae1c059..6e96cfbbbc45 100644 --- a/src/components/SwipeableView/index.native.js +++ b/src/components/SwipeableView/index.native.js @@ -36,5 +36,6 @@ function SwipeableView({children, onSwipeDown}) { } SwipeableView.propTypes = propTypes; +SwipeableView.displayName = 'SwipeableView'; export default SwipeableView; From ef896f7c17337061de04034b046bbc637ba7ffbc Mon Sep 17 00:00:00 2001 From: ojasjadhav2 <140224450+ojasjadhav2@users.noreply.github.com> Date: Sun, 6 Aug 2023 23:03:27 +0530 Subject: [PATCH 003/201] using props instead of spreading --- src/components/SwipeableView/index.native.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/SwipeableView/index.native.js b/src/components/SwipeableView/index.native.js index 6e96cfbbbc45..2f1148721af1 100644 --- a/src/components/SwipeableView/index.native.js +++ b/src/components/SwipeableView/index.native.js @@ -10,13 +10,13 @@ const propTypes = { onSwipeDown: PropTypes.func.isRequired, }; -function SwipeableView({children, onSwipeDown}) { +function SwipeableView(props) { const minimumPixelDistance = CONST.COMPOSER_MAX_HEIGHT; const oldYRef = useRef(0); const panResponder = useRef( PanResponder.create({ // The PanResponder gets focus only when the y-axis movement is over minimumPixelDistance - // & swip direction is downwards + // & swipe direction is downwards onMoveShouldSetPanResponderCapture: (_event, gestureState) => { if (gestureState.dy - oldYRef.current > 0 && gestureState.dy > minimumPixelDistance) { return true; @@ -25,13 +25,13 @@ function SwipeableView({children, onSwipeDown}) { }, // Calls the callback when the swipe down is released; after the completion of the gesture - onPanResponderRelease: onSwipeDown, + onPanResponderRelease: props.onSwipeDown, }), ).current; return ( // eslint-disable-next-line react/jsx-props-no-spreading - {children} + {props.children} ); } From a3b0dd238e5e6a1375355d4bba11c510bb02e560 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Mon, 7 Aug 2023 16:23:21 -0400 Subject: [PATCH 004/201] Disable copy to clipboard for report previews --- src/pages/home/report/ContextMenu/ContextMenuActions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 79e49efb6a03..449e10d35992 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -169,6 +169,7 @@ export default [ shouldShow: (type, reportAction) => type === CONTEXT_MENU_TYPES.REPORT_ACTION && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU && + reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.TASKREOPENED && From f143e1fa72d6988fa294513117252a06ab184f42 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Tue, 8 Aug 2023 16:25:23 -0400 Subject: [PATCH 005/201] Show receipt image --- src/components/ReportActionItem/ReportPreview.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index d5d85df5e7ee..37731d76aaf2 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -25,6 +25,7 @@ import refPropTypes from '../refPropTypes'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; import themeColors from '../../styles/themes/default'; import reportPropTypes from '../../pages/reportPropTypes'; +import RenderHTML from '../RenderHTML'; const propTypes = { /** All the data of the action */ @@ -116,6 +117,10 @@ function ReportPreview(props) { const previewMessage = props.translate(ReportUtils.isSettled(props.iouReportID) || props.iouReport.isWaitingOnBankAccount ? 'iou.payerPaid' : 'iou.payerOwes', {payer: managerName}); const shouldShowSettlementButton = !_.isEmpty(props.iouReport) && isCurrentUserManager && !ReportUtils.isSettled(props.iouReportID) && !props.iouReport.isWaitingOnBankAccount && reportTotal !== 0; + + const receiptIDs = lodashGet(props.action, 'childLastReceiptTransactionIDs', '').split(','); + const receipts = _.filter(props.receipts, (transaction) => receiptIDs.includes(transaction.transactionID)); + return ( + `} /> @@ -179,6 +185,9 @@ export default compose( iouReport: { key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, }, + receipts: { + key: ONYXKEYS.COLLECTION.TRANSACTION, + }, session: { key: ONYXKEYS.SESSION, }, From 04d0c3ba35f0753960ea7a6e9e25c5178f0d7c0b Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Tue, 8 Aug 2023 17:21:28 -0400 Subject: [PATCH 006/201] Fix receipt image loading --- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.js | 8 ++++---- src/components/ReportActionItem/ReportPreview.js | 8 +++++++- src/libs/ReceiptUtils.js | 4 +++- src/pages/iou/ReceiptSelector/index.js | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index 103e653d7d88..643785ab09d1 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -33,11 +33,11 @@ function ImageRenderer(props) { // Concierge responder attachments are uploaded to S3 without any access // control and thus require no authToken to verify access. // - const isAttachment = Boolean(htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]); + const isAttachmentOrReceipt = Boolean(htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]); // Files created/uploaded/hosted by App should resolve from API ROOT. Other URLs aren't modified const previewSource = tryResolveUrlFromApiRoot(htmlAttribs.src); - const source = tryResolveUrlFromApiRoot(isAttachment ? htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE] : htmlAttribs.src); + const source = tryResolveUrlFromApiRoot(isAttachmentOrReceipt ? htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE] : htmlAttribs.src); const imageWidth = htmlAttribs['data-expensify-width'] ? parseInt(htmlAttribs['data-expensify-width'], 10) : undefined; const imageHeight = htmlAttribs['data-expensify-height'] ? parseInt(htmlAttribs['data-expensify-height'], 10) : undefined; @@ -47,7 +47,7 @@ function ImageRenderer(props) { @@ -67,7 +67,7 @@ function ImageRenderer(props) { diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 37731d76aaf2..3931e2248dcd 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -26,6 +26,7 @@ import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; import themeColors from '../../styles/themes/default'; import reportPropTypes from '../../pages/reportPropTypes'; import RenderHTML from '../RenderHTML'; +import * as ReceiptUtils from '../../libs/ReceiptUtils'; const propTypes = { /** All the data of the action */ @@ -134,7 +135,12 @@ function ReportPreview(props) { accessibilityRole="button" accessibilityLabel={props.translate('iou.viewDetails')} > - `} /> + + `} /> diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index b8ec54c0e899..42114914f084 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -25,4 +25,6 @@ const validateReceipt = (file) => { return true; }; -export default {validateReceipt}; +export { + validateReceipt, +}; diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index c674878c2c73..392e96887f8b 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -19,7 +19,7 @@ import Receipt from '../../../libs/actions/Receipt'; import useWindowDimensions from '../../../hooks/useWindowDimensions'; import useLocalize from '../../../hooks/useLocalize'; import {DragAndDropContext} from '../../../components/DragAndDrop/Provider'; -import ReceiptUtils from '../../../libs/ReceiptUtils'; +import * as ReceiptUtils from '../../../libs/ReceiptUtils'; const propTypes = { /** Information shown to the user when a receipt is not valid */ From 7466501076ff0380907e993999e7d7f17d92e533 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Tue, 8 Aug 2023 17:59:28 -0400 Subject: [PATCH 007/201] Begin styling for multiple receipts --- .../ReportActionItem/ReportPreview.js | 50 +++++++++++-------- src/styles/styles.js | 11 ++++ 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 3931e2248dcd..1519e4116d2e 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -135,29 +135,37 @@ function ReportPreview(props) { accessibilityRole="button" accessibilityLabel={props.translate('iou.viewDetails')} > - - `} /> - + - - {previewMessage} - - - - - {displayAmount} - {ReportUtils.isSettled(props.iouReportID) && ( - - ( + + - - )} + `} /> + + ))} + + + + + {previewMessage} + + + + + {displayAmount} + {ReportUtils.isSettled(props.iouReportID) && ( + + + + )} + {shouldShowSettlementButton && ( diff --git a/src/styles/styles.js b/src/styles/styles.js index f7517cd3acb7..4b2db3483d71 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3645,6 +3645,17 @@ const styles = { borderRadius: 16, margin: 20, }, + + reportPreviewBox: { + backgroundColor: themeColors.cardBG, + borderRadius: variables.componentBorderRadiusLarge, + maxWidth: variables.sideBarWidth, + width: '100%', + }, + + reportPreviewBoxText: { + padding: 16, + } }; export default styles; From 941d730532ec090e4e4e2f5d91d0388f1ac63864 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 9 Aug 2023 09:29:09 -0400 Subject: [PATCH 008/201] Fit images to container --- .../HTMLEngineProvider/BaseHTMLEngineProvider.js | 2 +- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.js | 11 ++++++++--- src/components/ReportActionItem/ReportPreview.js | 7 ++++--- src/components/ThumbnailImage.js | 8 +++++++- src/styles/styles.js | 2 +- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js index 5417f7af6820..e156c8bda3f4 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js @@ -48,7 +48,7 @@ const customHTMLElementModels = { 'mention-here': defaultHTMLElementModels.span.extend({tagName: 'mention-here'}), }; -const defaultViewProps = {style: [styles.alignItemsStart, styles.userSelectText]}; +const defaultViewProps = {style: [styles.alignItemsStart, styles.userSelectText, styles.w100, styles.h100]}; // We are using the explicit composite architecture for performance gains. // Configuration for RenderHTML is handled in a top-level component providing diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index 643785ab09d1..0ddd6c4657d7 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -43,19 +43,23 @@ function ImageRenderer(props) { const imageHeight = htmlAttribs['data-expensify-height'] ? parseInt(htmlAttribs['data-expensify-height'], 10) : undefined; const imagePreviewModalDisabled = htmlAttribs['data-expensify-preview-modal-disabled'] === 'true'; + const shouldFitContainer = htmlAttribs['data-expensify-fit-container'] === 'true'; + const fitContainerStyle = shouldFitContainer ? [styles.w100, styles.h100] : []; + return imagePreviewModalDisabled ? ( ) : ( {({anchor, report, action, checkIfContextMenuActive}) => ( { const route = ROUTES.getReportAttachmentRoute(report.reportID, source); Navigation.navigate(route); @@ -66,10 +70,11 @@ function ImageRenderer(props) { > )} diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 1519e4116d2e..4011c325fb71 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -136,13 +136,14 @@ function ReportPreview(props) { accessibilityLabel={props.translate('iou.viewDetails')} > - + {_.map(receipts, ({receipt, transactionID}) => ( - + `} /> diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 970227636e46..5c24cbd82bbc 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -25,6 +25,8 @@ const propTypes = { /** Height of the thumbnail image */ imageHeight: PropTypes.number, + shouldDynamicallyResize: PropTypes.bool, + ...windowDimensionsPropTypes, }; @@ -32,6 +34,7 @@ const defaultProps = { style: {}, imageWidth: 200, imageHeight: 200, + shouldDynamicallyResize: true, }; class ThumbnailImage extends PureComponent { @@ -91,9 +94,12 @@ class ThumbnailImage extends PureComponent { } render() { + const sizeStyles = this.props.shouldDynamicallyResize + ? [StyleUtils.getWidthAndHeightStyle(this.state.thumbnailWidth, this.state.thumbnailHeight)] + : [styles.w100, styles.h100]; return ( - + Date: Wed, 9 Aug 2023 11:53:46 -0400 Subject: [PATCH 009/201] Polish preview box on hover --- .../HTMLRenderers/ImageRenderer.js | 5 +-- .../MoneyRequestConfirmationList.js | 39 +------------------ .../ReportActionItem/ReportPreview.js | 36 +++++++++++------ src/libs/ReceiptUtils.js | 37 ++++++++++++++++++ src/styles/styles.js | 22 +++++++++++ 5 files changed, 87 insertions(+), 52 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index 0ddd6c4657d7..9a7f02220068 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -44,12 +44,11 @@ function ImageRenderer(props) { const imagePreviewModalDisabled = htmlAttribs['data-expensify-preview-modal-disabled'] === 'true'; const shouldFitContainer = htmlAttribs['data-expensify-fit-container'] === 'true'; - const fitContainerStyle = shouldFitContainer ? [styles.w100, styles.h100] : []; return imagePreviewModalDisabled ? ( { - const {fileExtension} = FileUtils.splitExtensionFromFileName(receiptSource); - const isReceiptImage = Str.isImage(props.receiptSource); - - if (isReceiptImage) { - return receiptPath; - } - - if (fileExtension === CONST.IOU.FILE_TYPES.HTML) { - return ReceiptHTML; - } - - if (fileExtension === CONST.IOU.FILE_TYPES.DOC || fileExtension === CONST.IOU.FILE_TYPES.DOCX) { - return ReceiptDoc; - } - - if (fileExtension === CONST.IOU.FILE_TYPES.SVG) { - return ReceiptSVG; - } - - return ReceiptGeneric; - }; - return ( ) : ( - - {_.map(receipts, ({receipt, transactionID}) => ( - - - `} /> - - ))} + + {_.map(receipts, ({receipt, filename, transactionID}) => { + const thumbnailURI = ReceiptUtils.getImageURI(`${receipt.source.replace('.jpg', '')}.1024.jpg`, filename); + const uri = ReceiptUtils.getImageURI(receipt.source, filename); + + return ( + + {uri === receipt.source + ? + `} /> + : + } + + ); + })} diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index 42114914f084..fdb2e180bba2 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -1,9 +1,14 @@ import lodashGet from 'lodash/get'; import _ from 'underscore'; +import Str from 'expensify-common/lib/str'; import * as FileUtils from './fileDownload/FileUtils'; import CONST from '../CONST'; import Receipt from './actions/Receipt'; import * as Localize from './Localize'; +import ReceiptHTML from '../../assets/images/receipt-html.png'; +import ReceiptDoc from '../../assets/images/receipt-doc.png'; +import ReceiptGeneric from '../../assets/images/receipt-generic.png'; +import ReceiptSVG from '../../assets/images/receipt-svg.png'; const validateReceipt = (file) => { const {fileExtension} = FileUtils.splitExtensionFromFileName(lodashGet(file, 'name', '')); @@ -25,6 +30,38 @@ const validateReceipt = (file) => { return true; }; + +/** + * Grab the appropriate receipt image URI based on file type + * + * @param {String} path URI to image, i.e. blob://new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg + * @param {String} filename of uploaded image or last part of remote URI + * @returns {*} + */ +const getImageURI = (path, filename) => { + const {fileExtension} = FileUtils.splitExtensionFromFileName(filename); + const isReceiptImage = Str.isImage(filename); + + if (isReceiptImage) { + return path; + } + + if (fileExtension === CONST.IOU.FILE_TYPES.HTML) { + return ReceiptHTML; + } + + if (fileExtension === CONST.IOU.FILE_TYPES.DOC || fileExtension === CONST.IOU.FILE_TYPES.DOCX) { + return ReceiptDoc; + } + + if (fileExtension === CONST.IOU.FILE_TYPES.SVG) { + return ReceiptSVG; + } + + return ReceiptGeneric; +}; + export { validateReceipt, + getImageURI, }; diff --git a/src/styles/styles.js b/src/styles/styles.js index dd4860047d2a..f5b2d883d845 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3653,6 +3653,28 @@ const styles = { width: '100%', }, + reportPreviewBoxImages: { + flexDirection: 'row', + borderWidth: 2, + borderColor: themeColors.cardBG, + borderTopLeftRadius: variables.componentBorderRadiusLarge, + borderTopRightRadius: variables.componentBorderRadiusLarge, + overflow: 'hidden', + height: 200, + }, + + reportPreviewBoxImage: { + borderWidth: 1, + borderColor: themeColors.cardBG, + flex: 1, + width: '100%', + height: '100%', + }, + + reportPreviewBoxHoverBorder: { + borderColor: themeColors.border, + }, + reportPreviewBoxText: { padding: 16, }, From f0ced3a749768b051648b9efc7b9ebaf13ef7cab Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 9 Aug 2023 12:01:40 -0400 Subject: [PATCH 010/201] Only apply full width when fitting container --- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index 9a7f02220068..2f4ee7780346 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -44,6 +44,7 @@ function ImageRenderer(props) { const imagePreviewModalDisabled = htmlAttribs['data-expensify-preview-modal-disabled'] === 'true'; const shouldFitContainer = htmlAttribs['data-expensify-fit-container'] === 'true'; + const sizingStyle = shouldFitContainer ? [styles.w100, styles.h100] : []; return imagePreviewModalDisabled ? ( {({anchor, report, action, checkIfContextMenuActive}) => ( { const route = ROUTES.getReportAttachmentRoute(report.reportID, source); Navigation.navigate(route); From e6954010031970e42f2e736931f3af4828011e74 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 9 Aug 2023 13:14:03 -0400 Subject: [PATCH 011/201] Add scanning in progress --- src/CONST.js | 3 ++ .../ReportActionItem/ReportPreview.js | 29 +++++++++++++------ src/languages/en.js | 2 ++ src/languages/es.js | 2 ++ src/libs/ReceiptUtils.js | 13 ++++++--- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 37a56b69f6d2..b2d5ff2b2956 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -1073,6 +1073,9 @@ const CONST = { AMOUNT_MAX_LENGTH: 10, RECEIPT_STATE: { SCANREADY: 'SCANREADY', + SCANNING: 'SCANNING', + SCANCOMPLETE: 'SCANCOMPLETE', + SCANFAILED: 'SCANFAILED', }, FILE_TYPES: { HTML: 'html', diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 8ec7da4e57ff..4e57a806eb93 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useMemo} from 'react'; import _ from 'underscore'; import {View} from 'react-native'; import PropTypes from 'prop-types'; @@ -99,9 +99,17 @@ function ReportPreview(props) { const managerID = props.iouReport.managerID || props.action.actorAccountID || 0; const isCurrentUserManager = managerID === lodashGet(props.session, 'accountID'); const reportTotal = ReportUtils.getMoneyRequestTotal(props.iouReport); + + const iouSettled = ReportUtils.isSettled(props.iouReportID); + const receiptIDs = lodashGet(props.action, 'childLastReceiptTransactionIDs', '').split(','); + const receipts = _.filter(props.receipts, (transaction) => receiptIDs.includes(transaction.transactionID)); + const isScanning = _.some(receipts, ({receipt}) => ReceiptUtils.isBeingScanned(receipt)); + let displayAmount; if (reportTotal) { displayAmount = CurrencyUtils.convertToDisplayString(reportTotal, props.iouReport.currency); + } else if (isScanning) { + displayAmount = props.translate('iou.receiptScanning'); } else { // If iouReport is not available, get amount from the action message (Ex: "Domain20821's Workspace owes $33.00" or "paid ₫60" or "paid -₫60 elsewhere") displayAmount = ''; @@ -114,14 +122,17 @@ function ReportPreview(props) { } } + let previewMessage; const managerName = ReportUtils.isPolicyExpenseChat(props.chatReport) ? ReportUtils.getPolicyName(props.chatReport) : ReportUtils.getDisplayNameForParticipant(managerID, true); + if (_.some(receipts, ({receipt}) => ReceiptUtils.isBeingScanned(receipt))) { + previewMessage = props.translate('iou.receipt'); + } else { + previewMessage = props.translate(iouSettled || props.iouReport.isWaitingOnBankAccount ? 'iou.payerPaid' : 'iou.payerOwes', {payer: managerName}); + } + const bankAccountRoute = ReportUtils.getBankAccountRoute(props.chatReport); - const previewMessage = props.translate(ReportUtils.isSettled(props.iouReportID) || props.iouReport.isWaitingOnBankAccount ? 'iou.payerPaid' : 'iou.payerOwes', {payer: managerName}); const shouldShowSettlementButton = - !_.isEmpty(props.iouReport) && isCurrentUserManager && !ReportUtils.isSettled(props.iouReportID) && !props.iouReport.isWaitingOnBankAccount && reportTotal !== 0; - - const receiptIDs = lodashGet(props.action, 'childLastReceiptTransactionIDs', '').split(','); - const receipts = _.filter(props.receipts, (transaction) => receiptIDs.includes(transaction.transactionID)); + !_.isEmpty(props.iouReport) && isCurrentUserManager && !iouSettled && !props.iouReport.isWaitingOnBankAccount && reportTotal !== 0; return ( @@ -139,18 +150,18 @@ function ReportPreview(props) { {_.map(receipts, ({receipt, filename, transactionID}) => { - const thumbnailURI = ReceiptUtils.getImageURI(`${receipt.source.replace('.jpg', '')}.1024.jpg`, filename); const uri = ReceiptUtils.getImageURI(receipt.source, filename); + const hasNoFallback = uri === receipt.source; return ( - {uri === receipt.source + {hasNoFallback ? diff --git a/src/languages/en.js b/src/languages/en.js index f532512dca1e..810b60d4ec5a 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -379,6 +379,8 @@ export default { pay: 'Pay', viewDetails: 'View details', pending: 'Pending', + receipt: 'Receipt', + receiptScanning: 'Scanning in progress...', settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', settledPaypalMe: 'Paid using Paypal.me', diff --git a/src/languages/es.js b/src/languages/es.js index 4cdfc4e32f14..656723c59ab0 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -378,6 +378,8 @@ export default { pay: 'Pagar', viewDetails: 'Ver detalles', pending: 'Pendiente', + receipt: 'Recibo', + receiptScanning: 'Escaneo en progreso...', settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', settledPaypalMe: 'Pagado con PayPal.me', diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index fdb2e180bba2..9106e9af2be7 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -10,7 +10,7 @@ import ReceiptDoc from '../../assets/images/receipt-doc.png'; import ReceiptGeneric from '../../assets/images/receipt-generic.png'; import ReceiptSVG from '../../assets/images/receipt-svg.png'; -const validateReceipt = (file) => { +function validateReceipt(file) { const {fileExtension} = FileUtils.splitExtensionFromFileName(lodashGet(file, 'name', '')); if (_.contains(CONST.API_ATTACHMENT_VALIDATIONS.UNALLOWED_EXTENSIONS, fileExtension.toLowerCase())) { Receipt.setUploadReceiptError(true, Localize.translateLocal('attachmentPicker.wrongFileType'), Localize.translateLocal('attachmentPicker.notAllowedExtension')); @@ -34,11 +34,11 @@ const validateReceipt = (file) => { /** * Grab the appropriate receipt image URI based on file type * - * @param {String} path URI to image, i.e. blob://new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg - * @param {String} filename of uploaded image or last part of remote URI + * @param {String} path URI to image, i.e. blob://new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg + * @param {String} filename of uploaded image or last part of remote URI * @returns {*} */ -const getImageURI = (path, filename) => { +function getImageURI(path, filename) { const {fileExtension} = FileUtils.splitExtensionFromFileName(filename); const isReceiptImage = Str.isImage(filename); @@ -61,7 +61,12 @@ const getImageURI = (path, filename) => { return ReceiptGeneric; }; +function isBeingScanned(receipt) { + return receipt.state === CONST.IOU.RECEIPT_STATE.SCANREADY || receipt.state === CONST.IOU.RECEIPT_STATE.SCANNING; +} + export { validateReceipt, getImageURI, + isBeingScanned, }; From 4ae0c8960bfc534d346cfca821aaeaaf52f57e75 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 9 Aug 2023 15:51:24 -0400 Subject: [PATCH 012/201] Fix thumbnail and add correct optimistic filename --- src/components/ReportActionItem/ReportPreview.js | 2 +- src/libs/TransactionUtils.js | 3 ++- src/libs/actions/IOU.js | 2 +- src/libs/fileDownload/FileUtils.js | 4 +++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 4e57a806eb93..6e40f32c35a0 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -161,7 +161,7 @@ function ReportPreview(props) { {hasNoFallback ? diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index f88f53467ae8..3e89d0fe87ef 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -15,7 +15,7 @@ import * as NumberUtils from './NumberUtils'; * @param {Object} [receipt] * @returns {Object} */ -function buildOptimisticTransaction(amount, currency, reportID, comment = '', source = '', originalTransactionID = '', merchant = CONST.REPORT.TYPE.IOU, receipt = {}) { +function buildOptimisticTransaction(amount, currency, reportID, comment = '', source = '', originalTransactionID = '', merchant = CONST.REPORT.TYPE.IOU, receipt = {}, filename = '') { // transactionIDs are random, positive, 64-bit numeric strings. // Because JS can only handle 53-bit numbers, transactionIDs are strings in the front-end (just like reportActionID) const transactionID = NumberUtils.rand64(); @@ -38,6 +38,7 @@ function buildOptimisticTransaction(amount, currency, reportID, comment = '', so created: DateUtils.getDBTime(), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, receipt, + filename, }; } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 45fcd35eb839..65a8315f05bf 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -363,7 +363,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part receiptObject.source = receipt.source; receiptObject.state = CONST.IOU.RECEIPT_STATE.SCANREADY; } - const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, iouReport.reportID, comment, '', '', undefined, receiptObject); + const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, iouReport.reportID, comment, '', '', undefined, receiptObject, receipt.filename); // STEP 4: Build optimistic reportActions. We need: // 1. CREATED action for the chatReport diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index 4d714fff4f24..d194e7d6cdeb 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -152,8 +152,10 @@ const readFileAsync = (path, fileName) => return res.blob(); }) .then((blob) => { - const file = new File([blob], cleanFileName(fileName)); + const cleanName = cleanFileName(fileName) + const file = new File([blob], cleanName); file.source = path; + file.filename = cleanName; resolve(file); }) .catch((e) => { From 2e957083b92f20d7bffffbf27a7aee7f8f5579d2 Mon Sep 17 00:00:00 2001 From: Stefan Nemeth Date: Wed, 9 Aug 2023 23:13:33 +0200 Subject: [PATCH 013/201] When openOnAdminRoom is set, do not redirect first time users to another chat --- src/libs/ReportUtils.js | 19 +++++++++--------- src/libs/actions/Welcome.js | 40 ++++++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 8b3a0cacffca..89a7a789f1ee 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -485,11 +485,20 @@ function findLastAccessedReport(reports, ignoreDomainRooms, policies, isFirstTim // since the Concierge report would be incorrectly selected over the deep-linked report in the logic below. let sortedReports = sortReportsByLastRead(reports); + let adminReport; + if (openOnAdminRoom) { + adminReport = _.find(sortedReports, (report) => { + const chatType = getChatType(report); + return chatType === CONST.REPORT.CHAT_TYPE.POLICY_ADMINS; + }); + } + if (isFirstTimeNewExpensifyUser) { if (sortedReports.length === 1) { return sortedReports[0]; } - return _.find(sortedReports, (report) => !isConciergeChatReport(report)); + + return adminReport || _.find(sortedReports, (report) => !isConciergeChatReport(report)); } if (ignoreDomainRooms) { @@ -502,14 +511,6 @@ function findLastAccessedReport(reports, ignoreDomainRooms, policies, isFirstTim ); } - let adminReport; - if (openOnAdminRoom) { - adminReport = _.find(sortedReports, (report) => { - const chatType = getChatType(report); - return chatType === CONST.REPORT.CHAT_TYPE.POLICY_ADMINS; - }); - } - return adminReport || _.last(sortedReports); } diff --git a/src/libs/actions/Welcome.js b/src/libs/actions/Welcome.js index f7063fb61308..e06ee12aa4b3 100644 --- a/src/libs/actions/Welcome.js +++ b/src/libs/actions/Welcome.js @@ -116,25 +116,33 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => {}}) { const isWorkspaceRoute = topRoute.name === 'Settings' && topRoute.params.path.includes('workspace'); const transitionRoute = _.find(routes, (route) => route.name === SCREENS.TRANSITION_BETWEEN_APPS); const exitingToWorkspaceRoute = lodashGet(transitionRoute, 'params.exitTo', '') === 'workspace/new'; + const openOnAdminRoom = lodashGet(topRoute, 'params.openOnAdminRoom', false); const isDisplayingWorkspaceRoute = isWorkspaceRoute || exitingToWorkspaceRoute; - // We want to display the Workspace chat first since that means a user is already in a Workspace and doesn't need to create another one - const workspaceChatReport = _.find( - allReports, - (report) => ReportUtils.isPolicyExpenseChat(report) && report.ownerAccountID === currentUserAccountID && report.statusNum !== CONST.REPORT.STATUS.CLOSED, - ); - if (workspaceChatReport && !isDisplayingWorkspaceRoute) { - // This key is only updated when we call ReconnectApp, setting it to false now allows the user to navigate normally instead of always redirecting to the workspace chat - Onyx.set(ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER, false); - Navigation.navigate(ROUTES.getReportRoute(workspaceChatReport.reportID)); - - // If showPopoverMenu exists and returns true then it opened the Popover Menu successfully, and we can update isFirstTimeNewExpensifyUser - // so the Welcome logic doesn't run again - if (showPopoverMenu()) { - isFirstTimeNewExpensifyUser = false; + // If we already opened the workspace settings or want the admin room to stay open, do not + // navigate away to the workspace chat report + const shouldNavigateToWorkspaceChat = !isDisplayingWorkspaceRoute && !openOnAdminRoom; + + if (shouldNavigateToWorkspaceChat) { + // We want to display the Workspace chat first since that means a user is already in a Workspace and doesn't need to create another one + const workspaceChatReport = _.find( + allReports, + (report) => ReportUtils.isPolicyExpenseChat(report) && report.ownerAccountID === currentUserAccountID && report.statusNum !== CONST.REPORT.STATUS.CLOSED, + ); + + if (workspaceChatReport) { + // This key is only updated when we call ReconnectApp, setting it to false now allows the user to navigate normally instead of always redirecting to the workspace chat + Onyx.set(ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER, false); + Navigation.navigate(ROUTES.getReportRoute(workspaceChatReport.reportID)); + + // If showPopoverMenu exists and returns true then it opened the Popover Menu successfully, and we can update isFirstTimeNewExpensifyUser + // so the Welcome logic doesn't run again + if (showPopoverMenu()) { + isFirstTimeNewExpensifyUser = false; + } + + return; } - - return; } // If user is not already an admin of a free policy and we are not navigating them to their workspace or creating a new workspace via workspace/new then From 5140fede8076c1469f280d44e4d54b7406fe8ce3 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 9 Aug 2023 23:47:51 -0400 Subject: [PATCH 014/201] Fix filename when not present --- src/components/ReportActionItem/ReportPreview.js | 6 +++--- src/libs/actions/IOU.js | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 6e40f32c35a0..7d30900bed62 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -147,8 +147,8 @@ function ReportPreview(props) { accessibilityRole="button" accessibilityLabel={props.translate('iou.viewDetails')} > - - + + {_.map(receipts, ({receipt, filename, transactionID}) => { const uri = ReceiptUtils.getImageURI(receipt.source, filename); const hasNoFallback = uri === receipt.source; @@ -156,7 +156,7 @@ function ReportPreview(props) { return ( {hasNoFallback ? Date: Thu, 10 Aug 2023 00:30:26 -0400 Subject: [PATCH 015/201] Rename IOUPreview to MoneyRequestPreview --- src/ONYXKEYS.js | 2 +- .../ReportActionItem/MoneyRequestAction.js | 10 +++++----- .../{IOUPreview.js => MoneyRequestPreview.js} | 14 +++++++------- src/components/ReportActionItem/ReportPreview.js | 2 +- src/libs/actions/IOU.js | 2 +- src/pages/home/report/ReportActionItem.js | 2 +- src/styles/styles.js | 8 ++++---- 7 files changed, 20 insertions(+), 20 deletions(-) rename src/components/ReportActionItem/{IOUPreview.js => MoneyRequestPreview.js} (96%) diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 4a255538c786..f60ca9f3120f 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -33,7 +33,7 @@ export default { // Credentials to authenticate the user CREDENTIALS: 'credentials', - // Contains loading data for the IOU feature (MoneyRequestModal, IOUDetail, & IOUPreview Components) + // Contains loading data for the IOU feature (MoneyRequestModal, IOUDetail, & MoneyRequestPreview Components) IOU: 'iou', // Keeps track if there is modal currently visible or not diff --git a/src/components/ReportActionItem/MoneyRequestAction.js b/src/components/ReportActionItem/MoneyRequestAction.js index 5790e55b2c78..9bb342a6b735 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.js +++ b/src/components/ReportActionItem/MoneyRequestAction.js @@ -10,7 +10,7 @@ import compose from '../../libs/compose'; import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes'; import networkPropTypes from '../networkPropTypes'; import iouReportPropTypes from '../../pages/iouReportPropTypes'; -import IOUPreview from './IOUPreview'; +import MoneyRequestPreview from './MoneyRequestPreview'; import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; import styles from '../../styles/styles'; @@ -87,7 +87,7 @@ const defaultProps = { function MoneyRequestAction(props) { const isSplitBillAction = lodashGet(props.action, 'originalMessage.type', '') === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; - const onIOUPreviewPressed = () => { + const onMoneyRequestPreviewPressed = () => { if (isSplitBillAction) { const reportActionID = lodashGet(props.action, 'reportActionID', '0'); Navigation.navigate(ROUTES.getSplitBillDetailsRoute(props.chatReportID, reportActionID)); @@ -141,7 +141,7 @@ function MoneyRequestAction(props) { return isDeletedParentAction ? ( ${props.translate('parentReportAction.deletedRequest')}`} /> ) : ( - ); diff --git a/src/components/ReportActionItem/IOUPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js similarity index 96% rename from src/components/ReportActionItem/IOUPreview.js rename to src/components/ReportActionItem/MoneyRequestPreview.js index 85a0b22ac327..5488df581335 100644 --- a/src/components/ReportActionItem/IOUPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -124,7 +124,7 @@ const defaultProps = { shouldShowPendingConversionMessage: false, }; -function IOUPreview(props) { +function MoneyRequestPreview(props) { if (_.isEmpty(props.iouReport) && !props.isBillSplit) { return null; } @@ -186,7 +186,7 @@ function IOUPreview(props) { errorRowStyles={[styles.mbn1]} needsOffscreenAlphaCompositing > - + {getPreviewHeaderText()} @@ -216,7 +216,7 @@ function IOUPreview(props) { )} {props.isBillSplit && ( - + - + {_.map(receipts, ({receipt, filename, transactionID}) => { const uri = ReceiptUtils.getImageURI(receipt.source, filename); diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 0f8ac015d5a1..85022a503071 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -809,7 +809,7 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView // STEP 2: Decide if we need to: // 1. Delete the transactionThread - delete if there are no visible comments in the thread - // 2. Update the iouPreview to show [Deleted request] - update if the transactionThread exists AND it isn't being deleted + // 2. Update the moneyRequestPreview to show [Deleted request] - update if the transactionThread exists AND it isn't being deleted const shouldDeleteTransactionThread = transactionThreadID ? ReportActionsUtils.getLastVisibleMessage(transactionThreadID).lastMessageText.length === 0 : false; const shouldShowDeletedRequestMessage = transactionThreadID && !shouldDeleteTransactionThread; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 05e57c592b9f..b25efda7ebc8 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -238,7 +238,7 @@ function ReportActionItem(props) { // IOUDetails only exists when we are sending money const isSendingMoney = originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && _.has(originalMessage, 'IOUDetails'); - // Show the IOUPreview for when request was created, bill was split or money was sent + // Show the MoneyRequestPreview for when request was created, bill was split or money was sent if ( props.action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && originalMessage && diff --git a/src/styles/styles.js b/src/styles/styles.js index f5b2d883d845..42c75f510435 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2640,7 +2640,7 @@ const styles = { maxWidth: variables.sideBarWidth, }, - iouPreviewBox: { + moneyRequestPreviewBox: { backgroundColor: themeColors.cardBG, borderRadius: variables.componentBorderRadiusLarge, padding: 16, @@ -2648,11 +2648,11 @@ const styles = { width: '100%', }, - iouPreviewBoxHover: { + moneyRequestPreviewBoxHover: { backgroundColor: themeColors.border, }, - iouPreviewBoxLoading: { + moneyRequestPreviewBoxLoading: { // When a new IOU request arrives it is very briefly in a loading state, so set the minimum height of the container to 94 to match the rendered height after loading. // Otherwise, the IOU request pay button will not be fully visible and the user will have to scroll up to reveal the entire IOU request container. // See https://github.com/Expensify/App/issues/10283. @@ -2660,7 +2660,7 @@ const styles = { width: '100%', }, - iouPreviewBoxAvatar: { + moneyRequestPreviewBoxAvatar: { marginRight: -10, marginBottom: 0, }, From 3f7acbeb98382dba26fb5e9c89384c0d1b310c4a Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 10 Aug 2023 14:44:03 -0400 Subject: [PATCH 016/201] Implement whispers --- .../ReportActionItem/ReportPreview.js | 7 +- src/libs/ReportActionsUtils.js | 64 +++++++++++++++++++ src/libs/TransactionUtils.js | 12 +++- src/libs/actions/IOU.js | 2 +- src/pages/home/report/ReportActionItem.js | 2 +- 5 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 6475ddc99b2c..b2f6822d9585 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -27,6 +27,7 @@ import themeColors from '../../styles/themes/default'; import reportPropTypes from '../../pages/reportPropTypes'; import RenderHTML from '../RenderHTML'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; +import * as ReportActionUtils from '../../libs/ReportActionsUtils'; import Image from '../Image'; const propTypes = { @@ -101,8 +102,7 @@ function ReportPreview(props) { const reportTotal = ReportUtils.getMoneyRequestTotal(props.iouReport); const iouSettled = ReportUtils.isSettled(props.iouReportID); - const receiptIDs = lodashGet(props.action, 'childLastReceiptTransactionIDs', '').split(','); - const receipts = _.filter(props.receipts, (transaction) => receiptIDs.includes(transaction.transactionID)); + const receipts = ReportActionUtils.getReportPreviewTransactions(props.action); const isScanning = _.some(receipts, ({receipt}) => ReceiptUtils.isBeingScanned(receipt)); let displayAmount; @@ -223,9 +223,6 @@ export default compose( iouReport: { key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, }, - receipts: { - key: ONYXKEYS.COLLECTION.TRANSACTION, - }, session: { key: ONYXKEYS.SESSION, }, diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 3cd5621e5e32..4fe5f1929fcc 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -10,6 +10,8 @@ import ONYXKEYS from '../ONYXKEYS'; import Log from './Log'; import * as CurrencyUtils from './CurrencyUtils'; import isReportMessageAttachment from './isReportMessageAttachment'; +import * as TransactionUtils from './TransactionUtils'; +import * as ReceiptUtils from './ReceiptUtils'; const allReports = {}; Onyx.connect({ @@ -37,6 +39,19 @@ Onyx.connect({ }, }); +const allTransactions = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + callback: (actions, key) => { + if (!key || !actions) { + return; + } + + const transactionID = CollectionUtils.extractCollectionItemID(key); + allTransactions[transactionID] = actions; + }, +}); + let isNetworkOffline = false; Onyx.connect({ key: ONYXKEYS.NETWORK, @@ -561,6 +576,53 @@ function isMessageDeleted(reportAction) { return lodashGet(reportAction, ['message', 0, 'isDeletedParentAction'], false); } +/** + * Get the transactions related to a report preview + * + * @param {Object} reportPreviewAction + * @returns {Object} + */ +function getReportPreviewTransactions(reportPreviewAction) { + const transactionIDs = lodashGet(reportPreviewAction, ['childLastReceiptTransactionIDs'], '').split(','); + return _.reduce(transactionIDs, (transactions, transactionID) => { + const transaction = allTransactions[transactionID]; + if (transaction) { + transactions.push(transaction); + } + return transactions; + }, []); +} + +/** + * @param {Object} iouReportAction + * @returns {Object} + */ +function getTransaction(iouReportAction) { + const transactionID = lodashGet(iouReportAction, ['originalMessage', 'IOUTransactionID']); + return allTransactions[transactionID] || {}; +} + + +/** + * Checks if the IOU or expense report has either no smartscanned receipts or at least one is already done scanning + * + * @param {Object|null} reportAction + * @returns {Boolean} + */ +function hasReadyMoneyRequests(reportAction) { + if (isReportPreviewAction(reportAction)) { + const transactions = getReportPreviewTransactions(reportAction); + return _.some(transactions, (transaction) => !TransactionUtils.hasReceipt(transaction) || !ReceiptUtils.isBeingScanned(transaction.receipt)); + } + + if (isMoneyRequestAction(reportAction)) { + const transaction = getTransaction(reportAction); + return !TransactionUtils.hasReceipt(transaction) || !ReceiptUtils.isBeingScanned(transaction.receipt); + } + + return true; +} + export { getSortedReportActions, getLastVisibleAction, @@ -593,4 +655,6 @@ export { isWhisperAction, isPendingRemove, getReportAction, + getReportPreviewTransactions, + hasReadyMoneyRequests, }; diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 3e89d0fe87ef..71881236696f 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import CONST from '../CONST'; import DateUtils from './DateUtils'; import * as NumberUtils from './NumberUtils'; @@ -42,6 +43,15 @@ function buildOptimisticTransaction(amount, currency, reportID, comment = '', so }; } -export default { +/** + * @param {Object|null} transaction + * @returns {Boolean} + */ +function hasReceipt(transaction) { + return _.has(transaction, 'receipt'); +} + +export { buildOptimisticTransaction, + hasReceipt, }; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 85022a503071..fda08ab912df 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -16,7 +16,7 @@ import * as ReportActionsUtils from '../ReportActionsUtils'; import * as IOUUtils from '../IOUUtils'; import * as OptionsListUtils from '../OptionsListUtils'; import DateUtils from '../DateUtils'; -import TransactionUtils from '../TransactionUtils'; +import * as TransactionUtils from '../TransactionUtils'; import * as ErrorUtils from '../ErrorUtils'; import * as UserUtils from '../UserUtils'; import * as Report from './Report'; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index b25efda7ebc8..42a9998e0ae1 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -495,7 +495,7 @@ function ReportActionItem(props) { const hasErrors = !_.isEmpty(props.action.errors); const whisperedToAccountIDs = props.action.whisperedToAccountIDs || []; - const isWhisper = whisperedToAccountIDs.length > 0; + const isWhisper = whisperedToAccountIDs.length > 0 || !ReportActionsUtils.hasReadyMoneyRequests(props.action); const isMultipleParticipant = whisperedToAccountIDs.length > 1; const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedToAccountIDs); const whisperedToPersonalDetails = isWhisper ? _.filter(props.personalDetailsList, (details) => _.includes(whisperedToAccountIDs, details.accountID)) : []; From 0f25a34ba31477a72d92d91d45e26f6c79486343 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 10 Aug 2023 15:34:35 -0400 Subject: [PATCH 017/201] Update MoneyRequestPreview for receipts --- .../ReportActionItem/MoneyRequestPreview.js | 140 ++++++++++++------ .../ReportActionItem/ReportPreview.js | 6 +- src/libs/ReportActionsUtils.js | 1 + src/libs/TransactionUtils.js | 2 +- src/styles/styles.js | 19 ++- 5 files changed, 114 insertions(+), 54 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 5488df581335..0932ef47dfb5 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -28,6 +28,11 @@ import * as IOUUtils from '../../libs/IOUUtils'; import * as ReportUtils from '../../libs/ReportUtils'; import refPropTypes from '../refPropTypes'; import PressableWithFeedback from '../Pressable/PressableWithoutFeedback'; +import * as ReportActionUtils from '../../libs/ReportActionsUtils'; +import * as TransactionUtils from '../../libs/TransactionUtils'; +import * as ReceiptUtils from '../../libs/ReceiptUtils'; +import RenderHTML from '../RenderHTML'; +import Image from '../Image'; const propTypes = { /** The active IOUReport, used for Onyx subscription */ @@ -143,6 +148,10 @@ function MoneyRequestPreview(props) { const requestCurrency = moneyRequestAction.currency; const requestComment = moneyRequestAction.comment.trim(); + const transaction = ReportActionUtils.getTransaction(props.action); + const hasReceipt = TransactionUtils.hasReceipt(transaction); + const isScanning = !ReportActionUtils.hasReadyMoneyRequests(props.action); + const getSettledMessage = () => { switch (lodashGet(props.action, 'originalMessage.paymentType', '')) { case CONST.IOU.PAYMENT_TYPE.PAYPAL_ME: @@ -161,6 +170,10 @@ function MoneyRequestPreview(props) { }; const getPreviewHeaderText = () => { + if (isScanning) { + return props.translate('iou.receipt'); + } + if (props.isBillSplit) { return props.translate('iou.split'); } @@ -174,6 +187,32 @@ function MoneyRequestPreview(props) { return message; }; + const getDisplayAmountText = () => { + if (isScanning) { + return props.translate('iou.receiptScanning'); + } + + return CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency); + } + + const renderReceipt = ({receipt, filename}) => { + const uri = ReceiptUtils.getImageURI(receipt.source, filename); + const hasNoFallback = uri === receipt.source; + + if (hasNoFallback) { + return ( + + `} /> + ); + } + return (); + } + const childContainer = ( - - - - {getPreviewHeaderText()} - {Boolean(getSettledMessage()) && ( - <> - - {getSettledMessage()} - - )} + + {hasReceipt && ( + + {renderReceipt(transaction)} - - - - {CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency)} - {ReportUtils.isSettled(props.iouReport.reportID) && !props.isBillSplit && ( - - + + + {getPreviewHeaderText()} + {Boolean(getSettledMessage()) && ( + <> + + {getSettledMessage()} + + )} + + + + + {getDisplayAmountText()} + {ReportUtils.isSettled(props.iouReport.reportID) && !props.isBillSplit && ( + + + + )} + + {props.isBillSplit && ( + + )} - {props.isBillSplit && ( - - + + + {!isCurrentUserManager && props.shouldShowPendingConversionMessage && ( + {props.translate('iou.pendingConversionMessage')} + )} + {!_.isEmpty(requestComment) && {requestComment}} - )} - - - - {!isCurrentUserManager && props.shouldShowPendingConversionMessage && ( - {props.translate('iou.pendingConversionMessage')} + {props.isBillSplit && !_.isEmpty(participantAccountIDs) && ( + + {props.translate('iou.amountEach', { + amount: CurrencyUtils.convertToDisplayString(IOUUtils.calculateAmount(participantAccountIDs.length - 1, requestAmount), requestCurrency), + })} + )} - {!_.isEmpty(requestComment) && {requestComment}} - {props.isBillSplit && !_.isEmpty(participantAccountIDs) && ( - - {props.translate('iou.amountEach', { - amount: CurrencyUtils.convertToDisplayString(IOUUtils.calculateAmount(participantAccountIDs.length - 1, requestAmount), requestCurrency), - })} - - )} diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index b2f6822d9585..a2010ccecf71 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -103,7 +103,7 @@ function ReportPreview(props) { const iouSettled = ReportUtils.isSettled(props.iouReportID); const receipts = ReportActionUtils.getReportPreviewTransactions(props.action); - const isScanning = _.some(receipts, ({receipt}) => ReceiptUtils.isBeingScanned(receipt)); + const isScanning = !ReportActionUtils.hasReadyMoneyRequests(props.action); let displayAmount; if (reportTotal) { @@ -148,7 +148,7 @@ function ReportPreview(props) { accessibilityLabel={props.translate('iou.viewDetails')} > - + {_.map(receipts, ({receipt, filename, transactionID}) => { const uri = ReceiptUtils.getImageURI(receipt.source, filename); const hasNoFallback = uri === receipt.source; @@ -156,7 +156,7 @@ function ReportPreview(props) { return ( {hasNoFallback ? Date: Thu, 10 Aug 2023 17:43:25 -0400 Subject: [PATCH 018/201] Refactor thumbnail/image for receipts --- .../MoneyRequestConfirmationList.js | 2 +- .../ReportActionItem/MoneyRequestPreview.js | 13 +++++----- .../ReportActionItem/ReportPreview.js | 12 ++++------ src/libs/ReceiptUtils.js | 24 +++++++++++-------- src/libs/actions/IOU.js | 2 +- src/libs/fileDownload/FileUtils.js | 1 - src/pages/iou/ReceiptSelector/index.js | 3 +-- 7 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 5b12c8da4052..94b7a53dc795 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -332,7 +332,7 @@ function MoneyRequestConfirmationList(props) { {!_.isEmpty(props.receiptPath) ? ( ) : ( { - const uri = ReceiptUtils.getImageURI(receipt.source, filename); - const hasNoFallback = uri === receipt.source; - - if (hasNoFallback) { + const {thumbnail, image} = ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename); + console.log(thumbnail, image); + if (thumbnail) { return ( `} /> ); } - return (); + return (); } const childContainer = ( diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index a2010ccecf71..0abb6d824f91 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -150,23 +150,21 @@ function ReportPreview(props) { {_.map(receipts, ({receipt, filename, transactionID}) => { - const uri = ReceiptUtils.getImageURI(receipt.source, filename); - const hasNoFallback = uri === receipt.source; - + const {thumbnail, image} = ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename); return ( - {hasNoFallback + {thumbnail ? `} /> - : + : } ); diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index 9106e9af2be7..9dd83ed48e87 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -32,33 +32,37 @@ function validateReceipt(file) { /** - * Grab the appropriate receipt image URI based on file type + * Grab the appropriate receipt image and thumbnail URIs based on file type * * @param {String} path URI to image, i.e. blob://new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg * @param {String} filename of uploaded image or last part of remote URI - * @returns {*} + * @returns {Object} */ -function getImageURI(path, filename) { +function getThumbnailAndImageURIs(path, filename) { + if (path.startsWith('blob://')) { + return {thumbnail: null, image: path}; + } + const {fileExtension} = FileUtils.splitExtensionFromFileName(filename); const isReceiptImage = Str.isImage(filename); if (isReceiptImage) { - return path; + return {thumbnail: `${path}.1024.jpg`, image: path}; } + let image = ReceiptGeneric; if (fileExtension === CONST.IOU.FILE_TYPES.HTML) { - return ReceiptHTML; + image = ReceiptHTML; } if (fileExtension === CONST.IOU.FILE_TYPES.DOC || fileExtension === CONST.IOU.FILE_TYPES.DOCX) { - return ReceiptDoc; + image = ReceiptDoc; } if (fileExtension === CONST.IOU.FILE_TYPES.SVG) { - return ReceiptSVG; + image = ReceiptSVG; } - - return ReceiptGeneric; + return {thumbnail: null, image}; }; function isBeingScanned(receipt) { @@ -67,6 +71,6 @@ function isBeingScanned(receipt) { export { validateReceipt, - getImageURI, + getThumbnailAndImageURIs, isBeingScanned, }; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index fda08ab912df..6b9190c999c9 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -363,7 +363,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part if (receipt && receipt.source) { receiptObject.source = receipt.source; receiptObject.state = CONST.IOU.RECEIPT_STATE.SCANREADY; - filename = receipt.filename; + filename = receipt.name; } const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, iouReport.reportID, comment, '', '', undefined, receiptObject, filename); diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index d194e7d6cdeb..962923241b62 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -155,7 +155,6 @@ const readFileAsync = (path, fileName) => const cleanName = cleanFileName(fileName) const file = new File([blob], cleanName); file.source = path; - file.filename = cleanName; resolve(file); }) .catch((e) => { diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index 392e96887f8b..b693b299eac3 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -99,8 +99,7 @@ function ReceiptSelector(props) { return; } - const filePath = URL.createObjectURL(file); - IOU.setMoneyRequestReceipt(filePath, file.name); + IOU.setMoneyRequestReceipt(file.uri, file.name); IOU.navigateToNextPage(iou, iouType, reportID, report); }; From fb4120ee101b29c28570729df1f4c31a253a7eb9 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 10 Aug 2023 18:32:55 -0400 Subject: [PATCH 019/201] Update request header, disable receipt modal, update image URI blob check --- src/components/ReportActionItem/MoneyRequestPreview.js | 2 +- src/components/ReportActionItem/ReportPreview.js | 1 + src/libs/ReceiptUtils.js | 4 ++-- src/libs/ReportUtils.js | 4 ++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index f1b34ae652b9..7be5a6cbe7d2 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -197,7 +197,6 @@ function MoneyRequestPreview(props) { const renderReceipt = ({receipt, filename}) => { const {thumbnail, image} = ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename); - console.log(thumbnail, image); if (thumbnail) { return ( `} /> ); diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 0abb6d824f91..7676bff77a40 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -162,6 +162,7 @@ function ReportPreview(props) { src="${thumbnail}" data-expensify-source="${image}" data-expensify-fit-container="true" + data-expensify-preview-modal-disabled="true" /> `} /> : diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index 9dd83ed48e87..e973c85f02d8 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -34,12 +34,12 @@ function validateReceipt(file) { /** * Grab the appropriate receipt image and thumbnail URIs based on file type * - * @param {String} path URI to image, i.e. blob://new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg + * @param {String} path URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg * @param {String} filename of uploaded image or last part of remote URI * @returns {Object} */ function getThumbnailAndImageURIs(path, filename) { - if (path.startsWith('blob://')) { + if (path.startsWith('blob:')) { return {thumbnail: null, image: path}; } diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 3fd1236e26ae..bf9f263c6c57 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1259,6 +1259,10 @@ function getTransactionReportName(reportAction) { return Localize.translateLocal('parentReportAction.deletedRequest'); } + if (!ReportActionsUtils.hasReadyMoneyRequests(reportAction)) { + return Localize.translateLocal('iou.receiptScanning'); + } + return Localize.translateLocal(ReportActionsUtils.isSentMoneyReportAction(reportAction) ? 'iou.threadSentMoneyReportName' : 'iou.threadRequestReportName', { formattedAmount: ReportActionsUtils.getFormattedAmount(reportAction), comment: lodashGet(reportAction, 'originalMessage.comment'), From b6280f2e5a7563b59f0ce19c8adedd8948790405 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 10 Aug 2023 18:34:22 -0400 Subject: [PATCH 020/201] Update scan in progress string --- src/languages/en.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.js b/src/languages/en.js index 810b60d4ec5a..f6e445537aeb 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -380,7 +380,7 @@ export default { viewDetails: 'View details', pending: 'Pending', receipt: 'Receipt', - receiptScanning: 'Scanning in progress...', + receiptScanning: 'Scan in progress...', settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', settledPaypalMe: 'Paid using Paypal.me', From a7fe6c8d2dc17c141a6fc6d37ffc1fdbcc34ded3 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 10 Aug 2023 19:10:03 -0400 Subject: [PATCH 021/201] Add receipt in money request view --- .../ReportActionItem/MoneyRequestView.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index dc8916bdaecb..26333b9ca3db 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -20,6 +20,9 @@ import DateUtils from '../../libs/DateUtils'; import * as CurrencyUtils from '../../libs/CurrencyUtils'; import EmptyStateBackgroundImage from '../../../assets/images/empty-state_background-fade.png'; import useLocalize from '../../hooks/useLocalize'; +import * as TransactionUtils from '../../libs/TransactionUtils'; +import * as ReceiptUtils from '../../libs/ReceiptUtils'; +import RenderHTML from '../RenderHTML'; const propTypes = { /** The report currently being looked at */ @@ -49,6 +52,13 @@ function MoneyRequestView(props) { const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const {translate} = useLocalize(); + const transaction = ReportActionsUtils.getTransaction(parentReportAction) + const hasReceipt = TransactionUtils.hasReceipt(transaction); + let receiptUris; + if (hasReceipt) { + receiptUris = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); + } + return ( @@ -58,6 +68,18 @@ function MoneyRequestView(props) { style={[StyleUtils.getReportWelcomeBackgroundImageStyle(true)]} /> + {hasReceipt && ( + + + `} /> + + )} Date: Thu, 10 Aug 2023 20:05:42 -0400 Subject: [PATCH 022/201] Add receipt whisper to details --- .../ReportActionItem/MoneyRequestView.js | 35 +++++++++++++------ .../ReportActionItem/ReportPreview.js | 2 +- src/languages/en.js | 4 ++- src/languages/es.js | 4 ++- src/styles/styles.js | 21 +++++++++++ 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 26333b9ca3db..45187cc092d5 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -23,6 +23,8 @@ import useLocalize from '../../hooks/useLocalize'; import * as TransactionUtils from '../../libs/TransactionUtils'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; import RenderHTML from '../RenderHTML'; +import withLocalize from '../withLocalize'; +import Text from '../Text'; const propTypes = { /** The report currently being looked at */ @@ -69,16 +71,28 @@ function MoneyRequestView(props) { /> {hasReceipt && ( - - - `} /> - + <> + + + `} /> + + + + + {props.translate('iou.receiptWhisperTitle')} + + + {props.translate('iou.receiptWhisperText')} + + + + )} `${ONYXKEYS.COLLECTION.REPORT}${props.report.parentReportID}`, diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 7676bff77a40..d3f4e66bcfeb 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react'; +import React from 'react'; import _ from 'underscore'; import {View} from 'react-native'; import PropTypes from 'prop-types'; diff --git a/src/languages/en.js b/src/languages/en.js index f6e445537aeb..04138cbde8df 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -380,7 +380,9 @@ export default { viewDetails: 'View details', pending: 'Pending', receipt: 'Receipt', - receiptScanning: 'Scan in progress...', + receiptScanning: 'Scan in progress…', + receiptWhisperTitle: 'Receipt scanning…', + receiptWhisperText: 'Only you can see this receipt when it\'s scanning. Check back later or enter the details now.', settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', settledPaypalMe: 'Paid using Paypal.me', diff --git a/src/languages/es.js b/src/languages/es.js index 656723c59ab0..7828acb0e084 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -379,7 +379,9 @@ export default { viewDetails: 'Ver detalles', pending: 'Pendiente', receipt: 'Recibo', - receiptScanning: 'Escaneo en progreso...', + receiptScanning: 'Escaneo en progreso…', + receiptWhisperTitle: 'Escaneo de recibos…', + receiptWhisperText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o ingresa los detalles ahora.', settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', settledPaypalMe: 'Pagado con PayPal.me', diff --git a/src/styles/styles.js b/src/styles/styles.js index c945f7fee990..9ac0559b1105 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3691,6 +3691,27 @@ const styles = { reportPreviewBoxText: { padding: 16, }, + + moneyRequestViewReceipt: { + marginHorizontal: 20, + marginVertical: 8, + borderWidth: 2, + borderColor: themeColors.border, + borderRadius: variables.componentBorderRadiusCard, + overflow: 'hidden', + height: 300, + }, + + moneyRequestViewReceiptWhisper: { + flexDirection: 'row', + marginHorizontal: 20, + marginVertical: 8, + paddingHorizontal: 20, + paddingVertical: 12, + justifyContent: 'space-between', + backgroundColor: themeColors.border, + borderRadius: variables.componentBorderRadiusCard, + } }; export default styles; From 57be23d9ad0aa6ae8d9e994e21bf634e42792376 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 11 Aug 2023 10:41:15 +0700 Subject: [PATCH 023/201] update context menu correctly for thread first chat --- src/pages/home/report/ReportActionItem.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 3c71b8bc62f3..283222692421 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -204,7 +204,7 @@ function ReportActionItem(props) { } setIsContextMenuActive(true); - + const originalReport = ReportUtils.getReport(ReportUtils.getOriginalReportID(props.report.reportID, props.action)); const selection = SelectionScraper.getCurrentSelection(); ReportActionContextMenu.showContextMenu( ContextMenuActions.CONTEXT_MENU_TYPES.REPORT_ACTION, @@ -216,8 +216,8 @@ function ReportActionItem(props) { props.draftMessage, () => {}, toggleContextMenuFromActiveReportAction, - ReportUtils.isArchivedRoom(props.report), - ReportUtils.chatIncludesChronos(props.report), + ReportUtils.isArchivedRoom(originalReport), + ReportUtils.chatIncludesChronos(originalReport), ); }, [props.draftMessage, props.action, props.report, toggleContextMenuFromActiveReportAction], @@ -506,6 +506,7 @@ function ReportActionItem(props) { const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedToAccountIDs); const whisperedToPersonalDetails = isWhisper ? _.filter(props.personalDetailsList, (details) => _.includes(whisperedToAccountIDs, details.accountID)) : []; const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; + const originalReport = ReportUtils.getReport(ReportUtils.getOriginalReportID(props.report.reportID, props.action)); return ( Date: Fri, 11 Aug 2023 19:06:45 +0200 Subject: [PATCH 024/201] refactored DateUtils, timezone fixes, additional tests, removed all usage of moment --- src/CONST.js | 5 + src/components/AutoUpdateTime.js | 14 +- .../ReportActionItem/ChronosOOOListActions.js | 10 +- src/libs/DateUtils.js | 143 ++++++++++++++---- src/pages/home/report/ParticipantLocalTime.js | 13 +- tests/unit/DateUtilsTest.js | 86 ++++++----- 6 files changed, 185 insertions(+), 86 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 56102c7641f7..bfb57a5feaed 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -181,6 +181,11 @@ const CONST = { MOMENT_FORMAT_STRING: 'YYYY-MM-DD', SQL_DATE_TIME: 'YYYY-MM-DD HH:mm:ss', FNS_FORMAT_STRING: 'yyyy-MM-dd', + LOCAL_TIME_FORMAT: 'h:mm a', + WEEKDAY_TIME_FORMAT: 'eeee', + FNS_TIMEZONE_FORMAT_STRING: "yyyy-MM-dd'T'HH:mm:ssXXX", + FNS_DB_FORMAT_STRING: 'yyyy-MM-dd HH:mm:ss.SSS', + LONG_DATE_FORMAT_WITH_WEEKDAY: 'eeee, MMMM d, yyyy', UNIX_EPOCH: '1970-01-01 00:00:00.000', MAX_DATE: '9999-12-31', MIN_DATE: '0001-01-01', diff --git a/src/components/AutoUpdateTime.js b/src/components/AutoUpdateTime.js index a522a3e6dcdc..cb15cb20b4ea 100644 --- a/src/components/AutoUpdateTime.js +++ b/src/components/AutoUpdateTime.js @@ -27,21 +27,13 @@ function AutoUpdateTime(props) { * @returns {moment} Returns the locale moment object */ const getCurrentUserLocalTime = useCallback( - () => DateUtils.getLocalMomentFromDatetime(props.preferredLocale, null, props.timezone.selected), + () => DateUtils.getLocalDateFromDatetime(props.preferredLocale, null, props.timezone.selected), [props.preferredLocale, props.timezone.selected], ); const [currentUserLocalTime, setCurrentUserLocalTime] = useState(getCurrentUserLocalTime); const minuteRef = useRef(new Date().getMinutes()); - const timezoneName = useMemo(() => { - // With non-GMT timezone, moment.zoneAbbr() will return the name of that timezone, so we can use it directly. - if (Number.isNaN(Number(currentUserLocalTime.zoneAbbr()))) { - return currentUserLocalTime.zoneAbbr(); - } - - // With GMT timezone, moment.zoneAbbr() will return a number, so we need to display it as GMT {abbreviations} format, e.g.: GMT +07 - return `GMT ${currentUserLocalTime.zoneAbbr()}`; - }, [currentUserLocalTime]); + const timezoneName = useMemo(() => DateUtils.getZoneAbbreviation(currentUserLocalTime, props.timezone.selected), [currentUserLocalTime, props.timezone.selected]); useEffect(() => { // If the any of the props that getCurrentUserLocalTime depends on change, we want to update the displayed time immediately @@ -68,7 +60,7 @@ function AutoUpdateTime(props) { {props.translate('detailsPage.localTime')} - {currentUserLocalTime.format('LT')} {timezoneName} + {DateUtils.formatToLocalTime(currentUserLocalTime)} {timezoneName} ); diff --git a/src/components/ReportActionItem/ChronosOOOListActions.js b/src/components/ReportActionItem/ChronosOOOListActions.js index 3c9c65d8f254..61c504d122ff 100644 --- a/src/components/ReportActionItem/ChronosOOOListActions.js +++ b/src/components/ReportActionItem/ChronosOOOListActions.js @@ -37,8 +37,8 @@ function ChronosOOOListActions(props) { {_.map(events, (event) => { - const start = DateUtils.getLocalMomentFromDatetime(props.preferredLocale, lodashGet(event, 'start.date', '')); - const end = DateUtils.getLocalMomentFromDatetime(props.preferredLocale, lodashGet(event, 'end.date', '')); + const start = DateUtils.getLocalDateFromDatetime(props.preferredLocale, lodashGet(event, 'start.date', '')); + const end = DateUtils.getLocalDateFromDatetime(props.preferredLocale, lodashGet(event, 'end.date', '')); return (