Skip to content

Commit

Permalink
Merge pull request #52848 from gijoe0295/gijoe/52638
Browse files Browse the repository at this point in the history
Feat: Add placeholder thumbnail to expenses with no receipt
  • Loading branch information
jasperhuangg authored Dec 3, 2024
2 parents ab3bc24 + 874c2e0 commit 1ead788
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 35 deletions.
17 changes: 17 additions & 0 deletions assets/images/receipt-placeholder-plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/components/Icon/Expensicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ import Printer from '@assets/images/printer.svg';
import Profile from '@assets/images/profile.svg';
import QrCode from '@assets/images/qrcode.svg';
import QuestionMark from '@assets/images/question-mark-circle.svg';
import ReceiptPlaceholderPlus from '@assets/images/receipt-placeholder-plus.svg';
import ReceiptPlus from '@assets/images/receipt-plus.svg';
import ReceiptScan from '@assets/images/receipt-scan.svg';
import ReceiptSearch from '@assets/images/receipt-search.svg';
Expand Down Expand Up @@ -343,6 +344,7 @@ export {
QrCode,
QuestionMark,
Receipt,
ReceiptPlaceholderPlus,
ReceiptPlus,
ReceiptScan,
ReceiptSlash,
Expand Down
36 changes: 29 additions & 7 deletions src/components/ReceiptEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import Icon from './Icon';
Expand All @@ -14,12 +16,15 @@ type ReceiptEmptyStateProps = {
onPress?: () => void;

disabled?: boolean;

isThumbnail?: boolean;
};

// Returns an SVG icon indicating that the user should attach a receipt
function ReceiptEmptyState({hasError = false, onPress = () => {}, disabled = false}: ReceiptEmptyStateProps) {
function ReceiptEmptyState({hasError = false, onPress = () => {}, disabled = false, isThumbnail = false}: ReceiptEmptyStateProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const theme = useTheme();

return (
<PressableWithoutFeedback
Expand All @@ -28,13 +33,30 @@ function ReceiptEmptyState({hasError = false, onPress = () => {}, disabled = fal
onPress={onPress}
disabled={disabled}
disabledStyle={styles.cursorDefault}
style={[styles.alignItemsCenter, styles.justifyContentCenter, styles.moneyRequestViewImage, styles.moneyRequestAttachReceipt, hasError && styles.borderColorDanger]}
style={[
styles.alignItemsCenter,
styles.justifyContentCenter,
styles.moneyRequestViewImage,
isThumbnail ? styles.moneyRequestAttachReceiptThumbnail : styles.moneyRequestAttachReceipt,
hasError && styles.borderColorDanger,
]}
>
<Icon
src={Expensicons.EmptyStateAttachReceipt}
width={variables.eReceiptEmptyIconWidth}
height={variables.eReceiptIconHeight}
/>
<View>
<Icon
fill={theme.border}
src={Expensicons.Receipt}
width={variables.eReceiptEmptyIconWidth}
height={variables.eReceiptEmptyIconWidth}
/>
{!isThumbnail && (
<Icon
src={Expensicons.ReceiptPlaceholderPlus}
width={variables.avatarSizeSmall}
height={variables.avatarSizeSmall}
additionalStyles={styles.moneyRequestAttachReceiptThumbnailIcon}
/>
)}
</View>
</PressableWithoutFeedback>
);
}
Expand Down
18 changes: 18 additions & 0 deletions src/components/ReceiptImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import EReceiptThumbnail from './EReceiptThumbnail';
import type {IconSize} from './EReceiptThumbnail';
import Image from './Image';
import PDFThumbnail from './PDFThumbnail';
import ReceiptEmptyState from './ReceiptEmptyState';
import ThumbnailImage from './ThumbnailImage';

type Style = {height: number; borderRadius: number; margin: number};
Expand Down Expand Up @@ -79,6 +80,11 @@ type ReceiptImageProps = (

/** The background color of fallback icon */
fallbackIconBackground?: string;

isEmptyReceipt?: boolean;

/** Callback to be called on pressing the image */
onPress?: () => void;
};

function ReceiptImage({
Expand All @@ -97,9 +103,21 @@ function ReceiptImage({
shouldUseInitialObjectPosition = false,
fallbackIconColor,
fallbackIconBackground,
isEmptyReceipt = false,
onPress,
}: ReceiptImageProps) {
const styles = useThemeStyles();

if (isEmptyReceipt) {
return (
<ReceiptEmptyState
isThumbnail
onPress={onPress}
disabled={!onPress}
/>
);
}

if (isPDFThumbnail) {
return (
<PDFThumbnail
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ function MoneyRequestPreviewContent({
merchantOrDescription = description || '';
}

const receiptImages = hasReceipt ? [{...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction}] : [];
const receiptImages = [{...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction}];

const getSettledMessage = (): string => {
if (isCardTransaction) {
Expand Down Expand Up @@ -326,6 +326,8 @@ function MoneyRequestPreviewContent({
}
};

const shouldDisableOnPress = isBillSplit && isEmptyObject(transaction);

const childContainer = (
<View>
<OfflineWithFeedback
Expand All @@ -346,17 +348,16 @@ function MoneyRequestPreviewContent({
!onPreviewPressed ? [styles.moneyRequestPreviewBox, containerStyles] : {},
]}
>
{hasReceipt && (
<ReportActionItemImages
images={receiptImages}
isHovered={isHovered || isScanning}
size={1}
/>
)}
<ReportActionItemImages
images={receiptImages}
isHovered={isHovered || isScanning}
size={1}
onPress={shouldDisableOnPress ? undefined : onPreviewPressed}
/>
{isEmptyObject(transaction) && !ReportActionsUtils.isMessageDeleted(action) && action.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? (
<MoneyRequestSkeletonView />
) : (
<View style={[styles.expenseAndReportPreviewBoxBody, hasReceipt ? styles.mtn1 : {}]}>
<View style={[styles.expenseAndReportPreviewBoxBody, styles.mtn1]}>
<View style={styles.expenseAndReportPreviewTextButtonContainer}>
<View style={styles.expenseAndReportPreviewTextContainer}>
<View style={[styles.flexRow]}>
Expand Down Expand Up @@ -484,8 +485,6 @@ function MoneyRequestPreviewContent({
return childContainer;
}

const shouldDisableOnPress = isBillSplit && isEmptyObject(transaction);

return (
<PressableWithoutFeedback
onPress={shouldDisableOnPress ? undefined : onPreviewPressed}
Expand Down
9 changes: 9 additions & 0 deletions src/components/ReportActionItem/ReportActionItemImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type ReportActionItemImageProps = {
/** thumbnail URI for the image */
thumbnail?: string;

isEmptyReceipt?: boolean;

/** The file type of the receipt */
fileExtension?: string;

Expand Down Expand Up @@ -58,6 +60,9 @@ type ReportActionItemImageProps = {

/** whether or not this report is from review duplicates */
isFromReviewDuplicates?: boolean;

/** Callback to be called on pressing the image */
onPress?: () => void;
};

/**
Expand All @@ -73,12 +78,14 @@ function ReportActionItemImage({
enablePreviewModal = false,
transaction,
isLocalFile = false,
isEmptyReceipt = false,
fileExtension,
filename,
isSingleImage = true,
readonly = false,
shouldMapHaveBorderRadius,
isFromReviewDuplicates = false,
onPress,
}: ReportActionItemImageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
Expand Down Expand Up @@ -128,6 +135,8 @@ function ReportActionItemImage({
isAuthTokenRequired: false,
source: thumbnail ?? image ?? '',
shouldUseInitialObjectPosition: isDistanceRequest,
isEmptyReceipt,
onPress,
};
}

Expand Down
9 changes: 7 additions & 2 deletions src/components/ReportActionItem/ReportActionItemImages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ type ReportActionItemImagesProps = {

/** if the corresponding report action item is hovered */
isHovered?: boolean;

/** Callback to be called on onPress */
onPress?: () => void;
};

/**
Expand All @@ -38,7 +41,7 @@ type ReportActionItemImagesProps = {
* additional number when subtracted from size.
*/

function ReportActionItemImages({images, size, total, isHovered = false}: ReportActionItemImagesProps) {
function ReportActionItemImages({images, size, total, isHovered = false, onPress}: ReportActionItemImagesProps) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
Expand Down Expand Up @@ -67,7 +70,7 @@ function ReportActionItemImages({images, size, total, isHovered = false}: Report
<View style={styles.reportActionItemImagesContainer}>
<View style={[styles.reportActionItemImages, hoverStyle, heightStyle]}>
<ImageBehaviorContextProvider shouldSetAspectRatioInStyle={false}>
{shownImages.map(({thumbnail, isThumbnail, image, transaction, isLocalFile, fileExtension, filename}, index) => {
{shownImages.map(({thumbnail, isThumbnail, image, isEmptyReceipt, transaction, isLocalFile, fileExtension, filename}, index) => {
// Show a border to separate multiple images. Shown to the right for each except the last.
const shouldShowBorder = shownImages.length > 1 && index < shownImages.length - 1;
const borderStyle = shouldShowBorder ? styles.reportActionItemImageBorder : {};
Expand All @@ -81,11 +84,13 @@ function ReportActionItemImages({images, size, total, isHovered = false}: Report
fileExtension={fileExtension}
image={image}
isLocalFile={isLocalFile}
isEmptyReceipt={isEmptyReceipt}
filename={filename}
transaction={transaction}
isThumbnail={isThumbnail}
isSingleImage={numberOfShownImages === 1}
shouldMapHaveBorderRadius={false}
onPress={onPress}
/>
</View>
);
Expand Down
29 changes: 15 additions & 14 deletions src/components/ReportActionItem/ReportPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ function ReportPreview({
ReportUtils.hasWarningTypeViolations(iouReportID, transactionViolations, true) ||
(ReportUtils.isReportOwner(iouReport) && ReportUtils.hasReportViolations(iouReportID)) ||
ReportUtils.hasActionsWithErrors(iouReportID);
const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3);
const lastThreeReceipts = lastThreeTransactionsWithReceipts.map((transaction) => ({...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction}));
const lastThreeTransactions = allTransactions.slice(-3);
const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction}));
const showRTERViolationMessage =
numberOfRequests === 1 &&
TransactionUtils.hasPendingUI(allTransactions.at(0), TransactionUtils.getTransactionViolations(allTransactions.at(0)?.transactionID ?? '-1', transactionViolations));
Expand Down Expand Up @@ -448,6 +448,12 @@ function ReportPreview({
checkMarkScale.set(isPaidAnimationRunning ? withDelay(CONST.ANIMATION_PAID_CHECKMARK_DELAY, withSpring(1, {duration: CONST.ANIMATION_PAID_DURATION})) : 1);
}, [isPaidAnimationRunning, iouSettled, checkMarkScale]);

const openReportFromPreview = useCallback(() => {
Performance.markStart(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW);
Timing.start(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW);
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(iouReportID));
}, [iouReportID]);

return (
<OfflineWithFeedback
pendingAction={iouReport?.pendingFields?.preview}
Expand All @@ -456,11 +462,7 @@ function ReportPreview({
>
<View style={[styles.chatItemMessage, containerStyles]}>
<PressableWithoutFeedback
onPress={() => {
Performance.markStart(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW);
Timing.start(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW);
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(iouReportID));
}}
onPress={openReportFromPreview}
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)}
Expand All @@ -470,13 +472,12 @@ function ReportPreview({
accessibilityLabel={translate('iou.viewDetails')}
>
<View style={[styles.reportPreviewBox, isHovered || isScanning || isWhisper ? styles.reportPreviewBoxHoverBorder : undefined]}>
{hasReceipts && (
<ReportActionItemImages
images={lastThreeReceipts}
total={transactionsWithReceipts.length}
size={CONST.RECEIPT.MAX_REPORT_PREVIEW_RECEIPTS}
/>
)}
<ReportActionItemImages
images={lastThreeReceipts}
total={allTransactions.length}
size={CONST.RECEIPT.MAX_REPORT_PREVIEW_RECEIPTS}
onPress={openReportFromPreview}
/>
<View style={[styles.expenseAndReportPreviewBoxBody, hasReceipts ? styles.mtn1 : {}]}>
<View style={shouldShowSettlementButton ? {} : styles.expenseAndReportPreviewTextButtonContainer}>
<View style={styles.expenseAndReportPreviewTextContainer}>
Expand Down
4 changes: 4 additions & 0 deletions src/libs/ReceiptUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type ThumbnailAndImageURI = {
isThumbnail?: boolean;
filename?: string;
fileExtension?: string;
isEmptyReceipt?: boolean;
};

/**
Expand All @@ -26,6 +27,9 @@ type ThumbnailAndImageURI = {
* @param receiptFileName
*/
function getThumbnailAndImageURIs(transaction: OnyxEntry<Transaction>, receiptPath: ReceiptSource | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI {
if (!TransactionUtils.hasReceipt(transaction) && !receiptPath && !receiptFileName) {
return {isEmptyReceipt: true};
}
if (TransactionUtils.isFetchingWaypointsFromServer(transaction)) {
return {isThumbnail: true, isLocalFile: true};
}
Expand Down
15 changes: 15 additions & 0 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4647,6 +4647,21 @@ const styles = (theme: ThemeColors) =>
borderWidth: 1,
},

moneyRequestAttachReceiptThumbnail: {
backgroundColor: theme.hoverComponentBG,
width: '100%',
borderWidth: 0,
},

moneyRequestAttachReceiptThumbnailIcon: {
position: 'absolute',
bottom: -4,
right: -4,
borderColor: theme.highlightBG,
borderWidth: 2,
borderRadius: '50%',
},

mapViewContainer: {
...flex.flex1,
minHeight: 300,
Expand Down
2 changes: 1 addition & 1 deletion src/styles/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export default {
eReceiptThumbnailCenterReceiptBreakpoint: 200,
eReceiptIconHeight: 100,
eReceiptIconWidth: 72,
eReceiptEmptyIconWidth: 76,
eReceiptEmptyIconWidth: 64,
eReceiptMCCHeightWidth: 40,
eReceiptIconHeightSmall: 65,
eReceiptIconWidthSmall: 46,
Expand Down

0 comments on commit 1ead788

Please sign in to comment.