fix: camera unmount, sticker trash, update reset
This commit is contained in:
@@ -157,6 +157,7 @@ export default function App() {
|
|||||||
onBurstComplete={handleBurstComplete}
|
onBurstComplete={handleBurstComplete}
|
||||||
onCancel={handleReset}
|
onCancel={handleReset}
|
||||||
isIdle={currentScreen !== 'camera'}
|
isIdle={currentScreen !== 'camera'}
|
||||||
|
isObscured={currentScreen === 'preview' || currentScreen === 'admin'}
|
||||||
onStartBooth={handleStartBooth}
|
onStartBooth={handleStartBooth}
|
||||||
onNavigateToAdmin={handleOpenAdmin}
|
onNavigateToAdmin={handleOpenAdmin}
|
||||||
adminPassword={settings.adminPassword}
|
adminPassword={settings.adminPassword}
|
||||||
|
|||||||
Generated
+10
@@ -14,6 +14,7 @@
|
|||||||
"@react-native-masked-view/masked-view": "0.3.2",
|
"@react-native-masked-view/masked-view": "0.3.2",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"expo": "~54.0.0",
|
"expo": "~54.0.0",
|
||||||
|
"expo-application": "~7.0.8",
|
||||||
"expo-build-properties": "~1.0.10",
|
"expo-build-properties": "~1.0.10",
|
||||||
"expo-camera": "~17.0.10",
|
"expo-camera": "~17.0.10",
|
||||||
"expo-file-system": "~19.0.23",
|
"expo-file-system": "~19.0.23",
|
||||||
@@ -4617,6 +4618,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expo-application": {
|
||||||
|
"version": "7.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/expo-application/-/expo-application-7.0.8.tgz",
|
||||||
|
"integrity": "sha512-qFGyxk7VJbrNOQWBbE09XUuGuvkOgFS9QfToaK2FdagM2aQ+x3CvGV2DuVgl/l4ZxPgIf3b/MNh9xHpwSwn74Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"expo": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/expo-asset": {
|
"node_modules/expo-asset": {
|
||||||
"version": "12.0.13",
|
"version": "12.0.13",
|
||||||
"resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-12.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-12.0.13.tgz",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"@react-native-masked-view/masked-view": "0.3.2",
|
"@react-native-masked-view/masked-view": "0.3.2",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"expo": "~54.0.0",
|
"expo": "~54.0.0",
|
||||||
|
"expo-application": "~7.0.8",
|
||||||
"expo-build-properties": "~1.0.10",
|
"expo-build-properties": "~1.0.10",
|
||||||
"expo-camera": "~17.0.10",
|
"expo-camera": "~17.0.10",
|
||||||
"expo-file-system": "~19.0.23",
|
"expo-file-system": "~19.0.23",
|
||||||
|
|||||||
@@ -121,13 +121,6 @@ export default function DraggableSticker({
|
|||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const deleteBtnStyle = useAnimatedStyle(() => ({
|
|
||||||
transform: [
|
|
||||||
{ scale: 1 / scale.value },
|
|
||||||
{ rotate: `${-rotation.value}rad` }, // keep it upright
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
|
|
||||||
const isText = sticker.type === 'text';
|
const isText = sticker.type === 'text';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -140,17 +133,6 @@ export default function DraggableSticker({
|
|||||||
) : (
|
) : (
|
||||||
<Text style={styles.emojiText}>{sticker.content}</Text>
|
<Text style={styles.emojiText}>{sticker.content}</Text>
|
||||||
)}
|
)}
|
||||||
{isSelected && (
|
|
||||||
<Animated.View style={[styles.deleteBtnWrapper, deleteBtnStyle]}>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={styles.deleteBtn}
|
|
||||||
onPress={() => onDelete(sticker.id)}
|
|
||||||
hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
|
|
||||||
>
|
|
||||||
<Text style={styles.deleteBtnText}>×</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</Animated.View>
|
|
||||||
)}
|
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</GestureDetector>
|
</GestureDetector>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
import { THEME } from '../styles/theme';
|
import { THEME } from '../styles/theme';
|
||||||
import { logger } from '../services/logger';
|
import { logger } from '../services/logger';
|
||||||
import KioskMode from '../../modules/kiosk-mode';
|
import KioskMode from '../../modules/kiosk-mode';
|
||||||
import { AppSettings, loadSettings, saveSettings, APP_VERSION } from '../services/settings';
|
import { AppSettings, loadSettings, saveSettings } from '../services/settings';
|
||||||
import { FRAMES } from '../data/frames';
|
import { FRAMES } from '../data/frames';
|
||||||
|
|
||||||
interface AdminScreenProps {
|
interface AdminScreenProps {
|
||||||
@@ -229,7 +229,7 @@ export default function AdminScreen({ currentSettings, onSave, onClose }: AdminS
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updated: AppSettings = {
|
const updated: AppSettings = {
|
||||||
appVersion: APP_VERSION,
|
appLastUpdate: currentSettings.appLastUpdate,
|
||||||
countdownDuration: duration,
|
countdownDuration: duration,
|
||||||
printerIp: ipTrimmed,
|
printerIp: ipTrimmed,
|
||||||
adminPassword: password.trim(),
|
adminPassword: password.trim(),
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ interface CameraScreenProps {
|
|||||||
selectedFrameId: string | null;
|
selectedFrameId: string | null;
|
||||||
footerText: string;
|
footerText: string;
|
||||||
useFrontCamera: boolean;
|
useFrontCamera: boolean;
|
||||||
|
isObscured: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CameraScreen({
|
export default function CameraScreen({
|
||||||
@@ -74,6 +75,7 @@ export default function CameraScreen({
|
|||||||
selectedFrameId,
|
selectedFrameId,
|
||||||
footerText,
|
footerText,
|
||||||
useFrontCamera,
|
useFrontCamera,
|
||||||
|
isObscured,
|
||||||
}: CameraScreenProps) {
|
}: CameraScreenProps) {
|
||||||
const [permission, requestPermission] = useCameraPermissions();
|
const [permission, requestPermission] = useCameraPermissions();
|
||||||
const [isUsbConnected, setIsUsbConnected] = useState<boolean>(false);
|
const [isUsbConnected, setIsUsbConnected] = useState<boolean>(false);
|
||||||
@@ -653,10 +655,10 @@ 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' && !useFrontCamera ? (
|
{!isObscured && isUsbConnected && !useFrontCamera ? (
|
||||||
<UsbCameraView
|
<UsbCameraView
|
||||||
ref={usbCameraRef}
|
ref={usbCameraRef}
|
||||||
style={[styles.cameraPreview, { position: 'absolute', opacity: 1, zIndex: 10 }]}
|
style={StyleSheet.absoluteFill}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<CameraView
|
<CameraView
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
faRightFromBracket,
|
faRightFromBracket,
|
||||||
faFaceGrinStars,
|
faFaceGrinStars,
|
||||||
faBorderAll,
|
faBorderAll,
|
||||||
|
faTrash,
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import { THEME } from '../styles/theme';
|
import { THEME } from '../styles/theme';
|
||||||
import { printImageLocal } from '../services/printer';
|
import { printImageLocal } from '../services/printer';
|
||||||
@@ -432,7 +433,7 @@ export default function PreviewScreen({
|
|||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.collageFooter}>
|
<View style={styles.collageFooter}>
|
||||||
<Text style={styles.collageFooterText}>SCHNAPPIX FOTOBOX</Text>
|
<Text style={styles.collageFooterText}>{eventText}</Text>
|
||||||
<Text style={styles.collageFooterDate}>{new Date().toLocaleDateString('de-DE')}</Text>
|
<Text style={styles.collageFooterDate}>{new Date().toLocaleDateString('de-DE')}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -454,7 +455,7 @@ export default function PreviewScreen({
|
|||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.collageFooter}>
|
<View style={styles.collageFooter}>
|
||||||
<Text style={styles.collageFooterText}>SCHNAPPIX FOTOBOX</Text>
|
<Text style={styles.collageFooterText}>{eventText}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -475,7 +476,7 @@ export default function PreviewScreen({
|
|||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.collageFooter}>
|
<View style={styles.collageFooter}>
|
||||||
<Text style={styles.collageFooterText}>SCHNAPPIX FOTOBOX</Text>
|
<Text style={styles.collageFooterText}>{eventText}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -554,9 +555,20 @@ export default function PreviewScreen({
|
|||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Global Trash Can for Stickers */}
|
||||||
|
{selectedStickerId !== null && (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.globalTrashButton}
|
||||||
|
onPress={() => handleStickerDelete(selectedStickerId)}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTrash} color="#fff" size={32} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Right panel: Controls */}
|
{/* Right panel: Controls */}
|
||||||
<View style={styles.controlPanel}>
|
<View style={styles.controlPanel}>
|
||||||
<Text style={styles.header}>DEIN FOTO</Text>
|
<Text style={styles.header}>{eventText}</Text>
|
||||||
|
|
||||||
{/* Thumbnail Reel for Photo Selection */}
|
{/* Thumbnail Reel for Photo Selection */}
|
||||||
{photoUris.length > 1 && (
|
{photoUris.length > 1 && (
|
||||||
@@ -568,6 +580,7 @@ export default function PreviewScreen({
|
|||||||
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.thumbnailReelContent}>
|
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.thumbnailReelContent}>
|
||||||
{photoUris.map((uri, idx) => {
|
{photoUris.map((uri, idx) => {
|
||||||
const isActive = activeUris.includes(uri);
|
const isActive = activeUris.includes(uri);
|
||||||
|
const indexInSelection = activeUris.indexOf(uri) + 1;
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={idx}
|
key={idx}
|
||||||
@@ -578,7 +591,7 @@ export default function PreviewScreen({
|
|||||||
<Image source={{ uri }} style={styles.thumbnailImage} />
|
<Image source={{ uri }} style={styles.thumbnailImage} />
|
||||||
{isActive && (
|
{isActive && (
|
||||||
<View style={styles.thumbnailCheck}>
|
<View style={styles.thumbnailCheck}>
|
||||||
<Text style={{ color: '#fff', fontSize: 10, fontWeight: 'bold' }}>✓</Text>
|
<Text style={{ color: '#fff', fontSize: 14, fontWeight: 'bold' }}>{indexInSelection}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -739,6 +752,23 @@ const styles = StyleSheet.create({
|
|||||||
textShadowOffset: { width: 0, height: 0 },
|
textShadowOffset: { width: 0, height: 0 },
|
||||||
textShadowRadius: 12,
|
textShadowRadius: 12,
|
||||||
},
|
},
|
||||||
|
globalTrashButton: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 40,
|
||||||
|
alignSelf: 'center',
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
borderRadius: 30,
|
||||||
|
backgroundColor: THEME.colors.error,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
zIndex: 9999,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.5,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 8,
|
||||||
|
},
|
||||||
thumbnailInstruction: {
|
thumbnailInstruction: {
|
||||||
color: THEME.colors.textMuted,
|
color: THEME.colors.textMuted,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { File, Paths } from 'expo-file-system';
|
|||||||
import * as FileSystem from 'expo-file-system/legacy';
|
import * as FileSystem from 'expo-file-system/legacy';
|
||||||
import { FRAMES } from '../data/frames';
|
import { FRAMES } from '../data/frames';
|
||||||
|
|
||||||
export const APP_VERSION = 2; // Increment this whenever you want settings to reset on update
|
import * as Application from 'expo-application';
|
||||||
|
|
||||||
export interface AppSettings {
|
export interface AppSettings {
|
||||||
appVersion: number;
|
appLastUpdate: number | null;
|
||||||
countdownDuration: number; // in seconds
|
countdownDuration: number; // in seconds
|
||||||
printerIp: string;
|
printerIp: string;
|
||||||
adminPassword: string;
|
adminPassword: string;
|
||||||
@@ -33,7 +33,7 @@ export interface AppSettings {
|
|||||||
const settingsFile = new File(Paths.document, 'settings.json');
|
const settingsFile = new File(Paths.document, 'settings.json');
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: AppSettings = {
|
const DEFAULT_SETTINGS: AppSettings = {
|
||||||
appVersion: APP_VERSION,
|
appLastUpdate: null,
|
||||||
countdownDuration: 3,
|
countdownDuration: 3,
|
||||||
printerIp: '192.168.1.100',
|
printerIp: '192.168.1.100',
|
||||||
adminPassword: '1234',
|
adminPassword: '1234',
|
||||||
@@ -56,21 +56,35 @@ const DEFAULT_SETTINGS: AppSettings = {
|
|||||||
* Load settings from persistent storage.
|
* Load settings from persistent storage.
|
||||||
*/
|
*/
|
||||||
export async function loadSettings(): Promise<AppSettings> {
|
export async function loadSettings(): Promise<AppSettings> {
|
||||||
|
let currentUpdateTime: number | null = null;
|
||||||
|
try {
|
||||||
|
const updateTime = await Application.getLastUpdateTimeAsync();
|
||||||
|
currentUpdateTime = updateTime ? updateTime.getTime() : null;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not get app update time:', e);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const content = await settingsFile.text();
|
const content = await settingsFile.text();
|
||||||
const parsed = JSON.parse(content);
|
const parsed = JSON.parse(content);
|
||||||
|
|
||||||
// Reset to defaults if the app version has changed (app updated)
|
// Reset to defaults if the app installation/update time has changed
|
||||||
if (parsed.appVersion !== APP_VERSION) {
|
// Allow a small drift margin (e.g. 5 seconds) just in case
|
||||||
console.log(`Settings version mismatch (old: ${parsed.appVersion}, new: ${APP_VERSION}). Resetting to default.`);
|
if (parsed.appLastUpdate && currentUpdateTime) {
|
||||||
return DEFAULT_SETTINGS;
|
if (Math.abs(parsed.appLastUpdate - currentUpdateTime) > 5000) {
|
||||||
|
console.log(`App update detected. Resetting settings to default.`);
|
||||||
|
const defaults = { ...DEFAULT_SETTINGS, appLastUpdate: currentUpdateTime };
|
||||||
|
// Instantly save the new default settings to disk so they persist!
|
||||||
|
await saveSettings(defaults);
|
||||||
|
return defaults;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...DEFAULT_SETTINGS, ...parsed };
|
return { ...DEFAULT_SETTINGS, ...parsed, appLastUpdate: currentUpdateTime };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load settings, using defaults:', e);
|
console.error('Failed to load settings, using defaults:', e);
|
||||||
}
|
}
|
||||||
return DEFAULT_SETTINGS;
|
return { ...DEFAULT_SETTINGS, appLastUpdate: currentUpdateTime };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user