Skip to content

Commit d600d30

Browse files
authored
Merge pull request Expensify#83795 from nkdengineer/fix/83709
fix crop image regression
2 parents 62535a4 + 060bdb6 commit d600d30

2 files changed

Lines changed: 48 additions & 13 deletions

File tree

src/components/ReceiptCropView/index.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {LayoutChangeEvent} from 'react-native';
33
import {View} from 'react-native';
44
import {Gesture, GestureDetector, GestureHandlerRootView} from 'react-native-gesture-handler';
55
import type {GestureUpdateEvent, PanGestureChangeEventPayload, PanGestureHandlerEventPayload} from 'react-native-gesture-handler';
6+
import ImageSize from 'react-native-image-size';
67
import Animated, {useAnimatedStyle, useSharedValue} from 'react-native-reanimated';
78
import Image from '@components/Image';
89
import RESIZE_MODES from '@components/Image/resizeModes';
@@ -58,6 +59,7 @@ function ReceiptCropView({imageUri, onCropChange, initialCrop, isAuthTokenRequir
5859
const cropY = useSharedValue(0);
5960
const cropWidth = useSharedValue(0);
6061
const cropHeight = useSharedValue(0);
62+
const isImageInitialized = useRef(false);
6163

6264
// Track previous values to detect changes and recalculate crop on resize
6365
const prevDisplayValuesRef = useRef<{
@@ -71,6 +73,25 @@ function ReceiptCropView({imageUri, onCropChange, initialCrop, isAuthTokenRequir
7173

7274
const isCropInitialized = imageSize.width > 0 && imageSize.height > 0 && containerSize.width > 0 && containerSize.height > 0;
7375

76+
// Use react-native-image-size to get actual original image dimensions (not downsampled display dimensions).
77+
// On Android, expo-image's onLoad reports Glide-downsampled dimensions, which would make crop coordinates
78+
// wrong for expo-image-manipulator (which operates on full-resolution images). See AvatarCropModal for the same pattern.
79+
useEffect(() => {
80+
if (!imageUri || isAuthTokenRequired) {
81+
return;
82+
}
83+
ImageSize.getSize(imageUri).then(({width, height, rotation: originalRotation}) => {
84+
// On Android, ImageSize returns rotation; when image is rotated 90/270°, swap width/height for layout.
85+
if (originalRotation === 90 || originalRotation === 270) {
86+
setImageSize({width: height, height: width});
87+
} else {
88+
setImageSize({width, height});
89+
}
90+
91+
isImageInitialized.current = true;
92+
});
93+
}, [imageUri, isAuthTokenRequired]);
94+
7495
// Calculate scale factors to convert display coordinates to image coordinates
7596
const {scaleX, scaleY, displayWidth, displayHeight, imageOffsetX, imageOffsetY} = useMemo(() => {
7697
if (!containerSize.width || !containerSize.height || !imageSize.width || !imageSize.height) {
@@ -244,7 +265,9 @@ function ReceiptCropView({imageUri, onCropChange, initialCrop, isAuthTokenRequir
244265
if (!hasImageDimensions) {
245266
setHasImageDimensions(true);
246267
}
247-
setImageSize({width, height});
268+
if (!isImageInitialized.current) {
269+
setImageSize({width, height});
270+
}
248271
},
249272
[hasImageDimensions, imageSize.width, imageSize.height],
250273
);

src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {openReport} from '@libs/actions/Report';
1717
import cropOrRotateImage from '@libs/cropOrRotateImage';
1818
import fetchImage from '@libs/fetchImage';
1919
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
20+
import getPlatform from '@libs/getPlatform';
2021
import Navigation from '@libs/Navigation/Navigation';
2122
import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils';
2223
import {getReportAction, isTrackExpenseAction} from '@libs/ReportActionsUtils';
@@ -50,6 +51,8 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre
5051
const [session] = useOnyx(ONYXKEYS.SESSION);
5152
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
5253
const policy = usePolicy(report?.policyID);
54+
const platform = getPlatform();
55+
const isNative = platform === CONST.PLATFORM.ANDROID || platform === CONST.PLATFORM.IOS;
5356

5457
// If we have a merge transaction, we need to use the receipt from the merge transaction
5558
const [mergeTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.MERGE_TRANSACTION}${getNonEmptyStringOnyxID(mergeTransactionID)}`);
@@ -503,19 +506,27 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre
503506
<Button
504507
icon={expensifyIcons.Camera}
505508
onPress={() => {
509+
const getDestinationRoute = () => {
510+
return isOdometerImage
511+
? ROUTES.ODOMETER_IMAGE.getRoute(action ?? CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, imageType)
512+
: ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(
513+
action ?? CONST.IOU.ACTION.EDIT,
514+
iouType,
515+
draftTransactionID ?? transaction?.transactionID,
516+
report?.reportID,
517+
Navigation.getActiveRoute(),
518+
);
519+
};
520+
if (isNative) {
521+
Navigation.goBack();
522+
Navigation.setNavigationActionToMicrotaskQueue(() => {
523+
Navigation.navigate(getDestinationRoute());
524+
});
525+
return;
526+
}
527+
506528
Navigation.dismissModal({
507-
callback: () =>
508-
Navigation.navigate(
509-
isOdometerImage
510-
? ROUTES.ODOMETER_IMAGE.getRoute(action ?? CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, imageType)
511-
: ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(
512-
action ?? CONST.IOU.ACTION.EDIT,
513-
iouType,
514-
draftTransactionID ?? transaction?.transactionID,
515-
report?.reportID,
516-
Navigation.getActiveRoute(),
517-
),
518-
),
529+
callback: () => Navigation.navigate(getDestinationRoute()),
519530
});
520531
}}
521532
text={translate('common.replace')}
@@ -556,6 +567,7 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre
556567
draftTransactionID,
557568
transaction?.transactionID,
558569
report?.reportID,
570+
isNative,
559571
]);
560572

561573
const customAttachmentContent = useMemo(() => {

0 commit comments

Comments
 (0)