Fix 5 hidden edge cases: IPP decode, settings race, PreviewScreen state sync, burst timeout leak, and logger spam

This commit is contained in:
2026-05-31 23:47:25 +02:00
parent ecfc6e5d31
commit d38115528e
5 changed files with 38 additions and 23 deletions
+4 -6
View File
@@ -148,11 +148,10 @@ export default function DraggableSticker({
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
stickerWrapper: { stickerContainer: {
position: 'absolute', position: 'absolute',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
zIndex: 100,
}, },
emojiText: { emojiText: {
fontSize: 48, fontSize: 48,
@@ -179,13 +178,12 @@ const styles = StyleSheet.create({
position: 'absolute', position: 'absolute',
top: -10, top: -10,
right: -10, right: -10,
width: 22, width: 24,
height: 22, height: 24,
borderRadius: 11, borderRadius: 12,
backgroundColor: THEME.colors.error, backgroundColor: THEME.colors.error,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
zIndex: 200,
}, },
deleteBtnText: { deleteBtnText: {
color: '#fff', color: '#fff',
+21 -2
View File
@@ -96,6 +96,7 @@ export default function CameraScreen({
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 countdownTimerRef = useRef<any>(null);
const completionTimerRef = useRef<any>(null);
const isCancelledRef = useRef<boolean>(false); const isCancelledRef = useRef<boolean>(false);
// Reanimated countdown pulse animation // Reanimated countdown pulse animation
@@ -162,6 +163,7 @@ export default function CameraScreen({
return () => { return () => {
if (burstTimerRef.current) clearTimeout(burstTimerRef.current); if (burstTimerRef.current) clearTimeout(burstTimerRef.current);
if (countdownTimerRef.current) clearTimeout(countdownTimerRef.current); if (countdownTimerRef.current) clearTimeout(countdownTimerRef.current);
if (completionTimerRef.current) clearTimeout(completionTimerRef.current);
}; };
}, []); }, []);
@@ -207,6 +209,7 @@ export default function CameraScreen({
isCancelledRef.current = true; isCancelledRef.current = true;
if (burstTimerRef.current) clearTimeout(burstTimerRef.current); if (burstTimerRef.current) clearTimeout(burstTimerRef.current);
if (countdownTimerRef.current) clearTimeout(countdownTimerRef.current); if (countdownTimerRef.current) clearTimeout(countdownTimerRef.current);
if (completionTimerRef.current) clearTimeout(completionTimerRef.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) {
@@ -362,12 +365,12 @@ export default function CameraScreen({
setBurstIndex(0); setBurstIndex(0);
setBurstUris([]); setBurstUris([]);
// Short delay prevents Android expo-camera crash on immediate unmount // Short delay prevents Android expo-camera crash on immediate unmount
setTimeout(() => { completionTimerRef.current = setTimeout(() => {
onBurstComplete(newUris); onBurstComplete(newUris);
}, 400); }, 400);
} else { } else {
// Single shot mode // Single shot mode
setTimeout(() => { completionTimerRef.current = setTimeout(() => {
onPhotoCaptured(capturedUri); onPhotoCaptured(capturedUri);
}, 400); }, 400);
} }
@@ -443,6 +446,22 @@ export default function CameraScreen({
> >
{welcomeText} {welcomeText}
</Text> </Text>
{/* White Outline Layers */}
{[
{ t: -2, l: -2 }, { t: -2, l: 0 }, { t: -2, l: 2 },
{ t: 0, l: -2 }, { t: 0, l: 2 },
{ t: 2, l: -2 }, { t: 2, l: 0 }, { t: 2, l: 2 }
].map((pos, index) => (
<Text
key={`outline-${index}`}
style={[styles.headerTitleBase, { position: 'absolute', color: 'white', top: pos.t, left: pos.l }]}
adjustsFontSizeToFit
numberOfLines={1}
>
{welcomeText}
</Text>
))}
{/* Gradient Text Layer over the shadow layer */} {/* Gradient Text Layer over the shadow layer */}
<MaskedView <MaskedView
+7 -8
View File
@@ -66,9 +66,7 @@ export default function PreviewScreen({
const [isProcessing, setIsProcessing] = useState<boolean>(false); const [isProcessing, setIsProcessing] = useState<boolean>(false);
const [statusMessage, setStatusMessage] = useState<string>(''); const [statusMessage, setStatusMessage] = useState<string>('');
React.useEffect(() => {
logger.log('PreviewScreen: render function executed');
});
// Sticker state // Sticker state
const [stickers, setStickers] = useState<StickerData[]>([]); const [stickers, setStickers] = useState<StickerData[]>([]);
@@ -224,11 +222,10 @@ export default function PreviewScreen({
if (!mountedRef.current) return; if (!mountedRef.current) return;
if (isProcessing) return; if (isProcessing) return;
setIsProcessing(true); setIsProcessing(true);
setStatusMessage('Dein Foto wird vorbereitet...');
try { try {
setStatusMessage('Bild wird generiert...');
const printUri = await captureComposite(currentPhoto); const printUri = await captureComposite(currentPhoto);
if (!mountedRef.current) return; if (!mountedRef.current) return;
setStatusMessage('Dein Foto wird vorbereitet...');
setStatusMessage('Druckauftrag wird an Drucker gesendet...'); setStatusMessage('Druckauftrag wird an Drucker gesendet...');
await printImageLocal(printUri, { await printImageLocal(printUri, {
@@ -259,10 +256,10 @@ export default function PreviewScreen({
if (!mountedRef.current) return; if (!mountedRef.current) return;
if (isProcessing) return; if (isProcessing) return;
setIsProcessing(true); setIsProcessing(true);
setStatusMessage('Bild wird generiert...');
try { try {
const saveUri = await captureComposite(currentPhoto); const saveUri = await captureComposite(currentPhoto);
if (!mountedRef.current) return; if (!mountedRef.current) return;
setStatusMessage('Bild wird generiert...');
setStatusMessage('Wird in Galerie gespeichert...'); setStatusMessage('Wird in Galerie gespeichert...');
await saveToGallery(saveUri); await saveToGallery(saveUri);
@@ -293,10 +290,10 @@ export default function PreviewScreen({
} }
setIsProcessing(true); setIsProcessing(true);
setStatusMessage('Wird gespeichert...');
try { try {
const capturedUri = await captureComposite(state.currentPhoto); const capturedUri = await captureComposite(state.currentPhoto);
if (!mountedRef.current) return; if (!mountedRef.current) return;
setStatusMessage('Wird gespeichert...');
await saveToGallery(capturedUri); await saveToGallery(capturedUri);
if (!mountedRef.current) return; if (!mountedRef.current) return;
setStatusMessage('Erfolgreich gespeichert!'); setStatusMessage('Erfolgreich gespeichert!');
@@ -333,10 +330,10 @@ export default function PreviewScreen({
if (isProcessing) return; if (isProcessing) return;
if (stickers.length > 0 || layout !== 'single' || activeFrame || dateOverlay !== 'off') { if (stickers.length > 0 || layout !== 'single' || activeFrame || dateOverlay !== 'off') {
setIsProcessing(true); setIsProcessing(true);
setStatusMessage('Wird vor dem Wiederholen gespeichert...');
try { try {
const capturedUri = await captureComposite(currentPhoto); const capturedUri = await captureComposite(currentPhoto);
if (!mountedRef.current) return; if (!mountedRef.current) return;
setStatusMessage('Wird vor dem Wiederholen gespeichert...');
await saveToGallery(capturedUri); await saveToGallery(capturedUri);
if (!mountedRef.current) return; if (!mountedRef.current) return;
} catch (e) { } catch (e) {
@@ -659,6 +656,8 @@ const styles = StyleSheet.create({
height: 320, height: 320,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
overflow: 'hidden',
backgroundColor: THEME.colors.background,
}, },
controlPanel: { controlPanel: {
flex: 0.8, flex: 0.8,
+3 -2
View File
@@ -91,8 +91,9 @@ export async function printImageLocal(imageUri: string, options: PrintOptions):
} }
console.log('Silent print job accepted successfully!'); console.log('Silent print job accepted successfully!');
} catch (e: any) { } catch (e: any) {
// If decoding fails, we still assume success since the network request completed successfully // If decoding fails, it's a print error. Let it trigger a retry.
console.warn('Failed to parse IPP response, but network request succeeded:', e.message); console.warn('Failed to parse IPP response. The printer may be incompatible or returning malformed data:', e.message);
throw e;
} }
return; // Success, exit retry loop return; // Success, exit retry loop
+3 -5
View File
@@ -46,11 +46,9 @@ const DEFAULT_SETTINGS: AppSettings = {
*/ */
export async function loadSettings(): Promise<AppSettings> { export async function loadSettings(): Promise<AppSettings> {
try { try {
if (settingsFile.exists) { const content = await settingsFile.text();
const content = await settingsFile.text(); const parsed = JSON.parse(content);
const parsed = JSON.parse(content); return { ...DEFAULT_SETTINGS, ...parsed };
return { ...DEFAULT_SETTINGS, ...parsed };
}
} catch (e) { } catch (e) {
console.error('Failed to load settings, using defaults:', e); console.error('Failed to load settings, using defaults:', e);
} }