import { ErrorView } from '@/components/ErrorView'; import { PageLayout } from '@/components/bestai/layout'; import VideoPlayer from '@/components/sker/video-player/video-player'; import { getTemplateGenerations, TemplateGeneration } from '@/lib/api/template-generations'; import { groupByDate } from '@/lib/utils/date'; import { distributeToColumns } from '@/lib/utils/media'; import { FlashList } from '@shopify/flash-list'; import { router } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; const LAYOUT_CONFIG = { VIDEO_HEIGHT: 280, IMAGE_HEIGHT: 240, DEFAULT_HEIGHT: 200, COLUMN_GAP: 16, PAGE_SIZE: 20, } as const; const ColumnRenderer = React.memo(({ items }: { items: TemplateGeneration[] }) => ( <> {items.map(item => { const itemHeight = item.type === 'VIDEO' ? LAYOUT_CONFIG.VIDEO_HEIGHT : LAYOUT_CONFIG.IMAGE_HEIGHT; return ( router.push(`/result?generationId=${item.id}`)} activeOpacity={0.9} > {item.resultUrl.map(uri => { return })} ); })} )); ColumnRenderer.displayName = 'ColumnRenderer'; interface DateGroupItem { date: string; items: TemplateGeneration[]; } export default function HistoryScreen() { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [allGenerations, setAllGenerations] = useState([]); const [refreshing, setRefreshing] = useState(false); const groupedData = useMemo(() => groupByDate(allGenerations), [allGenerations]); const flatData = useMemo(() => Object.entries(groupedData).map(([date, items]) => ({ date, items })), [groupedData] ); const fetchData = useCallback(async (pageNum: number = 1, isRefresh: boolean = false) => { try { if (isRefresh) { setRefreshing(true); } else if (pageNum === 1) { setLoading(true); } const response = await getTemplateGenerations({ page: String(pageNum), limit: String(LAYOUT_CONFIG.PAGE_SIZE) }); if (response?.success && response.data) { const newGenerations = response.data.generations as any[]; if (isRefresh || pageNum === 1) { setAllGenerations(newGenerations); setPage(1); } else { setAllGenerations(prev => [...prev, ...newGenerations]); } setHasMore(newGenerations.length === LAYOUT_CONFIG.PAGE_SIZE); setError(null); } else { setError('获取数据失败'); } } catch (err) { console.error('Failed to fetch data:', err); const errorMessage = err instanceof Error ? err.message : '获取数据失败'; if (!errorMessage.includes('401')) { setError(errorMessage); } } finally { setLoading(false); setRefreshing(false); } }, []); useEffect(() => { fetchData(1); }, [fetchData]); const onRefresh = useCallback(() => { fetchData(1, true); }, [fetchData]); const loadMore = useCallback(() => { if (hasMore && !loading && !refreshing) { const nextPage = page + 1; setPage(nextPage); fetchData(nextPage, false); } }, [hasMore, page, loading, refreshing, fetchData]); const renderItem = useCallback(({ item }: { item: DateGroupItem }) => { const { leftColumn, rightColumn } = distributeToColumns( item.items, LAYOUT_CONFIG.VIDEO_HEIGHT, LAYOUT_CONFIG.IMAGE_HEIGHT, LAYOUT_CONFIG.COLUMN_GAP ); return ( {item.date} ); }, []); const ListHeaderComponent = useCallback(() => ( Content Generation ), []); const ListFooterComponent = useCallback(() => { if (hasMore) { return ( 加载更多... ); } if (allGenerations.length > 0) { return ( 没有更多内容了 ); } return null; }, [hasMore, allGenerations.length]); const ListEmptyComponent = useCallback(() => ( 暂无生成记录 ), []); if (loading) { return ( 加载中... ); } if (error) { return ( fetchData(1)} /> ); } return ( item.date} onEndReached={loadMore} onEndReachedThreshold={0.5} refreshing={refreshing} onRefresh={onRefresh} ListHeaderComponent={ListHeaderComponent} ListFooterComponent={ListFooterComponent} ListEmptyComponent={ListEmptyComponent} contentContainerStyle={styles.flashListContent} /> ); } const styles = StyleSheet.create({ flashListContent: { paddingTop: 14, paddingBottom: 14, }, heading: { fontSize: 24, fontWeight: '600', color: '#FFFFFF', textAlign: 'center', letterSpacing: 0.4, marginBottom: 16, }, dateline: { marginTop: 16, marginBottom: 16, fontSize: 16, fontWeight: '500', color: '#EDEDED', }, gallery: { flexDirection: 'row', marginBottom: 24, }, leadingLane: { flex: 1, paddingRight: 12, }, trailingLane: { flex: 1, paddingLeft: 12, }, frame: { width: '100%', borderRadius: 16, marginBottom: 16, overflow: 'hidden', backgroundColor: '#1A1A1A', }, image: { width: '100%', }, placeholderImage: { backgroundColor: '#2A2A2A', justifyContent: 'center', alignItems: 'center', }, placeholderText: { fontSize: 48, }, videoPlaceholder: { backgroundColor: '#1A1A1A', justifyContent: 'center', alignItems: 'center', }, videoIcon: { fontSize: 48, marginBottom: 8, }, videoText: { fontSize: 14, color: '#9E9E9E', }, overlay: { position: 'absolute', bottom: 0, left: 0, right: 0, padding: 12, backgroundColor: 'rgba(0, 0, 0, 0.7)', borderBottomLeftRadius: 16, borderBottomRightRadius: 16, }, statusBadge: { flexDirection: 'row', alignItems: 'center', marginBottom: 4, }, statusDot: { width: 8, height: 8, borderRadius: 4, marginRight: 6, }, statusText: { fontSize: 12, color: '#FFFFFF', fontWeight: '500', }, templateName: { fontSize: 13, color: '#FFFFFF', fontWeight: '600', }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, loadingText: { marginTop: 16, fontSize: 16, color: '#FFFFFF', }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 48, }, emptyText: { fontSize: 16, color: '#9E9E9E', textAlign: 'center', }, dateGroup: { marginBottom: 24, }, loadingMoreContainer: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', paddingVertical: 20, gap: 12, }, loadingMoreText: { fontSize: 14, color: '#9E9E9E', }, noMoreContainer: { paddingVertical: 20, alignItems: 'center', }, noMoreText: { fontSize: 14, color: '#666666', }, });