/** * PhotoGalleryModal — Phase 4: displays all of a plant's photos in a scrollable * grid with a full-screen viewer, plus a "save to device" action per photo and * a "save all" action. The JSON memory always survives — photos are for display * and sharing, never the source of truth. */ import React, { useState } from 'react'; import { Modal, ScrollView, StyleSheet, Text, TouchableOpacity, View, Image, useWindowDimensions } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { dark, line } from '../constants/theme'; export type GalleryPhoto = { uri: string; caption?: string; }; interface PhotoGalleryModalProps { visible: boolean; plantName: string; photos: GalleryPhoto[]; onClose: () => void; onSaveToDevice?: (photo: GalleryPhoto) => void; onSaveAll?: () => void; } export function PhotoGalleryModal({ visible, plantName, photos, onClose, onSaveToDevice, onSaveAll }: PhotoGalleryModalProps) { const [viewerUri, setViewerUri] = useState(null); // Pixel-based card height (Bekky rule #2 — no percentage maxHeight on modal cards). const insets = useSafeAreaInsets(); const { height: windowHeight } = useWindowDimensions(); const cardMaxHeight = Math.max(320, windowHeight - (insets.top + 24) - (insets.bottom + 24)); if (!visible) return null; return ( <> 📸 {plantName} {photos.length === 0 ? ( No photos yet — add progress photos or memories to see them here. ) : ( <> {photos.map((photo, i) => ( setViewerUri(photo.uri)}> {photo.caption ? {photo.caption} : null} ))} {onSaveAll && photos.length > 1 ? ( 💾 Save all photos ) : null} )} {/* Full-screen viewer */} setViewerUri(null)}> {viewerUri ? ( <> setViewerUri(null)}> {onSaveToDevice ? ( onSaveToDevice({ uri: viewerUri })} > 💾 Save photo ) : null} ) : null} ); } const s = StyleSheet.create({ overlay: { flex: 1, backgroundColor: 'rgba(16, 42, 16, 0.65)', justifyContent: 'center', alignItems: 'center', padding: 24, }, card: { width: '100%', maxWidth: 460, backgroundColor: '#FFFCF5', borderRadius: 24, padding: 20, maxHeight: '88%', shadowColor: '#000', shadowOpacity: 0.25, shadowRadius: 24, shadowOffset: { width: 0, height: 8 }, elevation: 12, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }, title: { fontSize: 18, fontWeight: '800', color: dark, flex: 1 }, headerClose: { width: 32, height: 32, borderRadius: 16, backgroundColor: '#FFF9EE', borderWidth: 1.5, borderColor: line, alignItems: 'center', justifyContent: 'center' }, headerCloseText: { color: dark, fontSize: 15, fontWeight: '800', lineHeight: 17 }, empty: { fontSize: 14, color: '#5D5A4E', textAlign: 'center', paddingVertical: 40, lineHeight: 21 }, gridScroll: { maxHeight: '70%' }, grid: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 }, cell: { width: '31%', marginBottom: 6 }, thumb: { width: '100%', aspectRatio: 1, borderRadius: 12, backgroundColor: '#EDE8D8' }, caption: { fontSize: 10, color: '#8A8571', marginTop: 3, textAlign: 'center' }, saveAllButton: { minHeight: 48, borderRadius: 999, backgroundColor: dark, alignItems: 'center', justifyContent: 'center', marginTop: 14, paddingHorizontal: 20 }, saveAllButtonText: { color: '#fff', fontSize: 14, fontWeight: '900' }, viewerOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.92)', justifyContent: 'center', alignItems: 'center' }, viewerImage: { width: '100%', height: '100%' }, viewerClose: { position: 'absolute', top: 48, right: 18, width: 36, height: 36, borderRadius: 18, backgroundColor: 'rgba(255,255,255,0.9)', alignItems: 'center', justifyContent: 'center' }, viewerCloseText: { color: '#000', fontSize: 18, fontWeight: '900', lineHeight: 20 }, viewerSave: { position: 'absolute', bottom: 40, alignSelf: 'center', minHeight: 48, borderRadius: 999, backgroundColor: dark, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 24 }, viewerSaveText: { color: '#fff', fontSize: 15, fontWeight: '900' }, });