Skip to content

Commit

Permalink
Merge pull request #34843 from FitseTLT/fix-update-to-use-of-ReceiptB…
Browse files Browse the repository at this point in the history
…ackground

Transactions - Update Generic Receipts to use ReceiptBackground
  • Loading branch information
dangrous authored Mar 26, 2024
2 parents 78e8d27 + ab837e1 commit b202825
Show file tree
Hide file tree
Showing 13 changed files with 273 additions and 112 deletions.
12 changes: 5 additions & 7 deletions src/components/DistanceEReceipt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import ImageSVG from './ImageSVG';
import PendingMapView from './MapView/PendingMapView';
import ReceiptImage from './ReceiptImage';
import ScrollView from './ScrollView';
import Text from './Text';
import ThumbnailImage from './ThumbnailImage';

type DistanceEReceiptProps = {
/** The transaction for the distance request */
Expand All @@ -30,7 +30,7 @@ function DistanceEReceipt({transaction}: DistanceEReceiptProps) {
const thumbnail = TransactionUtils.hasReceipt(transaction) ? ReceiptUtils.getThumbnailAndImageURIs(transaction).thumbnail : null;
const {amount: transactionAmount, currency: transactionCurrency, merchant: transactionMerchant, created: transactionDate} = ReportUtils.getTransactionDetails(transaction) ?? {};
const formattedTransactionAmount = CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency);
const thumbnailSource = tryResolveUrlFromApiRoot((thumbnail as string) || '');
const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail ?? '');
const waypoints = useMemo(() => transaction?.comment?.waypoints ?? {}, [transaction?.comment?.waypoints]);
const sortedWaypoints = useMemo<WaypointCollection>(
() =>
Expand Down Expand Up @@ -58,11 +58,9 @@ function DistanceEReceipt({transaction}: DistanceEReceiptProps) {
{TransactionUtils.isFetchingWaypointsFromServer(transaction) || !thumbnailSource ? (
<PendingMapView />
) : (
<ThumbnailImage
previewSourceURL={thumbnailSource}
style={[styles.w100, styles.h100]}
isAuthTokenRequired
shouldDynamicallyResize={false}
<ReceiptImage
source={thumbnailSource}
shouldUseThumbnailImage
/>
)}
</View>
Expand Down
43 changes: 37 additions & 6 deletions src/components/EReceiptThumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {withOnyx} from 'react-native-onyx';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import * as ReportUtils from '@libs/ReportUtils';
import colors from '@styles/theme/colors';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -14,6 +15,7 @@ import * as eReceiptBGs from './Icon/EReceiptBGs';
import * as Expensicons from './Icon/Expensicons';
import * as MCCIcons from './Icon/MCCIcons';
import Image from './Image';
import Text from './Text';

type EReceiptThumbnailOnyxProps = {
transaction: OnyxEntry<Transaction>;
Expand All @@ -26,6 +28,15 @@ type EReceiptThumbnailProps = EReceiptThumbnailOnyxProps & {
// eslint-disable-next-line react/no-unused-prop-types
transactionID: string;

/** Border radius to be applied on the parent view. */
borderRadius?: number;

/** The file extension of the receipt that the preview thumbnail is being displayed for. */
fileExtension?: string;

/** Whether it is a receipt thumbnail we are displaying. */
isReceiptThumbnail?: boolean;

/** Center the eReceipt Icon vertically */
centerIconV?: boolean;

Expand All @@ -42,13 +53,14 @@ const backgroundImages = {
[CONST.ERECEIPT_COLORS.PINK]: eReceiptBGs.EReceiptBG_Pink,
};

function EReceiptThumbnail({transaction, centerIconV = true, iconSize = 'large'}: EReceiptThumbnailProps) {
function EReceiptThumbnail({transaction, borderRadius, fileExtension, isReceiptThumbnail = false, centerIconV = true, iconSize = 'large'}: EReceiptThumbnailProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const colorCode = isReceiptThumbnail ? StyleUtils.getFileExtensionColorCode(fileExtension) : StyleUtils.getEReceiptColorCode(transaction);

const backgroundImage = useMemo(() => backgroundImages[StyleUtils.getEReceiptColorCode(transaction)], [StyleUtils, transaction]);
const backgroundImage = useMemo(() => backgroundImages[colorCode], [colorCode]);

const colorStyles = StyleUtils.getEReceiptColorStyles(StyleUtils.getEReceiptColorCode(transaction));
const colorStyles = StyleUtils.getEReceiptColorStyles(colorCode);
const primaryColor = colorStyles?.backgroundColor;
const secondaryColor = colorStyles?.color;
const transactionDetails = ReportUtils.getTransactionDetails(transaction);
Expand All @@ -58,15 +70,21 @@ function EReceiptThumbnail({transaction, centerIconV = true, iconSize = 'large'}
let receiptIconWidth: number = variables.eReceiptIconWidth;
let receiptIconHeight: number = variables.eReceiptIconHeight;
let receiptMCCSize: number = variables.eReceiptMCCHeightWidth;
let labelFontSize: number = variables.fontSizeNormal;
let labelLineHeight: number = variables.lineHeightLarge;

if (iconSize === 'small') {
receiptIconWidth = variables.eReceiptIconWidthSmall;
receiptIconHeight = variables.eReceiptIconHeightSmall;
receiptMCCSize = variables.eReceiptMCCHeightWidthSmall;
labelFontSize = variables.fontSizeExtraSmall;
labelLineHeight = variables.lineHeightXSmall;
} else if (iconSize === 'medium') {
receiptIconWidth = variables.eReceiptIconWidthMedium;
receiptIconHeight = variables.eReceiptIconHeightMedium;
receiptMCCSize = variables.eReceiptMCCHeightWidthMedium;
labelFontSize = variables.fontSizeLabel;
labelLineHeight = variables.lineHeightNormal;
}

return (
Expand All @@ -77,6 +95,7 @@ function EReceiptThumbnail({transaction, centerIconV = true, iconSize = 'large'}
styles.overflowHidden,
styles.alignItemsCenter,
centerIconV ? styles.justifyContentCenter : {},
borderRadius ? {borderRadius} : {},
]}
>
<Image
Expand All @@ -93,7 +112,20 @@ function EReceiptThumbnail({transaction, centerIconV = true, iconSize = 'large'}
fill={secondaryColor}
additionalStyles={[styles.fullScreen]}
/>
{MCCIcon ? (
{isReceiptThumbnail && fileExtension && (
<Text
selectable={false}
style={[
styles.labelStrong,
StyleUtils.getFontSizeStyle(labelFontSize),
StyleUtils.getLineHeightStyle(labelLineHeight),
StyleUtils.getTextColorStyle(primaryColor ?? colors.black),
]}
>
{fileExtension.toUpperCase()}
</Text>
)}
{MCCIcon && !isReceiptThumbnail ? (
<Icon
src={MCCIcon}
height={receiptMCCSize}
Expand All @@ -108,10 +140,9 @@ function EReceiptThumbnail({transaction, centerIconV = true, iconSize = 'large'}
}

EReceiptThumbnail.displayName = 'EReceiptThumbnail';

export default withOnyx<EReceiptThumbnailProps, EReceiptThumbnailOnyxProps>({
transaction: {
key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
},
})(EReceiptThumbnail);
export type {EReceiptThumbnailProps, EReceiptThumbnailOnyxProps};
export type {IconSize, EReceiptThumbnailProps, EReceiptThumbnailOnyxProps};
19 changes: 12 additions & 7 deletions src/components/MoneyRequestConfirmationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ import ButtonWithDropdownMenu from './ButtonWithDropdownMenu';
import type {DropdownOption} from './ButtonWithDropdownMenu/types';
import ConfirmedRoute from './ConfirmedRoute';
import FormHelpMessage from './FormHelpMessage';
import Image from './Image';
import MenuItemWithTopDescription from './MenuItemWithTopDescription';
import OptionsSelector from './OptionsSelector';
import ReceiptEmptyState from './ReceiptEmptyState';
import ReceiptImage from './ReceiptImage';
import SettlementButton from './SettlementButton';
import ShowMoreButton from './ShowMoreButton';
import Switch from './Switch';
Expand Down Expand Up @@ -577,8 +577,12 @@ function MoneyRequestConfirmationList({
);
}, [isReadOnly, iouType, bankAccountRoute, iouCurrencyCode, policyID, selectedParticipants.length, confirm, splitOrRequestOptions, formError, styles.ph1, styles.mb2]);

const {image: receiptImage, thumbnail: receiptThumbnail} =
receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI);
const {
image: receiptImage,
thumbnail: receiptThumbnail,
isThumbnail,
fileExtension,
} = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : ({} as ReceiptUtils.ThumbnailAndImageURI);
return (
// @ts-expect-error This component is deprecated and will not be migrated to TypeScript (context: https://expensify.slack.com/archives/C01GTK53T8Q/p1709232289899589?thread_ts=1709156803.359359&cid=C01GTK53T8Q)
<OptionsSelector
Expand All @@ -604,16 +608,17 @@ function MoneyRequestConfirmationList({
<ConfirmedRoute transaction={transaction} />
</View>
)}

{/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */}
{receiptImage || receiptThumbnail ? (
<Image
<ReceiptImage
style={styles.moneyRequestImage}
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
source={{uri: String(receiptThumbnail || receiptImage)}}
isThumbnail={isThumbnail}
source={String(receiptThumbnail ?? receiptImage)}
// AuthToken is required when retrieving the image from the server
// but we don't need it to load the blob:// or file:// image when starting a money request / split bill
// So if we have a thumbnail, it means we're retrieving the image from the server
isAuthTokenRequired={!!receiptThumbnail}
fileExtension={fileExtension}
/>
) : (
// The empty receipt component should only show for IOU Requests of a paid policy ("Team" or "Corporate")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ import ConfirmedRoute from './ConfirmedRoute';
import ConfirmModal from './ConfirmModal';
import FormHelpMessage from './FormHelpMessage';
import * as Expensicons from './Icon/Expensicons';
import Image from './Image';
import MenuItemWithTopDescription from './MenuItemWithTopDescription';
import optionPropTypes from './optionPropTypes';
import OptionsSelector from './OptionsSelector';
import PDFThumbnail from './PDFThumbnail';
import ReceiptEmptyState from './ReceiptEmptyState';
import ReceiptImage from './ReceiptImage';
import SettlementButton from './SettlementButton';
import Switch from './Switch';
import tagPropTypes from './tagPropTypes';
Expand Down Expand Up @@ -897,6 +897,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
const {
image: receiptImage,
thumbnail: receiptThumbnail,
isThumbnail,
fileExtension,
isLocalFile,
} = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : {};

Expand All @@ -911,16 +913,18 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
onPassword={() => setIsAttachmentInvalid(true)}
/>
) : (
<Image
<ReceiptImage
style={styles.moneyRequestImage}
source={{uri: receiptThumbnail || receiptImage}}
isThumbnail={isThumbnail}
source={receiptThumbnail || receiptImage}
// AuthToken is required when retrieving the image from the server
// but we don't need it to load the blob:// or file:// image when starting a money request / split bill
// So if we have a thumbnail, it means we're retrieving the image from the server
isAuthTokenRequired={!_.isEmpty(receiptThumbnail)}
fileExtension={fileExtension}
/>
),
[receiptFilename, receiptImage, styles, receiptThumbnail, isLocalFile, isAttachmentInvalid],
[isLocalFile, receiptFilename, receiptImage, styles.moneyRequestImage, isAttachmentInvalid, isThumbnail, receiptThumbnail, fileExtension],
);

return (
Expand Down
136 changes: 136 additions & 0 deletions src/components/ReceiptImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React from 'react';
import {View} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import type IconAsset from '@src/types/utils/IconAsset';
import EReceiptThumbnail from './EReceiptThumbnail';
import type {IconSize} from './EReceiptThumbnail';
import Image from './Image';
import PDFThumbnail from './PDFThumbnail';
import ThumbnailImage from './ThumbnailImage';

type Style = {height: number; borderRadius: number; margin: number};

type ReceiptImageProps = (
| {
/** Transaction ID of the transaction the receipt belongs to */
transactionID: string;

/** Whether it is EReceipt */
isEReceipt: boolean;

/** Whether it is receipt preview thumbnail we are displaying */
isThumbnail?: boolean;

/** Url of the receipt image */
source?: string;

/** Whether it is a pdf thumbnail we are displaying */
isPDFThumbnail?: boolean;
}
| {
transactionID?: string;
isEReceipt?: boolean;
isThumbnail: boolean;
source?: string;
isPDFThumbnail?: boolean;
}
| {
transactionID?: string;
isEReceipt?: boolean;
isThumbnail?: boolean;
source: string;
isPDFThumbnail?: boolean;
}
| {
transactionID?: string;
isEReceipt?: boolean;
isThumbnail?: boolean;
source: string;
isPDFThumbnail: string;
}
) & {
/** Whether we should display the receipt with ThumbnailImage component */
shouldUseThumbnailImage?: boolean;

/** Whether the receipt image requires an authToken */
isAuthTokenRequired?: boolean;

/** Any additional styles to apply */
style?: Style;

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

/** number of images displayed in the same parent container */
iconSize?: IconSize;

/** If the image fails to load – show the provided fallback icon */
fallbackIcon?: IconAsset;

/** The size of the fallback icon */
fallbackIconSize?: number;
};

function ReceiptImage({
transactionID,
isPDFThumbnail = false,
isThumbnail = false,
shouldUseThumbnailImage = false,
isEReceipt = false,
source,
isAuthTokenRequired,
style,
fileExtension,
iconSize,
fallbackIcon,
fallbackIconSize,
}: ReceiptImageProps) {
const styles = useThemeStyles();

if (isPDFThumbnail) {
return (
<PDFThumbnail
previewSourceURL={source ?? ''}
style={[styles.w100, styles.h100]}
/>
);
}

if (isEReceipt || isThumbnail) {
const props = isThumbnail && {borderRadius: style?.borderRadius, fileExtension, isReceiptThumbnail: true};
return (
<View style={style ?? [styles.w100, styles.h100]}>
<EReceiptThumbnail
transactionID={transactionID ?? ''}
iconSize={iconSize}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
</View>
);
}

if (shouldUseThumbnailImage) {
return (
<ThumbnailImage
previewSourceURL={source ?? ''}
style={[styles.w100, styles.h100]}
isAuthTokenRequired
shouldDynamicallyResize={false}
fallbackIcon={fallbackIcon}
fallbackIconSize={fallbackIconSize}
/>
);
}

return (
<Image
source={{uri: source}}
style={style ?? [styles.w100, styles.h100]}
isAuthTokenRequired={isAuthTokenRequired}
/>
);
}

export type {ReceiptImageProps};
export default ReceiptImage;
2 changes: 2 additions & 0 deletions src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ function MoneyRequestView({
) : (
<ReportActionItemImage
thumbnail={receiptURIs?.thumbnail}
fileExtension={receiptURIs?.fileExtension}
isThumbnail={receiptURIs?.isThumbnail}
image={receiptURIs?.image}
isLocalFile={receiptURIs?.isLocalFile}
filename={receiptURIs?.filename}
Expand Down
Loading

0 comments on commit b202825

Please sign in to comment.