Skip to content

Commit b72b9b7

Browse files
authored
Merge pull request #90774 from callstack-internal/reduce-photo-resolution-and-ios-snapshot
2 parents 2a6d403 + f5acc57 commit b72b9b7

6 files changed

Lines changed: 30 additions & 41 deletions

File tree

src/CONST/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,8 @@ const CONST = {
321321
},
322322

323323
RECEIPT_CAMERA: {
324-
PHOTO_WIDTH: 4032,
325-
PHOTO_HEIGHT: 3024,
324+
PHOTO_WIDTH: 2880,
325+
PHOTO_HEIGHT: 2160,
326326
PHOTO_ASPECT_RATIO: 4 / 3,
327327
// Limit how long the shutter handler waits for thumbnail pre-generation before navigating
328328
// to the confirmation screen. Past this, we navigate and let the confirm-side hook

src/pages/iou/request/step/IOURequestStepScan/captureReceipt/index.android.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/pages/iou/request/step/IOURequestStepScan/captureReceipt/index.ios.ts

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import type {CaptureReceipt} from './types';
22

3-
const captureReceipt: CaptureReceipt = (camera, {flash, hasFlash, isPlatformMuted, path}) => {
4-
return camera.takePhoto({
5-
flash: flash && hasFlash ? 'on' : 'off',
6-
enableShutterSound: !isPlatformMuted,
7-
path,
8-
});
3+
const captureReceipt: CaptureReceipt = (camera, {flash, hasFlash, isPlatformMuted, path, isInLandscapeMode}) => {
4+
const useFlash = flash && hasFlash;
5+
if (useFlash || isInLandscapeMode) {
6+
return camera.takePhoto({
7+
flash: useFlash ? 'on' : 'off',
8+
enableShutterSound: !isPlatformMuted,
9+
path,
10+
});
11+
}
12+
13+
return camera.takeSnapshot({quality: 85, path});
914
};
1015

1116
export default captureReceipt;

src/pages/iou/request/step/IOURequestStepScan/components/Camera/index.native.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, {useRef} from 'react';
2-
import {Alert, View} from 'react-native';
2+
import {Alert, Platform, View} from 'react-native';
33
import {RESULTS} from 'react-native-permissions';
44
import {useAnimatedStyle, useSharedValue, withSequence, withTiming} from 'react-native-reanimated';
55
import type {PhotoFile} from 'react-native-vision-camera';
@@ -74,14 +74,19 @@ function Camera({onCapture, onPicked, shouldAcceptMultipleFiles = false, onLayou
7474
cameraLoadingReasonAttributes,
7575
} = useNativeCamera({context: 'Camera', onFocusStart, onFocusCleanup});
7676

77-
// Prioritize photoResolution over videoResolution so the format selector picks a 4032x3024
78-
// format instead of the 5712x4284 (24.5MP) format that videoResolution:'max' would select.
79-
// This cuts capture time roughly in half while maintaining the same output photo resolution.
80-
// Use screen dimensions for video resolution since we only need enough for the preview.
77+
// Prioritize photoResolution so the format selector picks the configured PHOTO_WIDTH/PHOTO_HEIGHT
78+
// format. videoResolution is platform-specific:
79+
// - iOS: match the photo target — `takeSnapshot` reads from the video pipeline, so a smaller
80+
// video resolution would degrade the snapshot capture quality.
81+
// - Android: keep screen dimensions — `takeSnapshot` is a GPU screenshot of the preview surface
82+
// and doesn't depend on video resolution; constraining to screen size avoids burning GPU on a
83+
// higher-than-needed preview.
8184
const format = useCameraFormat(device, [
8285
{photoAspectRatio: CONST.RECEIPT_CAMERA.PHOTO_ASPECT_RATIO},
8386
{photoResolution: {width: CONST.RECEIPT_CAMERA.PHOTO_WIDTH, height: CONST.RECEIPT_CAMERA.PHOTO_HEIGHT}},
84-
{videoResolution: {width: windowHeight, height: windowWidth}},
87+
Platform.OS === 'ios'
88+
? {videoResolution: {width: CONST.RECEIPT_CAMERA.PHOTO_WIDTH, height: CONST.RECEIPT_CAMERA.PHOTO_HEIGHT}}
89+
: {videoResolution: {width: windowHeight, height: windowWidth}},
8590
]);
8691
const cameraAspectRatio = getCameraAspectRatio(format, isInLandscapeMode);
8792
const fps = format ? Math.min(Math.max(30, format.minFps), format.maxFps) : 30;

src/pages/iou/request/step/IOURequestStepScan/components/NavigationAwareCamera/Camera.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import {useIsFocused} from '@react-navigation/native';
22
import React from 'react';
3+
import {Platform} from 'react-native';
34
import {Camera as VisionCamera} from 'react-native-vision-camera';
45
import type {NavigationAwareCameraNativeProps} from './types';
56

7+
// iOS `takeSnapshot` requires the video pipeline to be running so it can grab a frame from it.
8+
// Android's `takeSnapshot` is a GPU screenshot of the preview view and doesn't need this.
9+
const IS_VIDEO_REQUIRED_FOR_SNAPSHOT = Platform.OS === 'ios';
10+
611
// Wraps a camera that will only be active when the tab is focused or as soon as it starts to become focused.
712
function Camera({cameraTabIndex, ref, forceInactive = false, ...props}: NavigationAwareCameraNativeProps) {
813
const isFocused = useIsFocused();
@@ -12,6 +17,7 @@ function Camera({cameraTabIndex, ref, forceInactive = false, ...props}: Navigati
1217
<VisionCamera
1318
ref={ref}
1419
photoQualityBalance="quality"
20+
video={IS_VIDEO_REQUIRED_FOR_SNAPSHOT}
1521
{...props}
1622
isActive={isCameraActive}
1723
/>

0 commit comments

Comments
 (0)