Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/components/DeferredImageWithLoading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import ScreenWrapperStatusContext from '@components/ScreenWrapper/ScreenWrapperStatusContext';

Check failure on line 1 in src/components/DeferredImageWithLoading.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

Unexpected subpath import via alias '@components/ScreenWrapper/ScreenWrapperStatusContext'. Use './ScreenWrapper/ScreenWrapperStatusContext' instead

Check failure on line 1 in src/components/DeferredImageWithLoading.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

Unexpected subpath import via alias '@components/ScreenWrapper/ScreenWrapperStatusContext'. Use './ScreenWrapper/ScreenWrapperStatusContext' instead

import useRunAfterTransitions from '@hooks/useRunAfterTransitions';
import useThemeStyles from '@hooks/useThemeStyles';

import React, {useContext} from 'react';
import {View} from 'react-native';

import type {ImageWithSizeLoadingProps} from './ImageWithLoading';

import ImageWithLoading from './ImageWithLoading';

/**
* Wrapper around ImageWithLoading that keeps the image out of the render passes happening during a screen's entry
* transition: fetching, decoding and laying out an image competes with the animation, so the screen slides in with an
* empty placeholder of the same size and the image mounts once the transition is over.
*
* The gate is the enclosing ScreenWrapper's `didScreenTransitionEnd`, not `useRunAfterTransitions(true)` alone: an
* incoming screen's subtree mounts (and runs its effects) *before* React Navigation emits `transitionStart`, so at that
* point TransitionTracker has no active transition and would let the image through immediately.
*
* On a screen that has already settled - a receipt arriving in an open chat - `didScreenTransitionEnd` is already true,
* so the image mounts on the very next render with no perceptible delay.
*/
function DeferredImageWithLoading({containerStyles, onLayout, ...rest}: ImageWithSizeLoadingProps) {
const styles = useThemeStyles();

// Read the context directly rather than through `useScreenWrapperTransitionStatus`, which throws outside a
// ScreenWrapper. There is no screen entry transition to wait for in that case, so don't hold the image back.
const screenWrapperStatus = useContext(ScreenWrapperStatusContext);
const didScreenTransitionEnd = screenWrapperStatus?.didScreenTransitionEnd ?? true;

// Also wait out any transition still tracked once the screen has settled, e.g. an overlapping modal or keyboard one.
const shouldRenderImage = useRunAfterTransitions(didScreenTransitionEnd);

if (!shouldRenderImage) {
return (
<View
style={[styles.w100, styles.h100, containerStyles]}
onLayout={onLayout}
/>
);
}

return (
<ImageWithLoading
containerStyles={containerStyles}
onLayout={onLayout}
{...rest}
/>
);
}

export default DeferredImageWithLoading;
1 change: 1 addition & 0 deletions src/components/ImageWithLoading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,4 @@ function ImageWithLoading({
ImageWithLoading.displayName = 'ImageWithLoading';

export default React.memo(ImageWithLoading);
export type {ImageWithSizeLoadingProps};
4 changes: 2 additions & 2 deletions src/components/ImageWithSizeCalculation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import React, {useMemo} from 'react';
import type {FullScreenLoadingIndicatorIconSize} from './FullscreenLoadingIndicator';
import type {ImageObjectPosition} from './Image/types';

import DeferredImageWithLoading from './DeferredImageWithLoading';
import RESIZE_MODES from './Image/resizeModes';
import ImageWithLoading from './ImageWithLoading';

type OnMeasure = (args: {width: number; height: number}) => void;

Expand Down Expand Up @@ -95,7 +95,7 @@ function ImageWithSizeCalculation({
};

return (
<ImageWithLoading
<DeferredImageWithLoading
containerStyles={[styles.w100, styles.h100, style]}
style={[styles.w100, styles.h100]}
source={source}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ReceiptImage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import DeferredImageWithLoading from '@components/DeferredImageWithLoading';
import EReceiptStaticThumbnail from '@components/EReceiptStaticThumbnail';
import EReceiptThumbnail from '@components/EReceiptThumbnail';
import type {IconSize} from '@components/EReceiptThumbnail';
import EReceiptWithSizeCalculation from '@components/EReceiptWithSizeCalculation';
import type {FullScreenLoadingIndicatorIconSize} from '@components/FullscreenLoadingIndicator';
import ImageWithLoading from '@components/ImageWithLoading';
import ReceiptEmptyState from '@components/ReceiptEmptyState';
import LocalPDFReceiptPreview from '@components/ReportActionItem/LocalPDFReceiptPreview';
import type {TransactionListItemType} from '@components/Search/SearchList/ListItem/types';
Expand Down Expand Up @@ -256,7 +256,7 @@ function ReceiptImage({
}

return (
<ImageWithLoading
<DeferredImageWithLoading
onLayout={(e) => {
if (e.nativeEvent.layout.width !== receiptImageWidth && e.timeStamp - lastUpdateWidthTimestampRef.current > MIN_UPDATE_WIDTH_DIFF) {
setReceiptImageWidth(e.nativeEvent.layout.width);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@
const effectiveImage = localSource != null && typeof image === 'string' ? localSource : image;

const originalImageSource = tryResolveUrlFromApiRoot(effectiveImage ?? '');
const thumbnailSource = tryResolveUrlFromApiRoot(effectiveThumbnail ?? '');

Check failure on line 181 in src/components/ReportActionItem/ReportActionItemImage.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

'thumbnailSource' is assigned a value but never used

Check failure on line 181 in src/components/ReportActionItem/ReportActionItemImage.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

'thumbnailSource' is assigned a value but never used
const previewUriSource = effectivePreviewUri ? tryResolveUrlFromApiRoot(effectivePreviewUri) : undefined;
const isEReceipt = transaction && !hasReceiptSource(transaction) && hasEReceipt(transaction);
const isPDF = filename && Str.isPDF(filename);
Expand All @@ -191,7 +191,7 @@
propsObj = {
shouldUseThumbnailImage: shouldUseThumbnailImage ?? true,

source: thumbnailSource,
source: originalImageSource,
fallbackIcon: icons.Receipt,
fallbackIconSize: isSingleImage ? variables.iconSizeSuperLarge : variables.iconSizeExtraLarge,
isAuthTokenRequired: true,
Expand Down
Loading