From 7063768e96f916a7a42b3e5c4f0a9cfae5c2e5fb Mon Sep 17 00:00:00 2001 From: m Date: Sun, 12 Jul 2026 21:12:56 +0200 Subject: [PATCH] file details --- mobile/App.tsx | 6 ++ mobile/app/file-detail.tsx | 216 +++++++++++++++++++++++++++++++++++++ mobile/app/index.tsx | 24 ++++- 3 files changed, 242 insertions(+), 4 deletions(-) create mode 100644 mobile/app/file-detail.tsx diff --git a/mobile/App.tsx b/mobile/App.tsx index d0c8dc5..38e2ba8 100644 --- a/mobile/App.tsx +++ b/mobile/App.tsx @@ -9,6 +9,7 @@ import { ScanScreen } from './app/scan'; import { SearchScreen } from './app/search'; import { BatchReviewScreen } from './app/batch-review'; import { PendingReviewScreen } from './app/pending-review'; +import { FileDetailScreen } from './app/file-detail'; const Stack = createNativeStackNavigator(); const queryClient = new QueryClient(); @@ -29,6 +30,11 @@ export default function App() { + diff --git a/mobile/app/file-detail.tsx b/mobile/app/file-detail.tsx new file mode 100644 index 0000000..c0314de --- /dev/null +++ b/mobile/app/file-detail.tsx @@ -0,0 +1,216 @@ +import React, { useRef, useState } from 'react'; +import { + View, + Text, + StyleSheet, + FlatList, + Image, + Dimensions, + ActivityIndicator, + ScrollView, +} from 'react-native'; +import { RouteProp, useRoute } from '@react-navigation/native'; +import { useFile, useFileImage } from '../hooks/useFiles'; +import { TagChip } from '../components/TagChip'; + +const SCREEN_WIDTH = Dimensions.get('window').width; + +type RootStackParamList = { + FileDetail: { fileIds: string[]; initialIndex: number }; +}; + +type FileDetailRouteProp = RouteProp; + +function DetailItem({ fileId }: { fileId: string }) { + const { data: imageData, isLoading: imageLoading } = useFileImage(fileId); + const { data: fileData } = useFile(fileId); + const uri = imageData?.data?.url; + const file = fileData as any; + + return ( + + + {imageLoading ? ( + + ) : uri ? ( + + ) : ( + + PDF + + )} + + + + {imageData?.data?.name ?? file?.name ?? fileId} + + {imageData?.data?.size != null && ( + + Taille : {(imageData.data.size / 1024).toFixed(1)} Ko + + )} + + {file?.createdAt && ( + + Ajouté le {new Date(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 } = route.params; + + const flatListRef = useRef(null); + const [currentIndex, setCurrentIndex] = useState(initialIndex); + + return ( + + item} + horizontal + pagingEnabled + 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} + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#000', + }, + pageWrapper: { + width: SCREEN_WIDTH, + }, + page: { + flex: 1, + }, + pageContent: { + flexGrow: 1, + }, + imageContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#000', + }, + image: { + width: SCREEN_WIDTH, + height: SCREEN_WIDTH, + }, + placeholder: { + width: SCREEN_WIDTH, + height: SCREEN_WIDTH, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#1a1a2e', + }, + placeholderText: { + fontSize: 48, + color: '#1976D2', + fontWeight: '700', + }, + details: { + backgroundColor: '#fff', + borderTopLeftRadius: 16, + borderTopRightRadius: 16, + marginTop: -16, + padding: 20, + }, + fileName: { + fontSize: 18, + fontWeight: '700', + color: '#333', + marginBottom: 8, + }, + 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, + }, +}); diff --git a/mobile/app/index.tsx b/mobile/app/index.tsx index ff784df..3f1abec 100644 --- a/mobile/app/index.tsx +++ b/mobile/app/index.tsx @@ -14,6 +14,7 @@ type RootStackParamList = { Home: undefined; Upload: undefined; Scan: undefined; + FileDetail: { fileIds: string[]; initialIndex: number }; }; type NavigationProp = NativeStackNavigationProp; @@ -51,13 +52,13 @@ function formatDateLabel(key: string): string { return key.charAt(0).toUpperCase() + key.slice(1); } -function FileGridItem({ file }: { file: FileItem }) { +function FileGridItem({ file, onPress }: { file: FileItem; onPress?: () => void }) { const { data, isLoading } = useFileImage(file.id); const uri = data?.data?.url; return ( - + {isLoading ? ( @@ -70,7 +71,7 @@ function FileGridItem({ file }: { file: FileItem }) { )} {file.name} - + ); } @@ -101,6 +102,12 @@ export function HomeScreen() { const groups = groupByDate(filteredFiles); const groupKeys = Object.keys(groups); + const fileIdToIndex = useMemo(() => { + const map = new Map(); + filteredFiles.forEach((f, i) => map.set(f.id, i)); + return map; + }, [filteredFiles]); + if (isLoading) { return ( @@ -154,7 +161,16 @@ export function HomeScreen() { {formatDateLabel(dateKey)} {groupFiles.map((file) => ( - + + navigation.navigate('FileDetail', { + fileIds: filteredFiles.map((f) => f.id), + initialIndex: fileIdToIndex.get(file.id) ?? 0, + }) + } + /> ))}