diff --git a/src/screens/CameraScreen.tsx b/src/screens/CameraScreen.tsx index f94d110f..337ff715 100644 --- a/src/screens/CameraScreen.tsx +++ b/src/screens/CameraScreen.tsx @@ -94,6 +94,7 @@ export default function CameraScreen({ const usbCameraRef = useRef(null); const expoCameraRef = useRef(null); const burstTimerRef = useRef(null); + const countdownTimerRef = useRef(null); const isCancelledRef = useRef(false); // Reanimated countdown pulse animation @@ -159,11 +160,12 @@ export default function CameraScreen({ useEffect(() => { return () => { if (burstTimerRef.current) clearTimeout(burstTimerRef.current); + if (countdownTimerRef.current) clearTimeout(countdownTimerRef.current); }; }, []); // Set safe picture size to avoid OOM - const handleCameraReady = async () => { + const handleCameraReady = useCallback(async () => { if (expoCameraRef.current) { try { const sizes = await expoCameraRef.current.getAvailablePictureSizes(); @@ -188,18 +190,24 @@ export default function CameraScreen({ setPictureSize(optimal.size); } else if (parsedSizes.length > 0) { setPictureSize(parsedSizes[parsedSizes.length - 1].size); + } else { + setPictureSize('fallback'); } + } else { + setPictureSize('fallback'); } } catch (e) { console.warn('Failed to fetch picture sizes:', e); + setPictureSize('fallback'); } } - }; + }, []); // Cancel capture process const handleLocalCancel = async () => { isCancelledRef.current = true; if (burstTimerRef.current) clearTimeout(burstTimerRef.current); + if (countdownTimerRef.current) clearTimeout(countdownTimerRef.current); // Clean up any temp files from a partially completed burst for (const uri of burstUris) { @@ -229,14 +237,20 @@ export default function CameraScreen({ return; } + // Delay start until safe picture size is negotiated (or fallback is set) + // Only applies to built-in camera, USB camera ignores pictureSize + if (pictureSize === undefined && !isUsbConnected) { + console.log('Waiting for pictureSize negotiation...'); + return; + } + startCountdown(); return () => {}; - }, [countdownDuration, isIdle]); + }, [countdownDuration, isIdle, pictureSize, isUsbConnected]); const startCountdown = () => { isCancelledRef.current = false; - let timer: any; let count = countdownDuration; setCountdown(count); @@ -248,20 +262,17 @@ export default function CameraScreen({ if (count > 1) { count -= 1; setCountdown(count); - timer = setTimeout(runTimer, 1000); + countdownTimerRef.current = setTimeout(runTimer, 1000); } else if (count === 1) { setCountdown('smile'); setIsCapturing(true); - timer = setTimeout(() => { + countdownTimerRef.current = setTimeout(() => { capture(); }, 800); } }; - timer = setTimeout(runTimer, 1000); - - // Store timer for cleanup - burstTimerRef.current = timer; + countdownTimerRef.current = setTimeout(runTimer, 1000); }; // 4. Capture photo function @@ -552,7 +563,7 @@ export default function CameraScreen({ ref={expoCameraRef} style={styles.cameraPreview} facing="front" - pictureSize={pictureSize} + pictureSize={pictureSize === 'fallback' ? undefined : pictureSize} onCameraReady={handleCameraReady} /> )} diff --git a/src/screens/PreviewScreen.tsx b/src/screens/PreviewScreen.tsx index 1e388f29..8aa39944 100644 --- a/src/screens/PreviewScreen.tsx +++ b/src/screens/PreviewScreen.tsx @@ -3,11 +3,11 @@ import { StyleSheet, Text, View, + Image, TouchableOpacity, ActivityIndicator, } from 'react-native'; import ViewShot from 'react-native-view-shot'; -import { Image } from 'expo-image'; import { LinearGradient } from 'expo-linear-gradient'; import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'; import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome'; @@ -326,11 +326,12 @@ export default function PreviewScreen({ {photoUris.slice(-3).map((uri, idx) => ( logger.log(`Image loaded successfully: ${uri}`)} - onError={(e) => logger.error(`Image load failed: ${uri}`, e)} + onError={(e) => logger.error(`Image load failed: ${uri}`, e.nativeEvent?.error || e)} /> ))} @@ -347,11 +348,12 @@ export default function PreviewScreen({ {photoUris.slice(-4).map((uri, idx) => ( logger.log(`Grid Image loaded successfully: ${uri}`)} - onError={(e) => logger.error(`Grid Image load failed: ${uri}`, e)} + onError={(e) => logger.error(`Grid Image load failed: ${uri}`, e.nativeEvent?.error || e)} /> ))} @@ -367,11 +369,12 @@ export default function PreviewScreen({ {photoUris.slice(-2).map((uri, idx) => ( logger.log(`Duo Image loaded successfully: ${uri}`)} - onError={(e) => logger.error(`Duo Image load failed: ${uri}`, e)} + onError={(e) => logger.error(`Duo Image load failed: ${uri}`, e.nativeEvent?.error || e)} /> ))} @@ -384,11 +387,12 @@ export default function PreviewScreen({ return ( logger.log(`Single Image loaded successfully: ${currentPhoto}`)} - onError={(e) => logger.error(`Single Image load failed: ${currentPhoto}`, e)} + onError={(e) => logger.error(`Single Image load failed: ${currentPhoto}`, e.nativeEvent?.error || e)} /> );