add icon for documents without thumbnails
This commit is contained in:
@@ -118,6 +118,10 @@ func (h *FileHandler) Get(c *gin.Context) {
|
||||
"name": file.Name,
|
||||
"url": h.urls.GenerateDownloadURL(file.ID),
|
||||
"size": file.Size,
|
||||
"mimeType": file.MimeType,
|
||||
"createdAt": file.CreatedAt,
|
||||
"updatedAt": file.UpdatedAt,
|
||||
"ocrText": file.OcrText,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<ScrollView style={styles.page} contentContainerStyle={styles.pageContent}>
|
||||
<View style={styles.imageContainer}>
|
||||
{imageLoading ? (
|
||||
<ActivityIndicator size="large" color="#1976D2" />
|
||||
) : uri ? (
|
||||
{uri ? (
|
||||
<Image source={{ uri }} style={styles.image} resizeMode="contain" />
|
||||
) : file ? (
|
||||
<FileThumbnail
|
||||
mimeType={file.mimeType ?? 'application/pdf'}
|
||||
fileName={file.name ?? fileId}
|
||||
size={SCREEN_WIDTH * 0.6}
|
||||
isLoading={imageLoading}
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.placeholder}>
|
||||
<Text style={styles.placeholderText}>PDF</Text>
|
||||
</View>
|
||||
<ActivityIndicator size="large" color="#1976D2" />
|
||||
)}
|
||||
</View>
|
||||
|
||||
|
||||
+9
-33
@@ -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 (
|
||||
<TouchableOpacity style={styles.gridItem} onPress={onPress} activeOpacity={0.7}>
|
||||
{isLoading ? (
|
||||
<View style={styles.placeholder}>
|
||||
<ActivityIndicator size="small" color="#1976D2" />
|
||||
</View>
|
||||
) : uri ? (
|
||||
<Image source={{ uri }} style={styles.thumb} />
|
||||
) : (
|
||||
<View style={styles.placeholder}>
|
||||
<Text style={styles.placeholderText}>PDF</Text>
|
||||
</View>
|
||||
)}
|
||||
<FileThumbnail
|
||||
uri={data?.data?.url}
|
||||
mimeType={file.mimeType}
|
||||
fileName={file.name}
|
||||
size={ITEM_SIZE}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<Text style={styles.fileName} numberOfLines={1}>{file.name}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
@@ -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',
|
||||
|
||||
@@ -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<typeof MaterialIcons>['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 (
|
||||
<View style={[styles.container, { width: size, height: size, backgroundColor: info.bg }]}>
|
||||
<ActivityIndicator size="small" color={info.color} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (uri && mimeType.startsWith('image/')) {
|
||||
return <Image source={{ uri }} style={[styles.image, { width: size, height: size }]} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { width: size, height: size, backgroundColor: info.bg }]}>
|
||||
<MaterialIcons name={info.icon} size={size * 0.35} color={info.color} />
|
||||
{ext.length <= 4 && (
|
||||
<Text style={[styles.ext, { color: info.color }]}>{ext}</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
borderRadius: 6,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
},
|
||||
image: {
|
||||
borderRadius: 6,
|
||||
},
|
||||
ext: {
|
||||
fontSize: 11,
|
||||
fontWeight: '700',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user