feat: Implement UI enhancements and fix burst bug
This commit is contained in:
@@ -5,7 +5,7 @@ import CameraScreen from './src/screens/CameraScreen';
|
|||||||
import PreviewScreen from './src/screens/PreviewScreen';
|
import PreviewScreen from './src/screens/PreviewScreen';
|
||||||
import AdminScreen from './src/screens/AdminScreen';
|
import AdminScreen from './src/screens/AdminScreen';
|
||||||
import { loadSettings, saveSettings, AppSettings } from './src/services/settings';
|
import { loadSettings, saveSettings, AppSettings } from './src/services/settings';
|
||||||
import { logger } from './src/services/logger';
|
import { logger, setLogsEnabled } from './src/services/logger';
|
||||||
import * as FileSystem from 'expo-file-system/legacy';
|
import * as FileSystem from 'expo-file-system/legacy';
|
||||||
|
|
||||||
// Set up global error handler
|
// Set up global error handler
|
||||||
@@ -33,6 +33,7 @@ export default function App() {
|
|||||||
async function initApp() {
|
async function initApp() {
|
||||||
const savedSettings = await loadSettings();
|
const savedSettings = await loadSettings();
|
||||||
setSettings(savedSettings);
|
setSettings(savedSettings);
|
||||||
|
setLogsEnabled(savedSettings.enableLogs);
|
||||||
|
|
||||||
// Auto-start Kiosk mode if it was configured as enabled
|
// Auto-start Kiosk mode if it was configured as enabled
|
||||||
if (savedSettings.kioskModeEnabled) {
|
if (savedSettings.kioskModeEnabled) {
|
||||||
@@ -129,6 +130,7 @@ export default function App() {
|
|||||||
|
|
||||||
const handleSaveSettings = (updated: AppSettings) => {
|
const handleSaveSettings = (updated: AppSettings) => {
|
||||||
setSettings(updated);
|
setSettings(updated);
|
||||||
|
setLogsEnabled(updated.enableLogs);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenAdmin = () => {
|
const handleOpenAdmin = () => {
|
||||||
@@ -160,6 +162,8 @@ export default function App() {
|
|||||||
burstIntervalSec={settings.burstIntervalSec}
|
burstIntervalSec={settings.burstIntervalSec}
|
||||||
frameMode={settings.frameMode}
|
frameMode={settings.frameMode}
|
||||||
selectedFrameId={settings.selectedFrameId}
|
selectedFrameId={settings.selectedFrameId}
|
||||||
|
footerText={settings.footerText}
|
||||||
|
useFrontCamera={settings.useFrontCamera}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -200,11 +204,11 @@ export default function App() {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#050510',
|
backgroundColor: 'green',
|
||||||
},
|
},
|
||||||
loadingContainer: {
|
loadingContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#050510',
|
backgroundColor: 'red',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
@@ -154,6 +154,7 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
elevation: 21,
|
elevation: 21,
|
||||||
|
padding: 30, // Large invisible padding to significantly increase the touch/pinch hitbox
|
||||||
},
|
},
|
||||||
emojiText: {
|
emojiText: {
|
||||||
fontSize: 48,
|
fontSize: 48,
|
||||||
@@ -178,8 +179,8 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
deleteBtn: {
|
deleteBtn: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: -10,
|
top: 15,
|
||||||
right: -10,
|
right: 15,
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
Switch,
|
Switch,
|
||||||
Alert,
|
Alert,
|
||||||
Modal,
|
Modal,
|
||||||
|
BackHandler,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
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';
|
||||||
@@ -81,6 +82,56 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
|
|||||||
const [burstCount, setBurstCount] = useState<string>(String(currentSettings.burstCount));
|
const [burstCount, setBurstCount] = useState<string>(String(currentSettings.burstCount));
|
||||||
const [burstInterval, setBurstInterval] = useState<string>(String(currentSettings.burstIntervalSec));
|
const [burstInterval, setBurstInterval] = useState<string>(String(currentSettings.burstIntervalSec));
|
||||||
const [welcomeText, setWelcomeText] = useState<string>(currentSettings.welcomeText);
|
const [welcomeText, setWelcomeText] = useState<string>(currentSettings.welcomeText);
|
||||||
|
const [footerText, setFooterText] = useState<string>(currentSettings.footerText);
|
||||||
|
const [useFrontCamera, setUseFrontCamera] = useState<boolean>(currentSettings.useFrontCamera);
|
||||||
|
const [enableLogs, setEnableLogs] = useState<boolean>(currentSettings.enableLogs);
|
||||||
|
|
||||||
|
const hasUnsavedChanges = () => {
|
||||||
|
return (
|
||||||
|
countdown !== String(currentSettings.countdownDuration) ||
|
||||||
|
printerIp !== currentSettings.printerIp ||
|
||||||
|
password !== currentSettings.adminPassword ||
|
||||||
|
kioskActive !== currentSettings.kioskModeEnabled ||
|
||||||
|
frameMode !== currentSettings.frameMode ||
|
||||||
|
selectedFrameId !== currentSettings.selectedFrameId ||
|
||||||
|
availableFrameIds.join() !== (currentSettings.availableFrameIds || []).join() ||
|
||||||
|
dateOverlay !== currentSettings.dateOverlay ||
|
||||||
|
dateOverlayPosition !== currentSettings.dateOverlayPosition ||
|
||||||
|
eventText !== currentSettings.eventText ||
|
||||||
|
burstCount !== String(currentSettings.burstCount) ||
|
||||||
|
burstInterval !== String(currentSettings.burstIntervalSec) ||
|
||||||
|
welcomeText !== currentSettings.welcomeText ||
|
||||||
|
footerText !== currentSettings.footerText ||
|
||||||
|
useFrontCamera !== currentSettings.useFrontCamera ||
|
||||||
|
enableLogs !== currentSettings.enableLogs
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onBackPress = () => {
|
||||||
|
if (hasUnsavedChanges()) {
|
||||||
|
Alert.alert(
|
||||||
|
'Änderungen speichern?',
|
||||||
|
'Du hast ungespeicherte Änderungen. Möchtest du diese vor dem Verlassen speichern?',
|
||||||
|
[
|
||||||
|
{ text: 'Abbrechen', style: 'cancel' },
|
||||||
|
{ text: 'Nein (Verwerfen)', onPress: handleClose, style: 'destructive' },
|
||||||
|
{ text: 'Ja (Speichern)', onPress: async () => { await handleSave(); handleClose(); } },
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
return true; // Prevent default
|
||||||
|
};
|
||||||
|
|
||||||
|
BackHandler.addEventListener('hardwareBackPress', onBackPress);
|
||||||
|
return () => BackHandler.removeEventListener('hardwareBackPress', onBackPress);
|
||||||
|
}, [
|
||||||
|
countdown, printerIp, password, kioskActive, frameMode, selectedFrameId, availableFrameIds,
|
||||||
|
dateOverlay, dateOverlayPosition, eventText, burstCount, burstInterval, welcomeText,
|
||||||
|
footerText, useFrontCamera, enableLogs, currentSettings
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkKioskStatus = () => {
|
const checkKioskStatus = () => {
|
||||||
@@ -191,6 +242,9 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
|
|||||||
burstCount: burst,
|
burstCount: burst,
|
||||||
burstIntervalSec: interval,
|
burstIntervalSec: interval,
|
||||||
welcomeText: welcomeText.trim() || 'Willkommen zu unserer Feier!',
|
welcomeText: welcomeText.trim() || 'Willkommen zu unserer Feier!',
|
||||||
|
footerText: footerText.trim() || 'ZUM STARTEN TIPPEN • Fotos machen • Collage • Drucken',
|
||||||
|
useFrontCamera,
|
||||||
|
enableLogs,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -245,7 +299,21 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
|
|||||||
<FontAwesomeIcon icon={faUserGear} size={22} color={THEME.colors.accent} />
|
<FontAwesomeIcon icon={faUserGear} size={22} color={THEME.colors.accent} />
|
||||||
<Text style={styles.title}>EINSTELLUNGEN</Text>
|
<Text style={styles.title}>EINSTELLUNGEN</Text>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity style={styles.closeBtn} onPress={handleClose}>
|
<TouchableOpacity style={styles.closeBtn} onPress={() => {
|
||||||
|
if (hasUnsavedChanges()) {
|
||||||
|
Alert.alert(
|
||||||
|
'Änderungen speichern?',
|
||||||
|
'Möchtest du die Änderungen speichern?',
|
||||||
|
[
|
||||||
|
{ text: 'Abbrechen', style: 'cancel' },
|
||||||
|
{ text: 'Verwerfen', onPress: handleClose, style: 'destructive' },
|
||||||
|
{ text: 'Speichern', onPress: async () => { await handleSave(); handleClose(); } },
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
}}>
|
||||||
<FontAwesomeIcon icon={faArrowLeft} size={14} color={THEME.colors.text} />
|
<FontAwesomeIcon icon={faArrowLeft} size={14} color={THEME.colors.text} />
|
||||||
<Text style={styles.closeBtnText}>Zurück zur Fotobox</Text>
|
<Text style={styles.closeBtnText}>Zurück zur Fotobox</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -319,6 +387,16 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
|
|||||||
onChangeText={setWelcomeText}
|
onChangeText={setWelcomeText}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
<View style={styles.inputGroup}>
|
||||||
|
<Text style={styles.label}>Subtext (Footer)</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="ZUM STARTEN TIPPEN..."
|
||||||
|
placeholderTextColor={THEME.colors.textMuted}
|
||||||
|
value={footerText}
|
||||||
|
onChangeText={setFooterText}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
<View style={styles.inputGroup}>
|
<View style={styles.inputGroup}>
|
||||||
<Text style={styles.label}>Event-Name (für Datum-Sticker)</Text>
|
<Text style={styles.label}>Event-Name (für Datum-Sticker)</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -361,6 +439,24 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
|
|||||||
keyboardType="number-pad"
|
keyboardType="number-pad"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
<View style={styles.toggleRow}>
|
||||||
|
<Text style={styles.toggleLabel}>Tablet-Frontkamera als Fallback erzwingen</Text>
|
||||||
|
<Switch
|
||||||
|
value={useFrontCamera}
|
||||||
|
onValueChange={setUseFrontCamera}
|
||||||
|
trackColor={{ false: THEME.colors.surfaceSecondary, true: THEME.colors.accent }}
|
||||||
|
thumbColor={THEME.colors.text}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.toggleRow}>
|
||||||
|
<Text style={styles.toggleLabel}>App-Protokollierung (Logs) aktivieren</Text>
|
||||||
|
<Switch
|
||||||
|
value={enableLogs}
|
||||||
|
onValueChange={setEnableLogs}
|
||||||
|
trackColor={{ false: THEME.colors.surfaceSecondary, true: THEME.colors.accent }}
|
||||||
|
thumbColor={THEME.colors.text}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.card}>
|
<View style={styles.card}>
|
||||||
|
|||||||
@@ -51,9 +51,11 @@ interface CameraScreenProps {
|
|||||||
adminPassword?: string;
|
adminPassword?: string;
|
||||||
welcomeText?: string;
|
welcomeText?: string;
|
||||||
burstCount?: number;
|
burstCount?: number;
|
||||||
burstIntervalSec?: number;
|
burstIntervalSec: number;
|
||||||
frameMode?: 'off' | 'always' | 'available';
|
frameMode: 'off' | 'always' | 'available';
|
||||||
selectedFrameId?: string | null;
|
selectedFrameId: string | null;
|
||||||
|
footerText: string;
|
||||||
|
useFrontCamera: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CameraScreen({
|
export default function CameraScreen({
|
||||||
@@ -67,20 +69,23 @@ export default function CameraScreen({
|
|||||||
adminPassword = '1234',
|
adminPassword = '1234',
|
||||||
welcomeText = 'Willkommen zu unserer Feier!',
|
welcomeText = 'Willkommen zu unserer Feier!',
|
||||||
burstCount = 1,
|
burstCount = 1,
|
||||||
burstIntervalSec = 5,
|
burstIntervalSec,
|
||||||
frameMode = 'off',
|
frameMode,
|
||||||
selectedFrameId = null,
|
selectedFrameId,
|
||||||
|
footerText,
|
||||||
|
useFrontCamera,
|
||||||
}: CameraScreenProps) {
|
}: CameraScreenProps) {
|
||||||
const [permission, requestPermission] = useCameraPermissions();
|
const [permission, requestPermission] = useCameraPermissions();
|
||||||
const [isUsbConnected, setIsUsbConnected] = useState<boolean>(false);
|
const [isUsbConnected, setIsUsbConnected] = useState<boolean>(false);
|
||||||
const [countdown, setCountdown] = useState<number | string>('');
|
const [countdown, setCountdown] = useState<number | 'smile'>(countdownDuration);
|
||||||
const [isCapturing, setIsCapturing] = useState<boolean>(false);
|
const [isCapturing, setIsCapturing] = useState<boolean>(false);
|
||||||
const [hasStarted, setHasStarted] = useState<boolean>(false);
|
const [hasStarted, setHasStarted] = useState<boolean>(false);
|
||||||
const [pictureSize, setPictureSize] = useState<string | undefined>(undefined);
|
const [pictureSize, setPictureSize] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
// Burst mode state
|
// Burst mode state and refs
|
||||||
const [burstIndex, setBurstIndex] = useState<number>(0);
|
const [burstIndex, setBurstIndex] = useState<number>(0);
|
||||||
const [burstUris, setBurstUris] = useState<string[]>([]);
|
const burstIndexRef = useRef<number>(0);
|
||||||
|
const burstUrisRef = useRef<string[]>([]);
|
||||||
const [showBurstIndicator, setShowBurstIndicator] = useState<string>('');
|
const [showBurstIndicator, setShowBurstIndicator] = useState<string>('');
|
||||||
const [isBurstWaiting, setIsBurstWaiting] = useState<boolean>(false);
|
const [isBurstWaiting, setIsBurstWaiting] = useState<boolean>(false);
|
||||||
|
|
||||||
@@ -228,14 +233,15 @@ export default function CameraScreen({
|
|||||||
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);
|
if (completionTimerRef.current) clearTimeout(completionTimerRef.current);
|
||||||
const urisToDelete = [...burstUris];
|
const urisToDelete = [...burstUrisRef.current];
|
||||||
|
|
||||||
setCountdown(countdownDuration);
|
setCountdown(countdownDuration);
|
||||||
setIsCapturing(false);
|
setIsCapturing(false);
|
||||||
setIsBurstWaiting(false);
|
setIsBurstWaiting(false);
|
||||||
setHasStarted(false);
|
setHasStarted(false);
|
||||||
setBurstIndex(0);
|
setBurstIndex(0);
|
||||||
setBurstUris([]);
|
burstIndexRef.current = 0;
|
||||||
|
burstUrisRef.current = [];
|
||||||
setShowBurstIndicator('');
|
setShowBurstIndicator('');
|
||||||
onCancel();
|
onCancel();
|
||||||
|
|
||||||
@@ -254,7 +260,8 @@ export default function CameraScreen({
|
|||||||
if (isIdle) {
|
if (isIdle) {
|
||||||
setHasStarted(false);
|
setHasStarted(false);
|
||||||
setBurstIndex(0);
|
setBurstIndex(0);
|
||||||
setBurstUris([]);
|
burstIndexRef.current = 0;
|
||||||
|
burstUrisRef.current = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,13 +368,14 @@ export default function CameraScreen({
|
|||||||
|
|
||||||
// Handle burst mode
|
// Handle burst mode
|
||||||
const effectiveBurst = burstCount || 1;
|
const effectiveBurst = burstCount || 1;
|
||||||
const newIndex = burstIndex + 1;
|
const newIndex = burstIndexRef.current + 1;
|
||||||
const newUris = [...burstUris, capturedUri];
|
const newUris = [...burstUrisRef.current, capturedUri];
|
||||||
|
|
||||||
if (effectiveBurst > 1 && newIndex < effectiveBurst) {
|
if (effectiveBurst > 1 && newIndex < effectiveBurst) {
|
||||||
// More burst photos to take
|
// More burst photos to take
|
||||||
|
burstIndexRef.current = newIndex;
|
||||||
|
burstUrisRef.current = newUris;
|
||||||
setBurstIndex(newIndex);
|
setBurstIndex(newIndex);
|
||||||
setBurstUris(newUris);
|
|
||||||
setShowBurstIndicator(`Foto ${newIndex} von ${effectiveBurst} ✓`);
|
setShowBurstIndicator(`Foto ${newIndex} von ${effectiveBurst} ✓`);
|
||||||
setIsCapturing(false);
|
setIsCapturing(false);
|
||||||
setIsBurstWaiting(true);
|
setIsBurstWaiting(true);
|
||||||
@@ -379,8 +387,9 @@ export default function CameraScreen({
|
|||||||
}, burstIntervalSec * 1000);
|
}, burstIntervalSec * 1000);
|
||||||
} else if (effectiveBurst > 1) {
|
} else if (effectiveBurst > 1) {
|
||||||
// Final burst photo
|
// Final burst photo
|
||||||
|
burstIndexRef.current = 0;
|
||||||
|
burstUrisRef.current = [];
|
||||||
setBurstIndex(0);
|
setBurstIndex(0);
|
||||||
setBurstUris([]);
|
|
||||||
// Short delay prevents Android expo-camera crash on immediate unmount
|
// Short delay prevents Android expo-camera crash on immediate unmount
|
||||||
completionTimerRef.current = setTimeout(() => {
|
completionTimerRef.current = setTimeout(() => {
|
||||||
onBurstComplete(newUris);
|
onBurstComplete(newUris);
|
||||||
@@ -512,7 +521,7 @@ export default function CameraScreen({
|
|||||||
adjustsFontSizeToFit
|
adjustsFontSizeToFit
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
>
|
>
|
||||||
ZUM STARTEN TIPPEN • Fotos machen • Collage • Drucken
|
{footerText}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -644,7 +653,7 @@ export default function CameraScreen({
|
|||||||
{/* Camera Preview Background */}
|
{/* Camera Preview Background */}
|
||||||
<View style={styles.previewContainer}>
|
<View style={styles.previewContainer}>
|
||||||
<View style={styles.cameraWrapper}>
|
<View style={styles.cameraWrapper}>
|
||||||
{Platform.OS === 'android' && isUsbConnected ? (
|
{Platform.OS === 'android' && !useFrontCamera ? (
|
||||||
<UsbCameraView
|
<UsbCameraView
|
||||||
ref={usbCameraRef}
|
ref={usbCameraRef}
|
||||||
style={[styles.cameraPreview, { position: 'absolute', opacity: 1, zIndex: 10 }]}
|
style={[styles.cameraPreview, { position: 'absolute', opacity: 1, zIndex: 10 }]}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Image,
|
Image,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
|
BackHandler,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import ViewShot from 'react-native-view-shot';
|
import ViewShot from 'react-native-view-shot';
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
@@ -96,6 +97,18 @@ export default function PreviewScreen({
|
|||||||
// Idle Timer state
|
// Idle Timer state
|
||||||
const idleTimerRef = useRef<any>(null);
|
const idleTimerRef = useRef<any>(null);
|
||||||
|
|
||||||
|
// Hardware Back Press
|
||||||
|
useEffect(() => {
|
||||||
|
const onBackPress = () => {
|
||||||
|
onReset();
|
||||||
|
return true; // Prevent default behavior (closing the app)
|
||||||
|
};
|
||||||
|
BackHandler.addEventListener('hardwareBackPress', onBackPress);
|
||||||
|
return () => {
|
||||||
|
BackHandler.removeEventListener('hardwareBackPress', onBackPress);
|
||||||
|
};
|
||||||
|
}, [onReset]);
|
||||||
|
|
||||||
// Frame state
|
// Frame state
|
||||||
const [selectedFrameId, setSelectedFrameId] = useState<string | null>(
|
const [selectedFrameId, setSelectedFrameId] = useState<string | null>(
|
||||||
frameMode === 'always' ? initialFrameId : null
|
frameMode === 'always' ? initialFrameId : null
|
||||||
@@ -106,7 +119,7 @@ export default function PreviewScreen({
|
|||||||
const currentPhoto = photoUris[photoUris.length - 1];
|
const currentPhoto = photoUris[photoUris.length - 1];
|
||||||
|
|
||||||
// Get the active frame asset
|
// Get the active frame asset
|
||||||
const activeFrame = selectedFrameId && photoUris.length === 1
|
const activeFrame = selectedFrameId
|
||||||
? FRAMES.find((f) => f.id === selectedFrameId)
|
? FRAMES.find((f) => f.id === selectedFrameId)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
@@ -578,7 +591,7 @@ export default function PreviewScreen({
|
|||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
{frameMode === 'available' && photoUris.length === 1 && (
|
{frameMode === 'available' && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.editToolBtn, selectedFrameId !== null && styles.editToolBtnActive]}
|
style={[styles.editToolBtn, selectedFrameId !== null && styles.editToolBtnActive]}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@@ -603,8 +616,8 @@ export default function PreviewScreen({
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Frame Picker (when mode = available and only 1 photo) */}
|
{/* Frame Picker (when mode = available) */}
|
||||||
{frameMode === 'available' && photoUris.length === 1 && (
|
{frameMode === 'available' && (
|
||||||
<FramePicker
|
<FramePicker
|
||||||
selectedFrameId={selectedFrameId}
|
selectedFrameId={selectedFrameId}
|
||||||
onSelectFrame={(id) => {
|
onSelectFrame={(id) => {
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ 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
|
||||||
const logQueue: string[] = [];
|
const logQueue: string[] = [];
|
||||||
let isWriting = false;
|
let isWriting = false;
|
||||||
|
let isEnabled = true;
|
||||||
|
|
||||||
|
export const setLogsEnabled = (enabled: boolean) => {
|
||||||
|
isEnabled = enabled;
|
||||||
|
};
|
||||||
|
|
||||||
const processLogQueue = async () => {
|
const processLogQueue = async () => {
|
||||||
if (isWriting) return;
|
if (isWriting) return;
|
||||||
@@ -48,6 +53,7 @@ const enqueueWrite = async (logLine: string) => {
|
|||||||
|
|
||||||
export const logger = {
|
export const logger = {
|
||||||
log: async (message: string) => {
|
log: async (message: string) => {
|
||||||
|
if (!isEnabled) return;
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
const logLine = `[INFO] ${timestamp}: ${message}\n`;
|
const logLine = `[INFO] ${timestamp}: ${message}\n`;
|
||||||
console.log(logLine.trim());
|
console.log(logLine.trim());
|
||||||
@@ -55,6 +61,10 @@ export const logger = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
error: async (message: string, error?: any) => {
|
error: async (message: string, error?: any) => {
|
||||||
|
if (!isEnabled) {
|
||||||
|
console.error(`[ERROR]: ${message}`, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
let errorString = '';
|
let errorString = '';
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ export interface AppSettings {
|
|||||||
burstIntervalSec: number; // seconds between burst shots
|
burstIntervalSec: number; // seconds between burst shots
|
||||||
// Welcome screen text
|
// Welcome screen text
|
||||||
welcomeText: string;
|
welcomeText: string;
|
||||||
|
footerText: string;
|
||||||
|
// System
|
||||||
|
useFrontCamera: boolean;
|
||||||
|
enableLogs: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsFile = new File(Paths.document, 'settings.json');
|
const settingsFile = new File(Paths.document, 'settings.json');
|
||||||
@@ -39,6 +43,9 @@ const DEFAULT_SETTINGS: AppSettings = {
|
|||||||
burstCount: 1,
|
burstCount: 1,
|
||||||
burstIntervalSec: 5,
|
burstIntervalSec: 5,
|
||||||
welcomeText: 'Willkommen zu unserer Feier!',
|
welcomeText: 'Willkommen zu unserer Feier!',
|
||||||
|
footerText: 'ZUM STARTEN TIPPEN • Fotos machen • Collage • Drucken',
|
||||||
|
useFrontCamera: false,
|
||||||
|
enableLogs: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user