Files
Media-Aggregation-and-Sorti…/src/components/PhotoGrid.js
T

121 lines
3.6 KiB
JavaScript

import React from 'react';
import {
View, Text, FlatList, TouchableOpacity, Image, Dimensions, StyleSheet,
} from 'react-native';
import { useTheme } from '../services/ThemeContext';
const { width } = Dimensions.get('window');
const GRID_GAP = 2;
const PhotoGrid = ({
data,
onItemPress,
columns = 3,
refreshControl,
ListHeaderComponent,
ListEmptyComponent,
ListFooterComponent,
onEndReached,
}) => {
const { theme } = useTheme();
const itemSize = (width - GRID_GAP * (columns + 1)) / columns;
const getServiceIcon = (type) =>
({ DEVICE: '📱', GOOGLE_PHOTOS: '📷', ICLOUD_PHOTOS: '🍎', NAS: '💾' }[type] || '📱');
const renderSourceBadges = (item) => {
const sources = item.sources && item.sources.length > 0
? item.sources
: [item.source_service];
if (sources.length === 1) {
// Single source — simple badge
return (
<View style={styles.sourceBadge}>
<Text style={{ fontSize: 10 }}>{getServiceIcon(sources[0])}</Text>
</View>
);
}
// Multiple sources — show all icons with a subtle indicator
return (
<View style={styles.multiSourceBadge}>
{sources.map((src, i) => (
<Text key={src + i} style={{ fontSize: 9 }}>{getServiceIcon(src)}</Text>
))}
</View>
);
};
const renderItem = ({ item }) => (
<TouchableOpacity
style={[styles.gridItem, { width: itemSize, height: itemSize, backgroundColor: theme.chipBg }]}
onPress={() => onItemPress?.(item)}
activeOpacity={0.8}
>
{item.local_path ? (
<Image
source={{ uri: item.local_path }}
style={styles.gridImage}
resizeMode="cover"
onError={(e) => console.warn(`Image load failed [${item.source_service}]: ${item.local_path?.substring(0, 80)}`, e.nativeEvent?.error)}
/>
) : (
<View style={[styles.placeholder, { backgroundColor: theme.borderLight }]}>
<Text style={{ fontSize: 28 }}>📷</Text>
</View>
)}
{item.media_type === 'video' && (
<View style={styles.videoOverlay}>
<Text style={{ color: '#fff', fontSize: 20 }}>▶</Text>
</View>
)}
{renderSourceBadges(item)}
</TouchableOpacity>
);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item, i) => item.id?.toString() || item.source_id || i.toString()}
numColumns={columns}
contentContainerStyle={styles.gridContent}
refreshControl={refreshControl}
ListHeaderComponent={ListHeaderComponent}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
onEndReached={onEndReached}
onEndReachedThreshold={0.5}
/>
);
};
const styles = StyleSheet.create({
gridContent: { paddingBottom: 20 },
gridItem: {
margin: GRID_GAP / 2,
position: 'relative',
},
gridImage: { width: '100%', height: '100%' },
placeholder: {
width: '100%', height: '100%', justifyContent: 'center', alignItems: 'center',
},
videoOverlay: {
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
backgroundColor: 'rgba(0,0,0,0.25)', justifyContent: 'center', alignItems: 'center',
},
sourceBadge: {
position: 'absolute', bottom: 4, right: 4, backgroundColor: 'rgba(0,0,0,0.5)',
borderRadius: 8, paddingHorizontal: 4, paddingVertical: 2,
},
multiSourceBadge: {
position: 'absolute', bottom: 4, right: 4, backgroundColor: 'rgba(0,0,0,0.6)',
borderRadius: 8, paddingHorizontal: 4, paddingVertical: 2,
flexDirection: 'row', gap: 2,
},
});
export default PhotoGrid;