From 6ed51daa6ba7555105000892c671cad34e28e92e Mon Sep 17 00:00:00 2001 From: m Date: Sun, 12 Jul 2026 21:40:31 +0200 Subject: [PATCH] add icon for documents without thumbnails --- backend/internal/handler/files.go | 12 +++-- mobile/app/file-detail.tsx | 16 +++--- mobile/app/index.tsx | 42 ++++----------- mobile/components/FileThumbnail.tsx | 79 +++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 43 deletions(-) create mode 100644 mobile/components/FileThumbnail.tsx diff --git a/backend/internal/handler/files.go b/backend/internal/handler/files.go index 4fc5dde..69ddd36 100644 --- a/backend/internal/handler/files.go +++ b/backend/internal/handler/files.go @@ -114,10 +114,14 @@ func (h *FileHandler) Get(c *gin.Context) { } api.Success(c, gin.H{ - "id": file.ID, - "name": file.Name, - "url": h.urls.GenerateDownloadURL(file.ID), - "size": file.Size, + "id": file.ID, + "name": file.Name, + "url": h.urls.GenerateDownloadURL(file.ID), + "size": file.Size, + "mimeType": file.MimeType, + "createdAt": file.CreatedAt, + "updatedAt": file.UpdatedAt, + "ocrText": file.OcrText, }) } diff --git a/mobile/app/file-detail.tsx b/mobile/app/file-detail.tsx index c0314de..e8e6223 100644 --- a/mobile/app/file-detail.tsx +++ b/mobile/app/file-detail.tsx @@ -12,6 +12,7 @@ import { import { RouteProp, useRoute } from '@react-navigation/native'; import { useFile, useFileImage } from '../hooks/useFiles'; import { TagChip } from '../components/TagChip'; +import { FileThumbnail } from '../components/FileThumbnail'; const SCREEN_WIDTH = Dimensions.get('window').width; @@ -30,14 +31,17 @@ function DetailItem({ fileId }: { fileId: string }) { return ( - {imageLoading ? ( - - ) : uri ? ( + {uri ? ( + ) : file ? ( + ) : ( - - PDF - + )} diff --git a/mobile/app/index.tsx b/mobile/app/index.tsx index 9c4829c..8fcb005 100644 --- a/mobile/app/index.tsx +++ b/mobile/app/index.tsx @@ -1,5 +1,5 @@ import React, { useState, useMemo, useEffect } from 'react'; -import { View, FlatList, StyleSheet, TouchableOpacity, Text, Dimensions, Image, ActivityIndicator, KeyboardAvoidingView, Platform, Keyboard } from 'react-native'; +import { View, FlatList, StyleSheet, TouchableOpacity, Text, Dimensions, KeyboardAvoidingView, Platform, Keyboard } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; @@ -7,6 +7,7 @@ import { MaterialIcons } from '@expo/vector-icons'; import { useFiles, useFileImage } from '../hooks/useFiles'; import { FileItem } from '../types'; import { SearchBar, SearchFilters } from '../components/SearchBar'; +import { FileThumbnail } from '../components/FileThumbnail'; const NUM_COLUMNS = 3; const SCREEN_WIDTH = Dimensions.get('window').width; @@ -57,21 +58,15 @@ function formatDateLabel(key: string): string { function FileGridItem({ file, onPress }: { file: FileItem; onPress?: () => void }) { const { data, isLoading } = useFileImage(file.id); - const uri = data?.data?.url; - return ( - {isLoading ? ( - - - - ) : uri ? ( - - ) : ( - - PDF - - )} + {file.name} ); @@ -264,25 +259,6 @@ const styles = StyleSheet.create({ gridItem: { width: ITEM_SIZE, }, - thumb: { - width: ITEM_SIZE, - height: ITEM_SIZE, - borderRadius: 6, - backgroundColor: '#e0e0e0', - }, - placeholder: { - width: ITEM_SIZE, - height: ITEM_SIZE, - borderRadius: 6, - backgroundColor: '#e3f2fd', - justifyContent: 'center', - alignItems: 'center', - }, - placeholderText: { - fontSize: 12, - color: '#1976D2', - fontWeight: '600', - }, fileName: { fontSize: 11, color: '#666', diff --git a/mobile/components/FileThumbnail.tsx b/mobile/components/FileThumbnail.tsx new file mode 100644 index 0000000..852f06c --- /dev/null +++ b/mobile/components/FileThumbnail.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { View, Text, Image, ActivityIndicator, StyleSheet } from 'react-native'; +import { MaterialIcons } from '@expo/vector-icons'; +import type { ComponentProps } from 'react'; + +type IconName = ComponentProps['name']; + +interface FileTypeInfo { + icon: IconName; + color: string; + bg: string; +} + +function getFileInfo(mimeType: string, fileName: string): FileTypeInfo { + if (mimeType.startsWith('image/')) return { icon: 'image', color: '#4CAF50', bg: '#E8F5E9' }; + if (mimeType === 'application/pdf') return { icon: 'picture-as-pdf', color: '#E53935', bg: '#FFEBEE' }; + if (mimeType.includes('word') || mimeType.includes('document')) return { icon: 'description', color: '#1565C0', bg: '#E3F2FD' }; + if (mimeType.includes('spreadsheet') || mimeType.includes('excel') || mimeType.includes('csv')) return { icon: 'table-chart', color: '#2E7D32', bg: '#E8F5E9' }; + if (mimeType.includes('presentation') || mimeType.includes('powerpoint')) return { icon: 'slideshow', color: '#E65100', bg: '#FFF3E0' }; + if (mimeType.startsWith('text/')) return { icon: 'article', color: '#546E7A', bg: '#ECEFF1' }; + if (mimeType.startsWith('video/')) return { icon: 'movie', color: '#6A1B9A', bg: '#F3E5F5' }; + if (mimeType.startsWith('audio/')) return { icon: 'audiotrack', color: '#AD1457', bg: '#FCE4EC' }; + return { icon: 'insert-drive-file', color: '#757575', bg: '#F5F5F5' }; +} + +function getExtension(fileName: string): string { + const ext = fileName.split('.').pop(); + return ext ? ext.toUpperCase() : ''; +} + +interface FileThumbnailProps { + uri?: string; + mimeType: string; + fileName: string; + size: number; + isLoading?: boolean; +} + +export function FileThumbnail({ uri, mimeType, fileName, size, isLoading }: FileThumbnailProps) { + const info = getFileInfo(mimeType, fileName); + const ext = getExtension(fileName); + + if (isLoading) { + return ( + + + + ); + } + + if (uri && mimeType.startsWith('image/')) { + return ; + } + + return ( + + + {ext.length <= 4 && ( + {ext} + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + borderRadius: 6, + justifyContent: 'center', + alignItems: 'center', + gap: 2, + }, + image: { + borderRadius: 6, + }, + ext: { + fontSize: 11, + fontWeight: '700', + }, +});