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',
},
});