diff --git a/android/app/build.gradle b/android/app/build.gradle index b62ed8228022..f224d895e2fa 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,8 +91,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001040800 - versionName "1.4.8-0" + versionCode 1001040801 + versionName "1.4.8-1" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 6c66552f2325..7c3fbf13697a 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.8.0 + 1.4.8.1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index d54abec37b98..0d2561b67b74 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.8.0 + 1.4.8.1 diff --git a/package-lock.json b/package-lock.json index 1d6333ad719e..0bc56cb5d907 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.8-0", + "version": "1.4.8-1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.8-0", + "version": "1.4.8-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1453e85fef53..f0b48a32e5e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.8-0", + "version": "1.4.8-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index fc0e2c1348d5..57b0c6466a7f 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -90,9 +90,6 @@ const propTypes = { /** Denotes whether it is a workspace avatar or not */ isWorkspaceAvatar: PropTypes.bool, - - /** Whether it is a receipt attachment or not */ - isReceiptAttachment: PropTypes.bool, }; const defaultProps = { @@ -110,7 +107,6 @@ const defaultProps = { onModalHide: () => {}, onCarouselAttachmentChange: () => {}, isWorkspaceAvatar: false, - isReceiptAttachment: false, }; function AttachmentModal(props) { @@ -122,6 +118,7 @@ function AttachmentModal(props) { const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); const [isDeleteReceiptConfirmModalVisible, setIsDeleteReceiptConfirmModalVisible] = useState(false); const [isAuthTokenRequired, setIsAuthTokenRequired] = useState(props.isAuthTokenRequired); + const [isAttachmentReceipt, setIsAttachmentReceipt] = useState(null); const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState(''); const [attachmentInvalidReason, setAttachmentInvalidReason] = useState(null); const [source, setSource] = useState(props.source); @@ -157,6 +154,7 @@ function AttachmentModal(props) { (attachment) => { setSource(attachment.source); setFile(attachment.file); + setIsAttachmentReceipt(attachment.isReceipt); setIsAuthTokenRequired(attachment.isAuthTokenRequired); onCarouselAttachmentChange(attachment); }, @@ -359,7 +357,7 @@ function AttachmentModal(props) { const sourceForAttachmentView = props.source || source; const threeDotsMenuItems = useMemo(() => { - if (!props.isReceiptAttachment || !props.parentReport || !props.parentReportActions) { + if (!isAttachmentReceipt || !props.parentReport || !props.parentReportActions) { return []; } const menuItems = []; @@ -394,17 +392,17 @@ function AttachmentModal(props) { } return menuItems; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.isReceiptAttachment, props.parentReport, props.parentReportActions, props.policy, props.transaction, file]); + }, [isAttachmentReceipt, props.parentReport, props.parentReportActions, props.policy, props.transaction, file]); // There are a few things that shouldn't be set until we absolutely know if the file is a receipt or an attachment. - // props.isReceiptAttachment will be null until its certain what the file is, in which case it will then be true|false. + // isAttachmentReceipt will be null until its certain what the file is, in which case it will then be true|false. let headerTitle = props.headerTitle; let shouldShowDownloadButton = false; let shouldShowThreeDotsButton = false; - if (!_.isNull(props.isReceiptAttachment)) { - headerTitle = translate(props.isReceiptAttachment ? 'common.receipt' : 'common.attachment'); - shouldShowDownloadButton = props.allowDownload && isDownloadButtonReadyToBeShown && !props.isReceiptAttachment && !isOffline; - shouldShowThreeDotsButton = props.isReceiptAttachment && isModalOpen; + if (!_.isNull(isAttachmentReceipt)) { + headerTitle = translate(isAttachmentReceipt ? 'common.receipt' : 'common.attachment'); + shouldShowDownloadButton = props.allowDownload && isDownloadButtonReadyToBeShown && !isAttachmentReceipt && !isOffline; + shouldShowThreeDotsButton = isAttachmentReceipt && isModalOpen; } return ( @@ -445,7 +443,7 @@ function AttachmentModal(props) { shouldOverlay /> - {!_.isEmpty(props.report) && !props.isReceiptAttachment ? ( + {!_.isEmpty(props.report) ? ( )} - {props.isReceiptAttachment && ( + {isAttachmentReceipt && ( )} - {!props.isReceiptAttachment && ( + {!isAttachmentReceipt && ( { - if (!ReportActionsUtils.shouldReportActionBeVisible(action, key) || ReportActionsUtils.isMoneyRequestAction(action)) { + if (!ReportActionsUtils.shouldReportActionBeVisible(action, key)) { return; } + // We're handling receipts differently here because receipt images are not + // part of the report action message, the images are constructed client-side + if (ReportActionsUtils.isMoneyRequestAction(action)) { + const transactionID = lodashGet(action, ['originalMessage', 'IOUTransactionID']); + if (!transactionID) { + return; + } + + if (TransactionUtils.hasReceipt(transaction)) { + const {image} = ReceiptUtils.getThumbnailAndImageURIs(transaction); + const isLocalFile = typeof image === 'string' && _.some(CONST.ATTACHMENT_LOCAL_URL_PREFIX, (prefix) => image.startsWith(prefix)); + attachments.unshift({ + source: tryResolveUrlFromApiRoot(image), + isAuthTokenRequired: !isLocalFile, + file: {name: transaction.filename}, + isReceipt: true, + transactionID, + }); + return; + } + } + const decision = _.get(action, ['message', 0, 'moderationDecision', 'decision'], ''); const hasBeenFlagged = decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || decision === CONST.MODERATION.MODERATOR_DECISION_HIDDEN; const html = _.get(action, ['message', 0, 'html'], '').replace('/>', `data-flagged="${hasBeenFlagged}" data-id="${action.reportActionID}"/>`); diff --git a/src/components/Attachments/AttachmentCarousel/index.js b/src/components/Attachments/AttachmentCarousel/index.js index 1696f4adf0b4..141e619e489e 100644 --- a/src/components/Attachments/AttachmentCarousel/index.js +++ b/src/components/Attachments/AttachmentCarousel/index.js @@ -1,3 +1,4 @@ +import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {FlatList, Keyboard, PixelRatio, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -27,7 +28,7 @@ const viewabilityConfig = { itemVisiblePercentThreshold: 95, }; -function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, translate}) { +function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, translate, transaction}) { const styles = useThemeStyles(); const scrollRef = useRef(null); @@ -38,12 +39,21 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, const [attachments, setAttachments] = useState([]); const [activeSource, setActiveSource] = useState(source); const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows(); + const [isReceipt, setIsReceipt] = useState(false); - const compareImage = useCallback((attachment) => attachment.source === source, [source]); + const compareImage = useCallback( + (attachment) => { + if (attachment.isReceipt && isReceipt) { + return attachment.transactionID === transaction.transactionID; + } + return attachment.source === source; + }, + [source, isReceipt, transaction], + ); useEffect(() => { const parentReportAction = parentReportActions[report.parentReportActionID]; - const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions); + const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions, transaction); const initialPage = _.findIndex(attachmentsFromReport, compareImage); @@ -78,10 +88,12 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, // to get the index of the current page const entry = _.first(viewableItems); if (!entry) { + setIsReceipt(false); setActiveSource(null); return; } + setIsReceipt(entry.item.isReceipt); setPage(entry.index); setActiveSource(entry.item.source); @@ -229,6 +241,15 @@ export default compose( canEvict: false, }, }), + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file + withOnyx({ + transaction: { + key: ({report, parentReportActions}) => { + const parentReportAction = lodashGet(parentReportActions, [report.parentReportActionID]); + return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportAction, 'originalMessage.IOUTransactionID', 0)}`; + }, + }, + }), withLocalize, withWindowDimensions, )(AttachmentCarousel); diff --git a/src/components/Attachments/AttachmentCarousel/index.native.js b/src/components/Attachments/AttachmentCarousel/index.native.js index 4a62335a492d..6bf4e63c01e7 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.js +++ b/src/components/Attachments/AttachmentCarousel/index.native.js @@ -1,3 +1,4 @@ +import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {Keyboard, PixelRatio, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -17,7 +18,7 @@ import extractAttachmentsFromReport from './extractAttachmentsFromReport'; import AttachmentCarouselPager from './Pager'; import useCarouselArrows from './useCarouselArrows'; -function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, translate, onClose}) { +function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, translate, transaction, onClose}) { const styles = useThemeStyles(); const pagerRef = useRef(null); @@ -27,12 +28,21 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, const [activeSource, setActiveSource] = useState(source); const [isPinchGestureRunning, setIsPinchGestureRunning] = useState(true); const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows(); + const [isReceipt, setIsReceipt] = useState(false); - const compareImage = useCallback((attachment) => attachment.source === source, [source]); + const compareImage = useCallback( + (attachment) => { + if (attachment.isReceipt && isReceipt) { + return attachment.transactionID === transaction.transactionID; + } + return attachment.source === source; + }, + [source, isReceipt, transaction], + ); useEffect(() => { const parentReportAction = parentReportActions[report.parentReportActionID]; - const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions); + const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions, transaction); const initialPage = _.findIndex(attachmentsFromReport, compareImage); @@ -67,7 +77,9 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, const item = attachments[newPageIndex]; setPage(newPageIndex); + setIsReceipt(item.isReceipt); setActiveSource(item.source); + onNavigate(item); }, [setShouldShowArrows, attachments, onNavigate], @@ -174,5 +186,14 @@ export default compose( canEvict: false, }, }), + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file + withOnyx({ + transaction: { + key: ({report, parentReportActions}) => { + const parentReportAction = lodashGet(parentReportActions, [report.parentReportActionID]); + return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportAction, 'originalMessage.IOUTransactionID', 0)}`; + }, + }, + }), withLocalize, )(AttachmentCarousel); diff --git a/src/components/ReportActionItem/ReportActionItemImage.js b/src/components/ReportActionItem/ReportActionItemImage.js index 6181e799ab2a..f0eed3ac2f02 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.js +++ b/src/components/ReportActionItem/ReportActionItemImage.js @@ -2,7 +2,6 @@ import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import _ from 'underscore'; -import AttachmentModal from '@components/AttachmentModal'; import EReceiptThumbnail from '@components/EReceiptThumbnail'; import Image from '@components/Image'; import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus'; @@ -10,10 +9,12 @@ import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import ThumbnailImage from '@components/ThumbnailImage'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; +import Navigation from '@libs/Navigation/Navigation'; import * as TransactionUtils from '@libs/TransactionUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; const propTypes = { /** thumbnail URI for the image */ @@ -46,11 +47,11 @@ const defaultProps = { */ function ReportActionItemImage({thumbnail, image, enablePreviewModal, transaction, isLocalFile}) { + const styles = useThemeStyles(); const {translate} = useLocalize(); const imageSource = tryResolveUrlFromApiRoot(image || ''); const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); const isEReceipt = !_.isEmpty(transaction) && TransactionUtils.hasEReceipt(transaction); - const styles = useThemeStyles(); let receiptImageComponent; @@ -82,25 +83,17 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal, transactio return ( {({report}) => ( - { + const route = ROUTES.REPORT_ATTACHMENTS.getRoute(report.reportID, imageSource); + Navigation.navigate(route); + }} + role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + accessibilityLabel={translate('accessibilityHints.viewAttachment')} > - {({show}) => ( - - {receiptImageComponent} - - )} - + {receiptImageComponent} + )} );