import React, { useState, useEffect } from 'react'; import { StyleSheet, View, SafeAreaView, StatusBar, ActivityIndicator, Modal, TextInput, Text, TouchableOpacity } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import CameraScreen from './src/screens/CameraScreen'; import PreviewScreen from './src/screens/PreviewScreen'; import AdminScreen from './src/screens/AdminScreen'; import { loadSettings, saveSettings, AppSettings } from './src/services/settings'; import { logger, setLogsEnabled } from './src/services/logger'; import * as FileSystem from 'expo-file-system/legacy'; // Set up global error handler const defaultErrorHandler = (global as any).ErrorUtils.getGlobalHandler(); (global as any).ErrorUtils.setGlobalHandler((error: any, isFatal: boolean) => { logger.error(`Unhandled Exception (Fatal: ${isFatal})`, error) .catch(console.error) .finally(() => { // Delay termination until log is written defaultErrorHandler(error, isFatal); }); }); import KioskMode from './modules/kiosk-mode'; type ScreenState = 'home' | 'camera' | 'preview' | 'admin'; export default function App() { const [currentScreen, setCurrentScreen] = useState('home'); const [photoUris, setPhotoUris] = useState([]); const [captureHistory, setCaptureHistory] = useState([]); const [settings, setSettings] = useState(null); const [showEventModal, setShowEventModal] = useState(false); const [tempEventName, setTempEventName] = useState(''); // 1. Load configuration settings on app start useEffect(() => { async function initApp() { const savedSettings = await loadSettings(); setSettings(savedSettings); setLogsEnabled(savedSettings.enableLogs); // Auto-start Kiosk mode if it was configured as enabled if (savedSettings.kioskModeEnabled) { try { KioskMode.startKiosk(); } catch (e) { console.warn('Failed to auto-start Kiosk mode:', e); } } } initApp(); }, []); if (!settings) { return ( ); } // 2. Navigation Actions const handleStartBooth = () => { if (!settings?.eventText || settings.eventText.trim() === '') { setTempEventName(''); setShowEventModal(true); return; } setPhotoUris([]); setCaptureHistory([]); setCurrentScreen('camera'); }; const handleSaveEventName = async () => { if (!settings) return; const newName = tempEventName.trim() || 'Schnappix-Party'; const updated = { ...settings, eventText: newName }; await saveSettings(updated); setSettings(updated); setShowEventModal(false); // Resume start setPhotoUris([]); setCaptureHistory([]); setCurrentScreen('camera'); }; const handlePhotoCaptured = async (uri: string) => { await logger.log(`Photo captured via CameraScreen: ${uri}`); try { setPhotoUris((prev) => { const next = [...prev, uri]; return next.length > 4 ? next.slice(-4) : next; }); setCaptureHistory((prev) => [...prev, 1]); setCurrentScreen('preview'); await logger.log('State updated to preview screen successfully.'); } catch (e) { await logger.error('Error transitioning to preview screen:', e); } }; // Called by CameraScreen when burst mode completes all photos const handleBurstComplete = (uris: string[]) => { setPhotoUris((prev) => { const next = [...prev, ...uris]; const maxKeep = Math.max(4, uris.length); return next.length > maxKeep ? next.slice(-maxKeep) : next; }); setCaptureHistory((prev) => [...prev, uris.length]); setCurrentScreen('preview'); }; const handleRetakeLast = async () => { // Remove photos from the last capture event const lastCaptureCount = captureHistory.length > 0 ? captureHistory[captureHistory.length - 1] : 1; const updatedUris = [...photoUris]; const removedUris = updatedUris.splice(-lastCaptureCount, lastCaptureCount); setPhotoUris(updatedUris); setCaptureHistory((prev) => prev.slice(0, -1)); // Clean up temp files for (const uri of removedUris) { try { await FileSystem.deleteAsync(uri, { idempotent: true }); } catch (e) { console.warn('Failed to delete temp file:', e); } } // Always let user retake setCurrentScreen('camera'); }; const handleAddAnother = () => { setCurrentScreen('camera'); }; const handleReset = async () => { // Clean up temp files for (const uri of photoUris) { try { await FileSystem.deleteAsync(uri, { idempotent: true }); } catch (e) { console.warn('Failed to delete temp file:', e); } } setPhotoUris([]); setCaptureHistory([]); setCurrentScreen('home'); // Go to home when cancelled }; const handleSaveSettings = (updated: AppSettings) => { setSettings(updated); setLogsEnabled(updated.enableLogs); }; const handleOpenAdmin = () => { setCurrentScreen('admin'); }; // 3. Conditional Screen Rendering return ( ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#000', }, loadingContainer: { flex: 1, backgroundColor: '#000', justifyContent: 'center', alignItems: 'center', }, modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.8)', justifyContent: 'center', alignItems: 'center', }, modalContent: { backgroundColor: '#1a1a24', padding: 30, borderRadius: 16, width: '80%', maxWidth: 500, borderWidth: 1, borderColor: '#333', }, modalTitle: { color: '#fff', fontSize: 24, fontWeight: 'bold', marginBottom: 10, }, modalText: { color: '#aaa', fontSize: 16, marginBottom: 20, lineHeight: 24, }, modalInput: { backgroundColor: '#0a0a14', borderWidth: 1, borderColor: '#333', borderRadius: 8, color: '#fff', padding: 15, fontSize: 18, marginBottom: 25, }, modalButtons: { flexDirection: 'row', justifyContent: 'flex-end', gap: 15, }, modalBtn: { paddingVertical: 12, paddingHorizontal: 20, borderRadius: 8, }, modalBtnText: { color: '#fff', fontSize: 16, fontWeight: 'bold', }, });