diff --git a/app/fragments/utils/ScannerFragment.tsx b/app/fragments/utils/ScannerFragment.tsx index ed863d9c0..b5456be98 100644 --- a/app/fragments/utils/ScannerFragment.tsx +++ b/app/fragments/utils/ScannerFragment.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, useRef } from 'react'; import { Text, View, StyleSheet, Pressable, Platform, Linking, Alert, Image } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import * as Application from 'expo-application'; @@ -17,6 +17,7 @@ import { BarCodeScanner, BarCodeScannerResult } from 'expo-barcode-scanner'; import { Camera, FlashMode } from 'expo-camera'; import { useTheme } from '../../engine/hooks'; import { Typography } from '../../components/styles'; +import { useCameraAspectRatio } from '../../utils/useCameraAspectRatio'; import FlashOn from '../../../assets/ic-flash-on.svg'; import FlashOff from '../../../assets/ic-flash-off.svg'; @@ -38,6 +39,21 @@ export const ScannerFragment = systemFragment(() => { const [isActive, setActive] = useState(true); const [flashOn, setFlashOn] = useState(false); + const cameraRef = useRef(null); + + // Screen Ratio and image padding for Android + // The issue arises from the discrepancy between the camera preview's aspect ratio and the screen's aspect ratio. + // Possible causes: + // 1. Different camera manufacturers support different aspect ratios. + // 2. Different phone manufacturers design screens with varying aspect ratios. + const { ratio, imagePadding, prepareRatio } = useCameraAspectRatio(); + + const onCameraReady = useCallback(() => { + if (!!cameraRef.current) { + prepareRatio(cameraRef.current); + } + }, []); + const onReadFromMedia = useCallback(async () => { try { const { status } = await RNImagePicker.requestMediaLibraryPermissionsAsync() @@ -163,9 +179,15 @@ export const ScannerFragment = systemFragment(() => { @@ -191,12 +213,17 @@ export const ScannerFragment = systemFragment(() => { - + { return { opacity: props.pressed ? 0.5 : 1, @@ -233,7 +260,13 @@ export const ScannerFragment = systemFragment(() => { { setActive(false); setTimeout(() => { diff --git a/app/utils/useCameraAspectRatio.ts b/app/utils/useCameraAspectRatio.ts new file mode 100644 index 000000000..aaaf1b42a --- /dev/null +++ b/app/utils/useCameraAspectRatio.ts @@ -0,0 +1,54 @@ +import { Camera } from "expo-camera"; +import { useState } from "react"; +import { Dimensions, Platform } from "react-native"; + +export function useCameraAspectRatio() { + // Screen Ratio and image padding for Android + const [previewSettings, setPreviewSettings] = useState<{ + imagePadding: number; + ratio: string | undefined; + }>({ imagePadding: 0, ratio: undefined }); + const { height, width } = Dimensions.get('window'); + const screenRatio = height / width; + + // set the camera ratio and padding (portrait mode) + const prepareRatio = async (camera: Camera) => { + let desiredRatio = '4:3'; // Start with the system default + // This issue only affects Android + if (Platform.OS === 'android') { + const ratios = await camera.getSupportedRatiosAsync(); + + // Calculate the width/height of each of the supported camera ratios + // These width/height are measured in landscape mode + // find the ratio that is closest to the screen ratio without going over + let distances: { [key: string]: number } = {}; + let realRatios: { [key: string]: number } = {}; + let minDistance: string | null = null; + for (const ratio of (ratios ?? [])) { + const parts = ratio.split(':'); + const realRatio = parseInt(parts[0]) / parseInt(parts[1]); + realRatios[ratio] = realRatio; + // ratio can't be taller than screen, so we don't want an abs() + const distance = screenRatio - realRatio; + distances[ratio] = distance; + if (minDistance == null) { + minDistance = ratio; + } else { + if (distance >= 0 && distance < distances[minDistance]) { + minDistance = ratio; + } + } + } + // set the best match + desiredRatio = minDistance ?? '4:3'; + // calculate the diff between the camera width and the screen height + const remainder = Math.floor( + (height - realRatios[desiredRatio] * width) / 2 + ); + // set padding and ratio + setPreviewSettings({ imagePadding: remainder, ratio: desiredRatio }); + } + }; + + return { ratio: previewSettings.ratio, imagePadding: previewSettings.imagePadding, prepareRatio }; +} \ No newline at end of file