From d9b2c9cc52e25bbfc871d8832d1bbf3ae9db5eb1 Mon Sep 17 00:00:00 2001 From: orfelorfel23 Date: Thu, 11 Jun 2026 15:21:47 +0200 Subject: [PATCH] feat: add photo selection reel for collages and app version reset --- src/screens/PreviewScreen.tsx | 138 ++++++++++++++++++++++++++++------ src/services/settings.ts | 11 +++ 2 files changed, 127 insertions(+), 22 deletions(-) diff --git a/src/screens/PreviewScreen.tsx b/src/screens/PreviewScreen.tsx index ad8f5edd..a3e30447 100644 --- a/src/screens/PreviewScreen.tsx +++ b/src/screens/PreviewScreen.tsx @@ -1,13 +1,5 @@ import React, { useState, useRef, useCallback, useEffect } from 'react'; -import { - StyleSheet, - Text, - View, - Image, - TouchableOpacity, - ActivityIndicator, - BackHandler, -} from 'react-native'; +import { ScrollView, StyleSheet, Text, View, Image, TouchableOpacity, ActivityIndicator, BackHandler } from 'react-native'; import ViewShot from 'react-native-view-shot'; import { LinearGradient } from 'expo-linear-gradient'; import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'; @@ -68,6 +60,42 @@ export default function PreviewScreen({ const [isCapturing, setIsCapturing] = useState(false); const [statusMessage, setStatusMessage] = useState(''); + const getRequiredPhotoCount = (l: CollageLayout) => { + if (l === 'single') return 1; + if (l === 'duo') return 2; + if (l === 'strip') return 3; + if (l === 'grid') return 4; + return 1; + }; + + const [activeUris, setActiveUris] = useState([]); + + useEffect(() => { + const req = getRequiredPhotoCount(layout); + setActiveUris(prev => { + if (prev.length === req) return prev; + if (prev.length > req) return prev.slice(prev.length - req); + const available = photoUris.filter(u => !prev.includes(u)); + const needed = req - prev.length; + const toAdd = available.slice(-needed); + return [...prev, ...toAdd]; + }); + }, [layout, photoUris]); + + const togglePhoto = (uri: string) => { + logger.log(`Action: Toggled photo ${uri}`); + setActiveUris(prev => { + const req = getRequiredPhotoCount(layout); + if (prev.includes(uri)) { + return prev; // Already selected, do nothing + } + if (prev.length >= req) { + return [...prev.slice(1), uri]; // FIFO: remove oldest, add new + } + return [...prev, uri]; + }); + }; + // Sticker state @@ -387,7 +415,7 @@ export default function PreviewScreen({ return ( - {photoUris.slice(-3).map((uri, idx) => ( + {activeUris.map((uri, idx) => ( - {photoUris.slice(-4).map((uri, idx) => ( + {activeUris.map((uri, idx) => ( - {photoUris.slice(-2).map((uri, idx) => ( + {activeUris.map((uri, idx) => ( - logger.log(`Single Image loaded successfully: ${currentPhoto}`)} - onError={(e) => logger.error(`Single Image load failed: ${currentPhoto}`, e.nativeEvent?.error || e)} - /> + + {activeUris.length > 0 && ( + logger.log(`Single Image loaded successfully: ${currentPhoto}`)} + onError={(e) => logger.error(`Single Image load failed: ${currentPhoto}`, e.nativeEvent?.error || e)} + /> + )} ); } @@ -570,8 +600,34 @@ export default function PreviewScreen({ + {/* Thumbnail Reel for Photo Selection */} + {photoUris.length > 1 && layout !== 'single' && ( + + + {photoUris.map((uri, idx) => { + const isActive = activeUris.includes(uri); + return ( + togglePhoto(uri)} + activeOpacity={0.7} + > + + {isActive && ( + + + + )} + + ); + })} + + + )} + - Aufgenommene Fotos: {photoUris.length} / 4 + Aufgenommene Fotos: {photoUris.length} {/* Editing Tools Row */} @@ -752,6 +808,44 @@ const styles = StyleSheet.create({ fontSize: 12, fontWeight: 'bold', }, + thumbnailReel: { + marginBottom: THEME.spacing.md, + height: 60, + }, + thumbnailReelContent: { + alignItems: 'center', + gap: 10, + }, + thumbnailWrapper: { + width: 45, + height: 45, + borderRadius: 4, + borderWidth: 2, + borderColor: 'transparent', + overflow: 'hidden', + position: 'relative', + opacity: 0.5, + }, + thumbnailActive: { + borderColor: THEME.colors.primary, + opacity: 1, + }, + thumbnailImage: { + width: '100%', + height: '100%', + resizeMode: 'cover', + }, + thumbnailCheck: { + position: 'absolute', + bottom: 2, + right: 2, + backgroundColor: THEME.colors.primary, + borderRadius: 10, + width: 14, + height: 14, + justifyContent: 'center', + alignItems: 'center', + }, photosCountText: { fontSize: 14, color: THEME.colors.textMuted, diff --git a/src/services/settings.ts b/src/services/settings.ts index 7735ddcb..6b085ebd 100644 --- a/src/services/settings.ts +++ b/src/services/settings.ts @@ -2,7 +2,10 @@ import { File, Paths } from 'expo-file-system'; import * as FileSystem from 'expo-file-system/legacy'; import { FRAMES } from '../data/frames'; +export const APP_VERSION = 2; // Increment this whenever you want settings to reset on update + export interface AppSettings { + appVersion: number; countdownDuration: number; // in seconds printerIp: string; adminPassword: string; @@ -30,6 +33,7 @@ export interface AppSettings { const settingsFile = new File(Paths.document, 'settings.json'); const DEFAULT_SETTINGS: AppSettings = { + appVersion: APP_VERSION, countdownDuration: 3, printerIp: '192.168.1.100', adminPassword: '1234', @@ -55,6 +59,13 @@ export async function loadSettings(): Promise { try { const content = await settingsFile.text(); const parsed = JSON.parse(content); + + // Reset to defaults if the app version has changed (app updated) + if (parsed.appVersion !== APP_VERSION) { + console.log(`Settings version mismatch (old: ${parsed.appVersion}, new: ${APP_VERSION}). Resetting to default.`); + return DEFAULT_SETTINGS; + } + return { ...DEFAULT_SETTINGS, ...parsed }; } catch (e) { console.error('Failed to load settings, using defaults:', e);