Fix 8 critical/high resilience issues across camera, preview, logger, settings, and admin

This commit is contained in:
2026-06-01 10:14:29 +02:00
parent e069fef170
commit 2ddd74468e
6 changed files with 39 additions and 18 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 979 KiB

+7 -1
View File
@@ -65,6 +65,7 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
const [countdown, setCountdown] = useState<string>(String(currentSettings.countdownDuration)); const [countdown, setCountdown] = useState<string>(String(currentSettings.countdownDuration));
const [printerIp, setPrinterIp] = useState<string>(currentSettings.printerIp); const [printerIp, setPrinterIp] = useState<string>(currentSettings.printerIp);
const [password, setPassword] = useState<string>(currentSettings.adminPassword); const [password, setPassword] = useState<string>(currentSettings.adminPassword);
const [isTogglingKiosk, setIsTogglingKiosk] = useState<boolean>(false);
const [logs, setLogs] = useState<string | null>(null); const [logs, setLogs] = useState<string | null>(null);
const scrollViewRef = useRef<ScrollView>(null); const scrollViewRef = useRef<ScrollView>(null);
@@ -98,7 +99,9 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, []);
const handleKioskToggle = (enable: boolean) => { const handleKioskToggle = async (enable: boolean) => {
if (isTogglingKiosk) return;
setIsTogglingKiosk(true);
try { try {
if (enable) { if (enable) {
const success = KioskMode.startKiosk(); const success = KioskMode.startKiosk();
@@ -124,6 +127,8 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
} }
} catch (e: any) { } catch (e: any) {
Alert.alert('Nativer Fehler', e.message || 'Fehler im Kiosk-Modul'); Alert.alert('Nativer Fehler', e.message || 'Fehler im Kiosk-Modul');
} finally {
setIsTogglingKiosk(false);
} }
}; };
@@ -298,6 +303,7 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
placeholder="Standard: 1234" placeholder="Standard: 1234"
placeholderTextColor={THEME.colors.textMuted} placeholderTextColor={THEME.colors.textMuted}
secureTextEntry secureTextEntry
maxLength={20}
value={password} value={password}
onChangeText={setPassword} onChangeText={setPassword}
keyboardType="number-pad" keyboardType="number-pad"
+3 -3
View File
@@ -129,10 +129,10 @@ export default function CameraScreen({
// 1. Request built-in camera permission on mount (just in case we fallback) // 1. Request built-in camera permission on mount (just in case we fallback)
useEffect(() => { useEffect(() => {
if (!permission || !permission.granted) { if (permission && !permission.granted && permission.canAskAgain) {
requestPermission(); requestPermission();
} }
}, [permission]); }, []);
// 2. Check if a USB camera is connected. // 2. Check if a USB camera is connected.
useEffect(() => { useEffect(() => {
@@ -251,7 +251,7 @@ export default function CameraScreen({
return () => { return () => {
if (countdownTimerRef.current) clearTimeout(countdownTimerRef.current); if (countdownTimerRef.current) clearTimeout(countdownTimerRef.current);
}; };
}, [countdownDuration, isIdle, pictureSize, isUsbConnected]); }, [countdownDuration, isIdle, pictureSize, isUsbConnected, burstCount, burstIntervalSec]);
const startCountdown = () => { const startCountdown = () => {
isCancelledRef.current = false; isCancelledRef.current = false;
+5 -1
View File
@@ -217,8 +217,9 @@ export default function PreviewScreen({
} }
}; };
// Triggers the view capture and the print job // ── Actions ──
const handlePrint = async () => { const handlePrint = async () => {
if (idleTimerRef.current) clearTimeout(idleTimerRef.current);
if (!mountedRef.current) return; if (!mountedRef.current) return;
if (isProcessing) return; if (isProcessing) return;
setIsProcessing(true); setIsProcessing(true);
@@ -253,6 +254,7 @@ export default function PreviewScreen({
// Saves to gallery without printing // Saves to gallery without printing
const handleSaveOnly = async () => { const handleSaveOnly = async () => {
if (idleTimerRef.current) clearTimeout(idleTimerRef.current);
if (!mountedRef.current) return; if (!mountedRef.current) return;
if (isProcessing) return; if (isProcessing) return;
setIsProcessing(true); setIsProcessing(true);
@@ -281,6 +283,7 @@ export default function PreviewScreen({
// Auto-save and exit // Auto-save and exit
const handleExit = useCallback(async () => { const handleExit = useCallback(async () => {
if (idleTimerRef.current) clearTimeout(idleTimerRef.current);
if (!mountedRef.current) return; if (!mountedRef.current) return;
if (isProcessing) return; if (isProcessing) return;
const state = stateRef.current; const state = stateRef.current;
@@ -326,6 +329,7 @@ export default function PreviewScreen({
}, [resetIdleTimer]); }, [resetIdleTimer]);
const handleRetakeClick = async () => { const handleRetakeClick = async () => {
if (idleTimerRef.current) clearTimeout(idleTimerRef.current);
if (!mountedRef.current) return; if (!mountedRef.current) return;
if (isProcessing) return; if (isProcessing) return;
if (stickers.length > 0 || layout !== 'single' || activeFrame || dateOverlay !== 'off') { if (stickers.length > 0 || layout !== 'single' || activeFrame || dateOverlay !== 'off') {
+23 -12
View File
@@ -3,15 +3,23 @@ import * as FileSystem from 'expo-file-system/legacy';
const logFileUri = FileSystem.documentDirectory + 'app_logs.txt'; const logFileUri = FileSystem.documentDirectory + 'app_logs.txt';
// Queue to serialize file operations and prevent race conditions // Queue to serialize file operations and prevent race conditions
let writeQueue = Promise.resolve(); const logQueue: string[] = [];
let isWriting = false;
const processLogQueue = async () => {
if (isWriting) return;
isWriting = true;
while (logQueue.length > 0) {
const logLine = logQueue.shift();
if (!logLine) continue;
const enqueueWrite = (logLine: string) => {
writeQueue = writeQueue.then(async () => {
try { try {
const fileInfo = await FileSystem.getInfoAsync(logFileUri); if (logFileUri) {
if (!fileInfo.exists) { const fileInfo = await FileSystem.getInfoAsync(logFileUri);
await FileSystem.writeAsStringAsync(logFileUri, logLine, { encoding: FileSystem.EncodingType.UTF8 }); if (!fileInfo.exists) {
} else { await FileSystem.writeAsStringAsync(logFileUri, '', { encoding: FileSystem.EncodingType.UTF8 });
}
const current = await FileSystem.readAsStringAsync(logFileUri, { encoding: FileSystem.EncodingType.UTF8 }); const current = await FileSystem.readAsStringAsync(logFileUri, { encoding: FileSystem.EncodingType.UTF8 });
let newContent = current + logLine; let newContent = current + logLine;
if (newContent.length > 100000) { if (newContent.length > 100000) {
@@ -21,12 +29,15 @@ const enqueueWrite = (logLine: string) => {
} }
} catch (e) { } catch (e) {
console.warn('Failed to write to log file:', e); console.warn('Failed to write to log file:', e);
// We catch here so the queue is not broken for subsequent logs
} }
}).catch((e) => { }
console.error('Queue error:', e);
}); isWriting = false;
return writeQueue; };
const enqueueWrite = async (logLine: string) => {
logQueue.push(logLine);
processLogQueue(); // Intentionally unawaited to run in background
}; };
export const logger = { export const logger = {
+1 -1
View File
@@ -61,7 +61,7 @@ export async function loadSettings(): Promise<AppSettings> {
export async function saveSettings(settings: AppSettings): Promise<void> { export async function saveSettings(settings: AppSettings): Promise<void> {
try { try {
const content = JSON.stringify(settings, null, 2); const content = JSON.stringify(settings, null, 2);
await FileSystem.writeAsStringAsync(settingsFile.uri, content); await settingsFile.write(content);
console.log('Settings saved successfully:', content); console.log('Settings saved successfully:', content);
} catch (e) { } catch (e) {
console.error('Failed to save settings:', e); console.error('Failed to save settings:', e);