import React, { useEffect, useRef, useState } from 'react'; import { View, Text, Modal, TouchableOpacity, ScrollView, Share, Alert, useWindowDimensions } from 'react-native'; import * as Sharing from 'expo-sharing'; import { X, Share2, Download, Trash2 } from 'lucide-react-native'; import { PhotoEntry } from '../types'; import ZoomableImage from './ZoomableImage'; // Session photo viewer: swipe left/right across every photo taken/exported in // this session. Top shows preset name + index/total; trash deletes the photo // being viewed (confirm modal first); share/save as before. Opened from the // thumbnail button (camera mode) or automatically after a library export — // NOT after a camera capture (that only fills the thumbnail). interface PhotoViewerModalProps { visible: boolean; photos: PhotoEntry[]; initialIndex: number; onClose: () => void; // Remove photo id from the session history (App also deletes cache + gallery copy). onDelete: (id: string) => void; // Save the (unsaved) photo id to the gallery; App stores the resulting assetId. onSave: (id: string) => Promise; } export default function PhotoViewerModal({ visible, photos, initialIndex, onClose, onDelete, onSave, }: PhotoViewerModalProps) { const { width } = useWindowDimensions(); const [index, setIndex] = useState(Math.max(0, Math.min(initialIndex, photos.length - 1))); const scrollRef = useRef(null); const [saving, setSaving] = useState(false); const wasOpen = useRef(false); // Opening the modal (or reopening on a different photo): jump to initialIndex. // Shrinks of the list while open (deletion) must NOT re-scroll — the strip // shifts left by one page and the same page slot keeps showing the next photo. useEffect(() => { if (!visible) { wasOpen.current = false; return; } if (!wasOpen.current) { wasOpen.current = true; if (photos.length > 0) { const i = Math.max(0, Math.min(initialIndex, photos.length - 1)); setIndex(i); requestAnimationFrame(() => { scrollRef.current?.scrollTo({ x: i * width, animated: false }); }); } } }, [visible, initialIndex, width, photos.length]); // Keep the page state in bounds when the list shrinks underneath us. useEffect(() => { if (visible && photos.length > 0 && index >= photos.length) { const i = photos.length - 1; setIndex(i); scrollRef.current?.scrollTo({ x: i * width, animated: false }); } }, [visible, index, photos.length, width]); const photo = photos[index] || null; const handleShare = async () => { if (!photo) return; try { if (await Sharing.isAvailableAsync()) { await Sharing.shareAsync(photo.uri, { mimeType: 'image/jpeg', dialogTitle: `Share with RecipesCam - Preset: ${photo.recipeName}`, UTI: 'public.jpeg', }); } else { await Share.share({ url: photo.uri, message: `Captured with RecipesCam - Preset: ${photo.recipeName}`, }); } } catch (error) { console.error('Sharing failed:', error); } }; const handleDeletePress = () => { if (!photo) return; if (photo.savedToLibrary) { // The gallery copy exists → Android itself shows its per-item delete // confirmation, so no app modal in front of it. onDelete(photo.id); return; } // Never reached the gallery — no system prompt will happen, keep an app // confirm as the only guard against an accidental trash tap. Alert.alert( 'Delete Photo', 'Delete this photo? It was never saved to your gallery.', [ { text: 'Cancel', style: 'cancel' }, { text: 'Delete', style: 'destructive', onPress: () => onDelete(photo.id) }, ] ); }; const handleDone = async () => { if (!photo || photo.savedToLibrary || saving) { onClose(); return; } setSaving(true); try { await onSave(photo.id); onClose(); } catch (e) { console.error('Save failed:', e); } finally { setSaving(false); } }; if (!visible) return null; // see PhotoViewerModal return ( {/* Top bar: preset + index/total (left), trash + close (right) */} {photo ? photo.recipeName.toUpperCase() : ''} {photos.length > 0 ? `${index + 1} / ${photos.length}` : ''} {photo && ( )} {/* Swipeable photo strip */} {photos.length > 0 ? ( { const i = Math.round(e.nativeEvent.contentOffset.x / width); setIndex(Math.max(0, Math.min(i, photos.length - 1))); }} > {photos.map((p) => ( ))} ) : ( NO PHOTOS )} {/* Bottom bar */} {photo && !photo.savedToLibrary && ( NOT SAVED TO GALLERY - SAVE & DONE TO KEEP )} SHARE {saving ? 'SAVING...' : photo && !photo.savedToLibrary ? 'SAVE & DONE' : 'DONE'} ); }