diff --git a/app/(tabs)/history.tsx b/app/(tabs)/history.tsx index cb53927..0545ad9 100644 --- a/app/(tabs)/history.tsx +++ b/app/(tabs)/history.tsx @@ -1,14 +1,41 @@ import { ThemedView } from '@/components/themed-view'; import { StatusBar } from 'expo-status-bar'; -import React, { useState, useEffect } from 'react'; -import { ActivityIndicator, Image, ScrollView, StyleSheet, Text, View } from 'react-native'; +import React, { useState, useEffect, useCallback } from 'react'; +import { + ActivityIndicator, + Image, + ScrollView, + StyleSheet, + Text, + View, + RefreshControl, + Platform, + TouchableOpacity +} from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { getTemplateGenerations, TemplateGeneration } from '@/lib/api/template-generations'; +import { router } from 'expo-router'; interface GroupedData { [date: string]: TemplateGeneration[]; } +/** + * 根据文件 URL 后缀名判断媒体类型 + */ +function getMediaType(url: string): 'image' | 'video' | 'unknown' { + if (!url) return 'unknown'; + + const extension = url.split('.').pop()?.toLowerCase() || ''; + + const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg']; + const videoExts = ['mp4', 'webm', 'avi', 'mov', 'mkv', 'flv', 'm4v']; + + if (imageExts.includes(extension)) return 'image'; + if (videoExts.includes(extension)) return 'video'; + return 'unknown'; +} + const groupByDate = (data: TemplateGeneration[]): GroupedData => { const groups: GroupedData = {}; @@ -115,30 +142,127 @@ const getStatusText = (status: string): string => { export default function HistoryScreen() { const [groupedData, setGroupedData] = useState({}); const [loading, setLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); + const [loadingMore, setLoadingMore] = useState(false); const [error, setError] = useState(null); + const [page, setPage] = useState(1); + const [hasMore, setHasMore] = useState(true); + const [allGenerations, setAllGenerations] = useState([]); + + const fetchData = async (pageNum: number = 1, isRefresh: boolean = false) => { + try { + if (isRefresh) { + setRefreshing(true); + } else if (pageNum === 1) { + setLoading(true); + } else { + setLoadingMore(true); + } + + const response = await getTemplateGenerations({ page: pageNum, limit: 20 }); + + if (response.success && response.data) { + const newGenerations = response.data.generations; + + if (isRefresh || pageNum === 1) { + setAllGenerations(newGenerations); + setPage(1); + } else { + setAllGenerations(prev => [...prev, ...newGenerations]); + } + + setHasMore(newGenerations.length === 20); + const grouped = groupByDate(isRefresh || pageNum === 1 ? newGenerations : [...allGenerations, ...newGenerations]); + setGroupedData(grouped); + setError(null); + } else { + setError('获取数据失败'); + } + } catch (err) { + console.error('Failed to fetch data:', err); + setError(err instanceof Error ? err.message : '获取数据失败'); + } finally { + setLoading(false); + setRefreshing(false); + setLoadingMore(false); + } + }; useEffect(() => { - const fetchData = async () => { - try { - setLoading(true); - const response = await getTemplateGenerations({ limit: 100 }); - - if (response.success && response.data) { - const grouped = groupByDate(response.data.generations); - setGroupedData(grouped); - } else { - setError('获取数据失败'); - } - } catch (err) { - setError(err instanceof Error ? err.message : '获取数据失败'); - } finally { - setLoading(false); - } - }; - - fetchData(); + fetchData(1); }, []); + const onRefresh = useCallback(() => { + fetchData(1, true); + }, []); + + const loadMore = useCallback(() => { + if (!loadingMore && hasMore && !loading) { + const nextPage = page + 1; + setPage(nextPage); + fetchData(nextPage, false); + } + }, [loadingMore, hasMore, page, loading]); + + const handleScroll = (event: any) => { + const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent; + const paddingToBottom = 20; + const isCloseToBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom; + + if (isCloseToBottom && !loadingMore && hasMore) { + loadMore(); + } + }; + + const renderMediaItem = (item: TemplateGeneration) => { + const url = item.resultUrl && item.resultUrl.length > 0 ? item.resultUrl[0] : null; + + if (!url) { + return ( + + + {item.type === 'VIDEO' ? '🎬' : item.type === 'IMAGE' ? '🖼️' : '📝'} + + + ); + } + + const mediaType = getMediaType(url); + + if (mediaType === 'video') { + if (Platform.OS === 'web') { + return ( +