bw-expo-app/components/profile/content-gallery.tsx

229 lines
5.4 KiB
TypeScript

import React from 'react';
import { View, FlatList, ActivityIndicator, RefreshControl, StyleSheet, Image } from 'react-native';
import { VideoPlayer } from '@/components/video/video-player';
import { ThemedText } from '@/components/themed-text';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { useColorScheme } from '@/hooks/use-color-scheme';
import type { TemplateGeneration } from '@/lib/api/template-generations';
export function ContentGallery({
generations,
isRefreshing,
onRefresh,
isLoadingMore,
hasMore,
onLoadMore,
}: {
generations: TemplateGeneration[];
isRefreshing: boolean;
onRefresh: () => void;
isLoadingMore: boolean;
hasMore: boolean;
onLoadMore: () => void;
}) {
const colorScheme = useColorScheme();
const palette = colorScheme === 'dark' ? darkPalette : lightPalette;
const renderItem = ({ item }: { item: TemplateGeneration }) => (
<ContentItem palette={palette} generation={item} />
);
const renderFooter = () => {
if (isLoadingMore) {
return (
<View style={[styles.loadingMoreContainer, { backgroundColor: palette.background }]}>
<ActivityIndicator size="small" color={palette.accent} />
</View>
);
}
return null;
};
return (
<FlatList
data={generations}
renderItem={renderItem}
ListFooterComponent={renderFooter}
numColumns={2}
keyExtractor={(item) => item.id}
columnWrapperStyle={styles.columnWrapper}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={onRefresh}
tintColor={palette.accent}
colors={[palette.accent]}
/>
}
onEndReached={onLoadMore}
onEndReachedThreshold={0.5}
/>
);
}
function ContentItem({
palette,
generation,
}: {
palette: any;
generation: TemplateGeneration;
}) {
const formatDate = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
});
};
const renderMedia = () => {
const mediaUrl = generation.resultUrl[0];
if (generation.type === 'IMAGE') {
return (
<View style={styles.mediaContainer}>
<Image
source={{ uri: mediaUrl }}
style={styles.mediaImage}
resizeMode="cover"
/>
</View>
);
} else if (generation.type === 'VIDEO') {
return (
<View style={styles.mediaContainer}>
<VideoPlayer
source={{ uri: mediaUrl }}
style={styles.mediaVideo}
shouldPlay={false}
isLooping={true}
isMuted={true}
useNativeControls={false}
showPoster={true}
maxHeight={300}
/>
</View>
);
}
return null;
};
return (
<View
style={[
styles.contentItem,
{
backgroundColor: palette.surface,
borderColor: palette.border,
},
]}
>
{renderMedia()}
<View style={styles.contentInfo}>
<ThemedText
style={[styles.contentTitle, { color: palette.textPrimary }]}
numberOfLines={1}
>
{generation.template.title}
</ThemedText>
<View style={styles.contentMeta}>
<View style={styles.metaItem}>
<MaterialIcons name="schedule" size={14} color={palette.textSecondary} />
<ThemedText style={[styles.metaText, { color: palette.textSecondary }]}>
{formatDate(generation.createdAt)}
</ThemedText>
</View>
{generation.status !== 'completed' && (
<View style={styles.statusBadge}>
<ThemedText style={[styles.statusText, { color: palette.textSecondary }]}>
{generation.status}
</ThemedText>
</View>
)}
</View>
</View>
</View>
);
}
const darkPalette = {
background: '#050505',
surface: '#121216',
border: '#1D1E24',
textPrimary: '#F6F7FA',
textSecondary: '#8E9098',
accent: '#B7FF2F',
};
const lightPalette = {
background: '#F7F8FB',
surface: '#FFFFFF',
border: '#E2E5ED',
textPrimary: '#0F1320',
textSecondary: '#5E6474',
accent: '#405CFF',
};
const styles = StyleSheet.create({
columnWrapper: {
justifyContent: 'space-between',
},
contentItem: {
flex: 1,
borderRadius: 16,
borderWidth: 1,
marginBottom: 12,
overflow: 'hidden',
marginHorizontal: 6,
},
mediaContainer: {
width: '100%',
aspectRatio: 1,
backgroundColor: '#000',
},
mediaImage: {
width: '100%',
height: '100%',
},
mediaVideo: {
width: '100%',
height: '100%',
},
contentInfo: {
padding: 12,
},
contentTitle: {
fontSize: 15,
fontWeight: '600',
marginBottom: 8,
},
contentMeta: {
flexDirection: 'row',
alignItems: 'center',
},
metaItem: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 12,
},
metaText: {
fontSize: 12,
marginLeft: 4,
},
statusBadge: {
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 4,
backgroundColor: 'rgba(0, 0, 0, 0.1)',
},
statusText: {
fontSize: 11,
fontWeight: '600',
textTransform: 'capitalize' as const,
},
loadingMoreContainer: {
paddingVertical: 20,
alignItems: 'center',
},
});