Compare commits

...
2 Commits
4 changed files with 64 additions and 26 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+22 -11
View File
@@ -94,6 +94,7 @@ export default function CameraScreen({
const usbCameraRef = useRef<UsbCameraRef>(null); const usbCameraRef = useRef<UsbCameraRef>(null);
const expoCameraRef = useRef<any>(null); const expoCameraRef = useRef<any>(null);
const burstTimerRef = useRef<any>(null); const burstTimerRef = useRef<any>(null);
const countdownTimerRef = useRef<any>(null);
const isCancelledRef = useRef<boolean>(false); const isCancelledRef = useRef<boolean>(false);
// Reanimated countdown pulse animation // Reanimated countdown pulse animation
@@ -159,11 +160,12 @@ export default function CameraScreen({
useEffect(() => { useEffect(() => {
return () => { return () => {
if (burstTimerRef.current) clearTimeout(burstTimerRef.current); if (burstTimerRef.current) clearTimeout(burstTimerRef.current);
if (countdownTimerRef.current) clearTimeout(countdownTimerRef.current);
}; };
}, []); }, []);
// Set safe picture size to avoid OOM // Set safe picture size to avoid OOM
const handleCameraReady = async () => { const handleCameraReady = useCallback(async () => {
if (expoCameraRef.current) { if (expoCameraRef.current) {
try { try {
const sizes = await expoCameraRef.current.getAvailablePictureSizes(); const sizes = await expoCameraRef.current.getAvailablePictureSizes();
@@ -188,18 +190,24 @@ export default function CameraScreen({
setPictureSize(optimal.size); setPictureSize(optimal.size);
} else if (parsedSizes.length > 0) { } else if (parsedSizes.length > 0) {
setPictureSize(parsedSizes[parsedSizes.length - 1].size); setPictureSize(parsedSizes[parsedSizes.length - 1].size);
} else {
setPictureSize('fallback');
} }
} else {
setPictureSize('fallback');
} }
} catch (e) { } catch (e) {
console.warn('Failed to fetch picture sizes:', e); console.warn('Failed to fetch picture sizes:', e);
setPictureSize('fallback');
} }
} }
}; }, []);
// Cancel capture process // Cancel capture process
const handleLocalCancel = async () => { const handleLocalCancel = async () => {
isCancelledRef.current = true; isCancelledRef.current = true;
if (burstTimerRef.current) clearTimeout(burstTimerRef.current); if (burstTimerRef.current) clearTimeout(burstTimerRef.current);
if (countdownTimerRef.current) clearTimeout(countdownTimerRef.current);
// Clean up any temp files from a partially completed burst // Clean up any temp files from a partially completed burst
for (const uri of burstUris) { for (const uri of burstUris) {
@@ -229,14 +237,20 @@ export default function CameraScreen({
return; 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(); startCountdown();
return () => {}; return () => {};
}, [countdownDuration, isIdle]); }, [countdownDuration, isIdle, pictureSize, isUsbConnected]);
const startCountdown = () => { const startCountdown = () => {
isCancelledRef.current = false; isCancelledRef.current = false;
let timer: any;
let count = countdownDuration; let count = countdownDuration;
setCountdown(count); setCountdown(count);
@@ -248,20 +262,17 @@ export default function CameraScreen({
if (count > 1) { if (count > 1) {
count -= 1; count -= 1;
setCountdown(count); setCountdown(count);
timer = setTimeout(runTimer, 1000); countdownTimerRef.current = setTimeout(runTimer, 1000);
} else if (count === 1) { } else if (count === 1) {
setCountdown('smile'); setCountdown('smile');
setIsCapturing(true); setIsCapturing(true);
timer = setTimeout(() => { countdownTimerRef.current = setTimeout(() => {
capture(); capture();
}, 800); }, 800);
} }
}; };
timer = setTimeout(runTimer, 1000); countdownTimerRef.current = setTimeout(runTimer, 1000);
// Store timer for cleanup
burstTimerRef.current = timer;
}; };
// 4. Capture photo function // 4. Capture photo function
@@ -552,7 +563,7 @@ export default function CameraScreen({
ref={expoCameraRef} ref={expoCameraRef}
style={styles.cameraPreview} style={styles.cameraPreview}
facing="front" facing="front"
pictureSize={pictureSize} pictureSize={pictureSize === 'fallback' ? undefined : pictureSize}
onCameraReady={handleCameraReady} onCameraReady={handleCameraReady}
/> />
)} )}
+42 -15
View File
@@ -3,11 +3,11 @@ import {
StyleSheet, StyleSheet,
Text, Text,
View, View,
Image,
TouchableOpacity, TouchableOpacity,
ActivityIndicator, ActivityIndicator,
} from 'react-native'; } from 'react-native';
import ViewShot from 'react-native-view-shot'; import ViewShot from 'react-native-view-shot';
import { Image } from 'expo-image';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'; import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
@@ -72,6 +72,14 @@ export default function PreviewScreen({
// Sticker state // Sticker state
const [stickers, setStickers] = useState<StickerData[]>([]); const [stickers, setStickers] = useState<StickerData[]>([]);
const [selectedImageIndex, setSelectedImageIndex] = useState<number | null>(null);
const mountedRef = useRef(true);
useEffect(() => {
return () => {
mountedRef.current = false;
};
}, []);
const [selectedStickerId, setSelectedStickerId] = useState<string | null>(null); const [selectedStickerId, setSelectedStickerId] = useState<string | null>(null);
const [showStickerTray, setShowStickerTray] = useState<boolean>(false); const [showStickerTray, setShowStickerTray] = useState<boolean>(false);
@@ -214,6 +222,7 @@ export default function PreviewScreen({
try { try {
setStatusMessage('Bild wird generiert...'); setStatusMessage('Bild wird generiert...');
const printUri = await captureComposite(currentPhoto); const printUri = await captureComposite(currentPhoto);
if (!mountedRef.current) return;
setStatusMessage('Druckauftrag wird an Drucker gesendet...'); setStatusMessage('Druckauftrag wird an Drucker gesendet...');
await printImageLocal(printUri, { await printImageLocal(printUri, {
@@ -223,13 +232,16 @@ export default function PreviewScreen({
setStatusMessage('Wird in Galerie gespeichert...'); setStatusMessage('Wird in Galerie gespeichert...');
await saveToGallery(printUri); await saveToGallery(printUri);
if (!mountedRef.current) return;
setStatusMessage('Druck erfolgreich! Viel Spaß! 🎉'); setStatusMessage('Druck erfolgreich! Viel Spaß! 🎉');
setTimeout(() => { setTimeout(() => {
if (!mountedRef.current) return;
setIsProcessing(false); setIsProcessing(false);
onReset(); onReset();
}, 2000); }, 2000);
} catch (error: any) { } catch (error: any) {
if (!mountedRef.current) return;
console.error('Printing failed:', error); console.error('Printing failed:', error);
alert('Fehler: ' + error.message); alert('Fehler: ' + error.message);
setIsProcessing(false); setIsProcessing(false);
@@ -243,16 +255,20 @@ export default function PreviewScreen({
setStatusMessage('Bild wird generiert...'); setStatusMessage('Bild wird generiert...');
try { try {
const saveUri = await captureComposite(currentPhoto); const saveUri = await captureComposite(currentPhoto);
if (!mountedRef.current) return;
setStatusMessage('Wird in Galerie gespeichert...'); setStatusMessage('Wird in Galerie gespeichert...');
await saveToGallery(saveUri); await saveToGallery(saveUri);
if (!mountedRef.current) return;
setStatusMessage('Erfolgreich gespeichert!'); setStatusMessage('Erfolgreich gespeichert!');
setTimeout(() => { setTimeout(() => {
if (!mountedRef.current) return;
setIsProcessing(false); setIsProcessing(false);
onReset(); onReset();
}, 1500); }, 1500);
} catch (error: any) { } catch (error: any) {
if (!mountedRef.current) return;
console.error('Saving failed:', error); console.error('Saving failed:', error);
alert('Fehler: ' + error.message); alert('Fehler: ' + error.message);
setIsProcessing(false); setIsProcessing(false);
@@ -272,13 +288,17 @@ export default function PreviewScreen({
setStatusMessage('Wird gespeichert...'); setStatusMessage('Wird gespeichert...');
try { try {
const capturedUri = await captureComposite(state.currentPhoto); const capturedUri = await captureComposite(state.currentPhoto);
if (!mountedRef.current) return;
await saveToGallery(capturedUri); await saveToGallery(capturedUri);
if (!mountedRef.current) return;
setStatusMessage('Erfolgreich gespeichert!'); setStatusMessage('Erfolgreich gespeichert!');
setTimeout(() => { setTimeout(() => {
if (!mountedRef.current) return;
setIsProcessing(false); setIsProcessing(false);
state.onReset(); state.onReset();
}, 1000); }, 1000);
} catch (error) { } catch (error) {
if (!mountedRef.current) return;
console.error('Auto-saving on exit failed:', error); console.error('Auto-saving on exit failed:', error);
setIsProcessing(false); setIsProcessing(false);
state.onReset(); state.onReset();
@@ -307,9 +327,12 @@ export default function PreviewScreen({
setStatusMessage('Wird vor dem Wiederholen gespeichert...'); setStatusMessage('Wird vor dem Wiederholen gespeichert...');
try { try {
const capturedUri = await captureComposite(currentPhoto); const capturedUri = await captureComposite(currentPhoto);
if (!mountedRef.current) return;
await saveToGallery(capturedUri); await saveToGallery(capturedUri);
} catch (error) { if (!mountedRef.current) return;
console.error('Auto-saving before retake failed:', error); } catch (e) {
if (!mountedRef.current) return;
console.error('Failed to auto-save before retake:', e);
} }
setIsProcessing(false); setIsProcessing(false);
} }
@@ -326,11 +349,12 @@ export default function PreviewScreen({
{photoUris.slice(-3).map((uri, idx) => ( {photoUris.slice(-3).map((uri, idx) => (
<Image <Image
key={idx} key={idx}
source={uri} source={{ uri }}
style={styles.stripImage} style={styles.stripImage}
contentFit="cover" resizeMode="cover"
resizeMethod="resize"
onLoad={() => logger.log(`Image loaded successfully: ${uri}`)} onLoad={() => 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)}
/> />
))} ))}
</View> </View>
@@ -347,11 +371,12 @@ export default function PreviewScreen({
{photoUris.slice(-4).map((uri, idx) => ( {photoUris.slice(-4).map((uri, idx) => (
<Image <Image
key={idx} key={idx}
source={uri} source={{ uri }}
style={styles.gridImage} style={styles.gridImage}
contentFit="cover" resizeMode="cover"
resizeMethod="resize"
onLoad={() => logger.log(`Grid Image loaded successfully: ${uri}`)} onLoad={() => 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)}
/> />
))} ))}
</View> </View>
@@ -367,11 +392,12 @@ export default function PreviewScreen({
{photoUris.slice(-2).map((uri, idx) => ( {photoUris.slice(-2).map((uri, idx) => (
<Image <Image
key={idx} key={idx}
source={uri} source={{ uri }}
style={styles.duoImage} style={styles.duoImage}
contentFit="cover" resizeMode="cover"
resizeMethod="resize"
onLoad={() => logger.log(`Duo Image loaded successfully: ${uri}`)} onLoad={() => 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)}
/> />
))} ))}
</View> </View>
@@ -384,11 +410,12 @@ export default function PreviewScreen({
return ( return (
<View style={styles.singleCanvas}> <View style={styles.singleCanvas}>
<Image <Image
source={currentPhoto} source={{ uri: currentPhoto }}
style={styles.singleImage} style={styles.singleImage}
contentFit="cover" resizeMode="cover"
resizeMethod="resize"
onLoad={() => logger.log(`Single Image loaded successfully: ${currentPhoto}`)} onLoad={() => 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)}
/> />
</View> </View>
); );