import React, { useRef, useState, useCallback, useEffect } from 'react'; import { View, Text, StyleSheet, FlatList, Image, Dimensions, ActivityIndicator, ScrollView, Modal, Pressable, TouchableOpacity, } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; import { RouteProp, useRoute } from '@react-navigation/native'; import { useFile, useDownloadFile } from '../hooks/useFiles'; import { fileStore } from '../services/fileStore'; import { TagChip } from '../components/TagChip'; import { FileThumbnail } from '../components/FileThumbnail'; import { SyncStatusBadge } from '../components/SyncStatusBadge'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { ZoomableImage } from '../components/ZoomableImage'; import type { Variant, SyncStatus } from '../types'; const SCREEN_WIDTH = Dimensions.get('window').width; type SelectedImage = { uri: string; width: number; height: number }; type ModalState = { images: SelectedImage[]; index: number } | null; export type DeviceFileParam = { localUri: string; name: string; mimeType: string; createdAt: string; }; type RootStackParamList = { FileDetail: { fileIds: string[]; initialIndex: number; deviceFiles?: Record }; }; type FileDetailRouteProp = RouteProp; function DetailItem({ fileId, deviceFile, onSelectImage }: { fileId: string; deviceFile?: DeviceFileParam; onSelectImage?: (state: ModalState) => void }) { const isDevice = !!deviceFile; const localEntry = fileStore.getById(fileId); const apiId = isDevice ? '' : (localEntry?.backendId ?? fileId); const { data: fileData } = useFile(apiId); const downloadFile = useDownloadFile(); const [downloading, setDownloading] = useState(false); const file = fileData as any; const uri = isDevice ? deviceFile.localUri : (localEntry?.localUri ?? file?.url); const [imageSize, setImageSize] = useState<{ width: number; height: number } | null>(null); useEffect(() => { if (!uri) { setImageSize(null); return; } Image.getSize( uri, (w, h) => setImageSize({ width: w, height: h }), () => setImageSize(null), ); }, [uri]); const syncStatus: SyncStatus = isDevice ? 'local' : localEntry ? (localEntry.syncStatus as SyncStatus) : 'cloud'; const fullVariants: Variant[] = (file?.variants ?? []) .filter((v: Variant) => v.variantType === 'thumbnail_full') .sort((a: Variant, b: Variant) => a.pageNumber - b.pageNumber); const fullThumbnails = fullVariants; const hasPages = fullThumbnails.length > 0; const handleDownload = useCallback(async () => { if (!file) return; const bid = localEntry?.backendId ?? fileId; setDownloading(true); try { await downloadFile.mutateAsync({ id: bid, backendResourceId: bid, name: file?.name ?? localEntry?.name ?? fileId, mimeType: file?.mimeType ?? localEntry?.mimeType ?? 'application/octet-stream', size: file?.size ?? localEntry?.size ?? 0, createdAt: file?.createdAt ?? localEntry?.createdAt ?? new Date().toISOString(), source: 'cloud', syncStatus: 'cloud', tags: file?.tags ?? [], isFolder: false, }); } catch {} finally { setDownloading(false); } }, [file, fileId, localEntry, downloadFile]); return ( {hasPages ? ( fullThumbnails.map((thumb, thumbIndex) => { const allImages: SelectedImage[] = fullThumbnails.map((t) => ({ uri: t.url, width: SCREEN_WIDTH, height: t.height * (SCREEN_WIDTH / t.width), })); return ( onSelectImage?.({ images: allImages, index: thumbIndex })} > ); }) ) : ( {uri ? ( onSelectImage?.({ images: [{ uri, width: SCREEN_WIDTH, height: imageSize ? imageSize.height * SCREEN_WIDTH / imageSize.width : SCREEN_WIDTH }], index: 0, })} > ) : file ? ( {syncStatus === 'cloud' && ( {downloading ? ( ) : ( )} {downloading ? 'Téléchargement...' : 'Télécharger'} )} ) : ( )} )} {deviceFile?.name ?? file?.name ?? fileId} {(deviceFile || file?.size != null) && ( Taille : {((deviceFile ? 0 : file?.size) ?? 0 / 1024).toFixed(1)} Ko )} {(deviceFile?.createdAt || file?.createdAt) && ( Ajouté le {new Date(deviceFile?.createdAt ?? file?.createdAt).toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric', })} )} {file?.tags && file.tags.length > 0 && ( Tags {file.tags.map((tag: any) => ( ))} )} {file?.ocrText && ( Texte OCR {file.ocrText} )} ); } export function FileDetailScreen() { const route = useRoute(); const { fileIds, initialIndex, deviceFiles } = route.params; const flatListRef = useRef(null); const [currentIndex, setCurrentIndex] = useState(initialIndex); const [modalState, setModalState] = useState(null); const handleSwipeVertical = useCallback((direction: 'up' | 'down') => { setModalState((prev) => { if (!prev) return prev; const next = direction === 'up' ? Math.min(prev.index + 1, prev.images.length - 1) : Math.max(prev.index - 1, 0); if (next === prev.index) return prev; return { ...prev, index: next }; }); }, []); return ( item} horizontal snapToInterval={SCREEN_WIDTH} decelerationRate="fast" disableIntervalMomentum showsHorizontalScrollIndicator={false} initialScrollIndex={initialIndex} getItemLayout={(_, index) => ({ length: SCREEN_WIDTH, offset: SCREEN_WIDTH * index, index, })} onMomentumScrollEnd={(e) => { const index = Math.round(e.nativeEvent.contentOffset.x / SCREEN_WIDTH); setCurrentIndex(index); }} renderItem={({ item }) => ( )} /> {currentIndex + 1} / {fileIds.length} setModalState(null)} > {modalState && ( setModalState(null)} onSwipeVertical={handleSwipeVertical} /> {modalState.images.length > 1 && ( {modalState.index + 1} / {modalState.images.length} )} )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#000', }, pageWrapper: { width: SCREEN_WIDTH, }, page: { flex: 1, }, pageContent: { flexGrow: 1, }, imageContainer: { backgroundColor: '#000', }, image: { width: SCREEN_WIDTH, }, pageImageContainer: { alignItems: 'center', backgroundColor: '#000', paddingVertical: 4, }, pageImage: { width: SCREEN_WIDTH, }, details: { backgroundColor: '#fff', borderTopLeftRadius: 16, borderTopRightRadius: 16, marginTop: -16, padding: 20, }, detailHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8, }, fileName: { fontSize: 18, fontWeight: '700', color: '#333', flex: 1, marginRight: 8, }, cloudOnlyContainer: { alignItems: 'center', gap: 16, }, downloadBtn: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#1976D2', paddingHorizontal: 20, paddingVertical: 12, borderRadius: 8, gap: 8, }, downloadBtnText: { color: '#fff', fontSize: 15, fontWeight: '600', }, meta: { fontSize: 14, color: '#666', marginBottom: 4, }, tagsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 6, marginTop: 8, }, tagsSection: { marginTop: 12, }, sectionLabel: { fontSize: 14, fontWeight: '600', color: '#333', marginBottom: 4, }, ocrSection: { marginTop: 16, }, ocrText: { fontSize: 14, color: '#555', lineHeight: 20, marginTop: 4, }, pagination: { position: 'absolute', bottom: 16, alignSelf: 'center', backgroundColor: 'rgba(0,0,0,0.5)', borderRadius: 12, paddingHorizontal: 12, paddingVertical: 4, }, paginationText: { color: '#fff', fontSize: 13, }, modalPagination: { position: 'absolute', bottom: 40, alignSelf: 'center', backgroundColor: 'rgba(0,0,0,0.5)', borderRadius: 12, paddingHorizontal: 12, paddingVertical: 4, }, modalPaginationText: { color: '#fff', fontSize: 13, }, });