mirror of
https://github.com/mudabbir-ahmad/Media-Aggregation-and-Sorting-System.git
synced 2026-10-07 19:40:20 +00:00
121 lines
3.6 KiB
JavaScript
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;
|
|
|