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}
+
)}
);