// ImageViewerModal.tsx import React, { useState, useRef, useEffect } from 'react'; import { Modal, View, Text, Image, TouchableOpacity, StyleSheet, Dimensions, FlatList, SafeAreaView, Platform } from 'react-native'; export interface ImageObject { url: string; note?: string; } const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); interface ImageViewerModalProps { visible: boolean; images: ImageObject[]; initialIndex?: number; onClose: () => void; } const ImageViewerModal: React.FC = ({ visible, images, initialIndex = 0, onClose, }) => { const [currentIndex, setCurrentIndex] = useState(initialIndex); const flatListRef = useRef>(null); // Effect to scroll to initialIndex when modal becomes visible or initialIndex prop changes useEffect(() => { if (visible && images && images.length > 0) { const validInitialIndex = Math.max(0, Math.min(initialIndex, images.length - 1)); setCurrentIndex(validInitialIndex); // setTimeout is a common workaround for FlatList not scrolling immediately // to initialScrollIndex on mount or when data changes if modal was hidden. setTimeout(() => { flatListRef.current?.scrollToIndex({ animated: false, index: validInitialIndex, }); }, 100); } }, [visible, initialIndex, images]); const onViewableItemsChanged = useRef( ({ viewableItems }: { viewableItems: Array<{ item: ImageObject; index: number | null }> }) => { if (viewableItems.length > 0 && viewableItems[0].index !== null) { setCurrentIndex(viewableItems[0].index); } } ).current; const viewabilityConfig = useRef({ itemVisiblePercentThreshold: 50 }).current; if (!images || images.length === 0) { return null; // Don't render modal if no images } const renderImageItem = ({ item }: { item: ImageObject }) => ( ); return ( `${item.url}-${index}`} // Ensure unique key horizontal pagingEnabled showsHorizontalScrollIndicator={false} onViewableItemsChanged={onViewableItemsChanged} viewabilityConfig={viewabilityConfig} // initialScrollIndex={currentIndex} // Let useEffect handle initial scroll getItemLayout={(data, index) => ( { length: screenWidth, offset: screenWidth * index, index } )} /> {images[currentIndex]?.note && ( {images[currentIndex].note} )} {images.length > 1 && ( {currentIndex + 1} / {images.length} )} ); }; const styles = StyleSheet.create({ modalContainer: { flex: 1, backgroundColor: 'black', }, slide: { width: screenWidth, height: '100%', // Make slide take full height of SafeAreaView content area justifyContent: 'center', alignItems: 'center', }, largeImage: { width: screenWidth, // Image takes full width of its slide height: '80%', // Image takes 80% height of its slide, adjust as needed }, noteContainer: { position: 'absolute', bottom: Platform.OS === 'ios' ? 20 : 40, // Adjust for different OS safe areas or tab bars if modal is within tabs left: 0, right: 0, paddingHorizontal: 20, paddingVertical: 10, backgroundColor: 'rgba(0,0,0,0.7)', }, noteText: { color: 'white', textAlign: 'center', fontSize: 16, }, closeButton: { position: 'absolute', top: Platform.OS === 'ios' ? 50 : 20, // Safer position for status bar, adjust if using custom header right: 15, backgroundColor: 'rgba(0,0,0,0.5)', width: 40, height: 40, borderRadius: 20, justifyContent: 'center', alignItems: 'center', zIndex: 10, // Ensure it's above other elements }, closeButtonText: { color: 'white', fontSize: 18, fontWeight: 'bold', lineHeight: 20, // Helps center the X }, pagination: { position: 'absolute', top: Platform.OS === 'ios' ? 55 : 25, left: 15, backgroundColor: 'rgba(0,0,0,0.5)', paddingHorizontal: 10, paddingVertical: 5, borderRadius: 15, zIndex: 10, }, paginationText: { color: 'white', fontSize: 14, }, }); export default ImageViewerModal;