feat: improve preview screen workflow, persist edits, remove retake button
This commit is contained in:
@@ -5,6 +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 { StickerData } from './src/components/DraggableSticker';
|
||||||
import { logger, setLogsEnabled } 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';
|
||||||
|
|
||||||
@@ -30,6 +31,10 @@ export default function App() {
|
|||||||
const [showEventModal, setShowEventModal] = useState(false);
|
const [showEventModal, setShowEventModal] = useState(false);
|
||||||
const [tempEventName, setTempEventName] = useState('');
|
const [tempEventName, setTempEventName] = useState('');
|
||||||
|
|
||||||
|
// Persist edits across camera switches for collages
|
||||||
|
const [stickers, setStickers] = useState<StickerData[]>([]);
|
||||||
|
const [selectedFrameId, setSelectedFrameId] = useState<string | null>(null);
|
||||||
|
|
||||||
// 1. Load configuration settings on app start
|
// 1. Load configuration settings on app start
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initApp() {
|
async function initApp() {
|
||||||
@@ -45,6 +50,10 @@ export default function App() {
|
|||||||
console.warn('Failed to auto-start Kiosk mode:', e);
|
console.warn('Failed to auto-start Kiosk mode:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (savedSettings.frameMode === 'always') {
|
||||||
|
setSelectedFrameId(savedSettings.selectedFrameId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
initApp();
|
initApp();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -67,6 +76,8 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
setPhotoUris([]);
|
setPhotoUris([]);
|
||||||
setCaptureHistory([]);
|
setCaptureHistory([]);
|
||||||
|
setStickers([]);
|
||||||
|
setSelectedFrameId(settings?.frameMode === 'always' ? settings.selectedFrameId : null);
|
||||||
setCurrentScreen('camera');
|
setCurrentScreen('camera');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -81,6 +92,8 @@ export default function App() {
|
|||||||
// Resume start
|
// Resume start
|
||||||
setPhotoUris([]);
|
setPhotoUris([]);
|
||||||
setCaptureHistory([]);
|
setCaptureHistory([]);
|
||||||
|
setStickers([]);
|
||||||
|
setSelectedFrameId(updated.frameMode === 'always' ? updated.selectedFrameId : null);
|
||||||
setCurrentScreen('camera');
|
setCurrentScreen('camera');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -110,26 +123,7 @@ export default function App() {
|
|||||||
setCurrentScreen('preview');
|
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 = () => {
|
const handleAddAnother = () => {
|
||||||
setCurrentScreen('camera');
|
setCurrentScreen('camera');
|
||||||
@@ -146,6 +140,8 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
setPhotoUris([]);
|
setPhotoUris([]);
|
||||||
setCaptureHistory([]);
|
setCaptureHistory([]);
|
||||||
|
setStickers([]);
|
||||||
|
setSelectedFrameId(settings?.frameMode === 'always' ? settings.selectedFrameId : null);
|
||||||
setCurrentScreen('home'); // Go to home when cancelled
|
setCurrentScreen('home'); // Go to home when cancelled
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -199,7 +195,6 @@ export default function App() {
|
|||||||
<PreviewScreen
|
<PreviewScreen
|
||||||
photoUris={photoUris}
|
photoUris={photoUris}
|
||||||
printerIp={settings.printerIp}
|
printerIp={settings.printerIp}
|
||||||
onRetakeLast={handleRetakeLast}
|
|
||||||
onAddAnother={handleAddAnother}
|
onAddAnother={handleAddAnother}
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
frameMode={settings.frameMode}
|
frameMode={settings.frameMode}
|
||||||
@@ -208,6 +203,10 @@ export default function App() {
|
|||||||
dateOverlay={settings.dateOverlay}
|
dateOverlay={settings.dateOverlay}
|
||||||
dateOverlayTransform={settings.dateOverlayTransform}
|
dateOverlayTransform={settings.dateOverlayTransform}
|
||||||
eventText={settings.eventText}
|
eventText={settings.eventText}
|
||||||
|
stickers={stickers}
|
||||||
|
setStickers={setStickers}
|
||||||
|
selectedFrameId={selectedFrameId}
|
||||||
|
setSelectedFrameId={setSelectedFrameId}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import { FRAMES } from '../data/frames';
|
|||||||
interface PreviewScreenProps {
|
interface PreviewScreenProps {
|
||||||
photoUris: string[];
|
photoUris: string[];
|
||||||
printerIp: string;
|
printerIp: string;
|
||||||
onRetakeLast: () => void;
|
|
||||||
onAddAnother: () => void;
|
onAddAnother: () => void;
|
||||||
onReset: () => void;
|
onReset: () => void;
|
||||||
// Frame settings
|
// Frame settings
|
||||||
@@ -39,6 +38,11 @@ interface PreviewScreenProps {
|
|||||||
dateOverlayTransform: { x: number; y: number; rotation: number; scale: number };
|
dateOverlayTransform: { x: number; y: number; rotation: number; scale: number };
|
||||||
// Event text
|
// Event text
|
||||||
eventText: string;
|
eventText: string;
|
||||||
|
// Hoisted state
|
||||||
|
stickers: StickerData[];
|
||||||
|
setStickers: React.Dispatch<React.SetStateAction<StickerData[]>>;
|
||||||
|
selectedFrameId: string | null;
|
||||||
|
setSelectedFrameId: React.Dispatch<React.SetStateAction<string | null>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CollageLayout = 'single' | 'strip' | 'grid' | 'duo';
|
type CollageLayout = 'single' | 'strip' | 'grid' | 'duo';
|
||||||
@@ -46,7 +50,6 @@ type CollageLayout = 'single' | 'strip' | 'grid' | 'duo';
|
|||||||
export default function PreviewScreen({
|
export default function PreviewScreen({
|
||||||
photoUris,
|
photoUris,
|
||||||
printerIp,
|
printerIp,
|
||||||
onRetakeLast,
|
|
||||||
onAddAnother,
|
onAddAnother,
|
||||||
onReset,
|
onReset,
|
||||||
frameMode,
|
frameMode,
|
||||||
@@ -55,6 +58,10 @@ export default function PreviewScreen({
|
|||||||
dateOverlay,
|
dateOverlay,
|
||||||
dateOverlayTransform,
|
dateOverlayTransform,
|
||||||
eventText,
|
eventText,
|
||||||
|
stickers,
|
||||||
|
setStickers,
|
||||||
|
selectedFrameId,
|
||||||
|
setSelectedFrameId,
|
||||||
}: PreviewScreenProps) {
|
}: PreviewScreenProps) {
|
||||||
const [layout, setLayout] = useState<CollageLayout>('single');
|
const [layout, setLayout] = useState<CollageLayout>('single');
|
||||||
const [isProcessing, setIsProcessing] = useState<boolean>(false);
|
const [isProcessing, setIsProcessing] = useState<boolean>(false);
|
||||||
@@ -107,8 +114,6 @@ export default function PreviewScreen({
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Sticker state
|
|
||||||
const [stickers, setStickers] = useState<StickerData[]>([]);
|
|
||||||
const [selectedStickerId, setSelectedStickerId] = useState<string | null>(null);
|
const [selectedStickerId, setSelectedStickerId] = useState<string | null>(null);
|
||||||
|
|
||||||
const mountedRef = useRef(true);
|
const mountedRef = useRef(true);
|
||||||
@@ -147,10 +152,6 @@ export default function PreviewScreen({
|
|||||||
};
|
};
|
||||||
}, [onReset]);
|
}, [onReset]);
|
||||||
|
|
||||||
// Frame state
|
|
||||||
const [selectedFrameId, setSelectedFrameId] = useState<string | null>(
|
|
||||||
frameMode === 'always' ? initialFrameId : null
|
|
||||||
);
|
|
||||||
|
|
||||||
const viewShotRef = useRef<any>(null);
|
const viewShotRef = useRef<any>(null);
|
||||||
|
|
||||||
@@ -372,26 +373,26 @@ export default function PreviewScreen({
|
|||||||
};
|
};
|
||||||
}, [resetIdleTimer]);
|
}, [resetIdleTimer]);
|
||||||
|
|
||||||
const handleRetakeClick = async () => {
|
const handleAddAnotherClick = async () => {
|
||||||
logger.log('Action: Clicked Retake button');
|
logger.log('Action: Clicked Add Another button');
|
||||||
if (idleTimerRef.current) clearTimeout(idleTimerRef.current);
|
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') {
|
|
||||||
|
// Auto-save the intermediate collage before adding another photo
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
try {
|
try {
|
||||||
const capturedUri = await captureComposite(currentPhoto);
|
const capturedUri = await captureComposite(currentPhoto);
|
||||||
if (!mountedRef.current) return;
|
if (!mountedRef.current) return;
|
||||||
setStatusMessage('Wird vor dem Wiederholen gespeichert...');
|
setStatusMessage('Wird zwischengespeichert...');
|
||||||
await saveToGallery(capturedUri, 'Collage', eventText);
|
await saveToGallery(capturedUri, 'Collage', eventText);
|
||||||
if (!mountedRef.current) return;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mountedRef.current) return;
|
if (!mountedRef.current) return;
|
||||||
console.error('Failed to auto-save before retake:', e);
|
console.error('Failed to auto-save before adding another:', e);
|
||||||
}
|
}
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
}
|
|
||||||
onRetakeLast();
|
onAddAnother();
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Render the collage ──
|
// ── Render the collage ──
|
||||||
@@ -652,19 +653,14 @@ export default function PreviewScreen({
|
|||||||
|
|
||||||
|
|
||||||
{photoUris.length < 4 && (
|
{photoUris.length < 4 && (
|
||||||
<TouchableOpacity style={[styles.actionBtn, styles.addBtn, isProcessing && styles.btnDisabled, isSmallDevice && { paddingVertical: THEME.spacing.sm, marginBottom: THEME.spacing.sm }]} onPress={onAddAnother} disabled={isProcessing}>
|
<TouchableOpacity style={[styles.actionBtn, styles.addBtn, isProcessing && styles.btnDisabled, isSmallDevice && { paddingVertical: THEME.spacing.sm, marginBottom: THEME.spacing.sm }]} onPress={handleAddAnotherClick} disabled={isProcessing}>
|
||||||
<FontAwesomeIcon icon={faPlus} size={16} color={THEME.colors.text} style={{ marginRight: 8 }} />
|
<FontAwesomeIcon icon={faPlus} size={16} color={THEME.colors.text} style={{ marginRight: 8 }} />
|
||||||
<Text style={styles.actionBtnText}>Weiteres Foto aufnehmen</Text>
|
<Text style={styles.actionBtnText}>Foto zur Collage hinzufügen</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<View style={styles.rowActions}>
|
<View style={styles.rowActions}>
|
||||||
<TouchableOpacity style={[styles.smallBtn, styles.retakeBtn, isProcessing && styles.btnDisabled]} onPress={handleRetakeClick} disabled={isProcessing}>
|
<TouchableOpacity style={[styles.smallBtn, styles.resetBtn, isProcessing && styles.btnDisabled, { width: '100%' }]} onPress={handleExit} disabled={isProcessing}>
|
||||||
<FontAwesomeIcon icon={faRotateLeft} size={14} color={THEME.colors.error} style={{ marginRight: 6 }} />
|
|
||||||
<Text style={[styles.smallBtnText, { color: THEME.colors.error }]}>Wiederholen</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
<TouchableOpacity style={[styles.smallBtn, styles.resetBtn, isProcessing && styles.btnDisabled]} onPress={handleExit} disabled={isProcessing}>
|
|
||||||
<FontAwesomeIcon icon={faRightFromBracket} size={14} color={THEME.colors.textMuted} style={{ marginRight: 6 }} />
|
<FontAwesomeIcon icon={faRightFromBracket} size={14} color={THEME.colors.textMuted} style={{ marginRight: 6 }} />
|
||||||
<Text style={styles.smallBtnText}>Beenden</Text>
|
<Text style={styles.smallBtnText}>Beenden</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|||||||
Reference in New Issue
Block a user