import { useState, useRef, useMemo, useCallback, useEffect } from 'react' import { View, Text, StyleSheet, Pressable, Dimensions, FlatList, Platform, ActivityIndicator, } from 'react-native' import { Image } from 'expo-image' import { useTranslation } from 'react-i18next' import BottomSheet, { BottomSheetView, BottomSheetBackdrop } from '@gorhom/bottom-sheet' import { CloseIcon } from '@/components/icon' import { loomart } from '@/lib/auth' const { width: screenWidth } = Dimensions.get('window') type DrawerType = 'ai-record' | 'recent' | 'project-all' | 'project-face' interface AIGenerationRecordDrawerProps { visible: boolean onClose: () => void onSelectImage?: (imageUri: string) => void type?: DrawerType } interface ImageData { id: string uri: string } export default function AIGenerationRecordDrawer({ visible, onClose, onSelectImage, type = 'ai-record', }: AIGenerationRecordDrawerProps) { const { t } = useTranslation() const bottomSheetRef = useRef(null) const [images, setImages] = useState([]) const [loading, setLoading] = useState(false) const [hasMore, setHasMore] = useState(true) const [page, setPage] = useState(1) const snapPoints = useMemo(() => ['98%'], []) const limit = 20 // 根据类型获取数据 const fetchData = useCallback(async (pageNum: number = 1) => { if (loading) return try { setLoading(true) switch (type) { case 'ai-record': // 获取已完成的 AI 生成记录 const result = await loomart.templateGeneration.list({ page: pageNum, limit, status: 'completed', }) const aiImages: ImageData[] = (result.items || []).flatMap((item: any) => { const resultUrls = item.resultUrl || [] return resultUrls.map((url: string, idx: number) => ({ id: `${item.id}-${idx}`, uri: url, })) }) setImages((prev) => (pageNum === 1 ? aiImages : [...prev, ...aiImages])) setHasMore(aiImages.length === limit) break case 'recent': // 获取最近使用(按更新时间排序) const recentResult = await loomart.templateGeneration.list({ page: pageNum, limit, }) const recentImages: ImageData[] = (recentResult.items || []).flatMap((item: any) => { const resultUrls = item.resultUrl || [] return resultUrls.map((url: string, idx: number) => ({ id: `${item.id}-${idx}`, uri: url, })) }) setImages((prev) => (pageNum === 1 ? recentImages : [...prev, ...recentImages])) setHasMore(recentImages.length === limit) break case 'project-all': // 获取所有项目 const projects = await loomart.project.list({ page: pageNum, limit, }) const projectImages: ImageData[] = (projects.items || []) .filter((p: any) => p.resultUrl) .map((p: any) => ({ id: p.id, uri: p.resultUrl, })) setImages((prev) => (pageNum === 1 ? projectImages : [...prev, ...projectImages])) setHasMore(projectImages.length === limit) break case 'project-face': // 获取人脸标签的项目(假设标签名为 'face' 或具体标签ID) // 如果需要先获取人脸标签ID,可以调用 loomart.tag.list() 或 loomart.category.list() const faceProjects = await loomart.project.list({ page: pageNum, limit, tagIds: ['face'], // 替换为实际的人脸标签ID }) const faceImages: ImageData[] = (faceProjects.items || []) .filter((p: any) => p.resultUrl) .map((p: any) => ({ id: p.id, uri: p.resultUrl, })) setImages((prev) => (pageNum === 1 ? faceImages : [...prev, ...faceImages])) setHasMore(faceImages.length === limit) break } } catch (error) { console.error('获取图片失败:', error) } finally { setLoading(false) } }, [type, loading]) // 类型改变时重置并重新获取数据 useEffect(() => { if (visible) { setImages([]) setPage(1) setHasMore(true) fetchData(1) } }, [visible, type]) // 滚动加载更多 const handleLoadMore = useCallback(() => { if (!loading && hasMore) { const nextPage = page + 1 setPage(nextPage) fetchData(nextPage) } }, [loading, hasMore, page, fetchData]) useEffect(() => { if (visible) { bottomSheetRef.current?.expand() } else { bottomSheetRef.current?.close() } }, [visible]) const handleSheetChanges = useCallback((index: number) => { if (index === -1) { onClose() } }, [onClose]) const handleImageSelect = (imageUri: string) => { onSelectImage?.(imageUri) onClose() } const title = useMemo(() => { switch (type) { case 'ai-record': return t('aiGenerationRecord.title') case 'recent': return t('aiGenerationRecord.recentUsed') case 'project-all': return t('aiGenerationRecord.projectAll') case 'project-face': return t('aiGenerationRecord.projectFace') default: return '' } }, [type, t]) const renderBackdrop = useCallback( (props: any) => ( ), [] ) const renderImageItem = ({ item, index }: { item: ImageData; index: number }) => { const gap = 2 const itemWidth = (screenWidth - gap * 2) / 3 return ( handleImageSelect(item.uri)} android_ripple={{ color: 'rgba(255, 255, 255, 0.1)' }} > ) } const renderFooter = () => { if (!loading) return null return ( ) } const renderEmpty = () => { if (loading) return null return ( {t('common.noData')} ) } return ( {/* 顶部标题栏 */} {title} {/* 图片网格 */} item.id.toString()} numColumns={3} contentContainerStyle={styles.imageGrid} showsVerticalScrollIndicator={false} onEndReached={handleLoadMore} onEndReachedThreshold={0.5} ListFooterComponent={renderFooter} ListEmptyComponent={renderEmpty} removeClippedSubviews={Platform.OS === 'android'} maxToRenderPerBatch={Platform.OS === 'ios' ? 10 : 5} updateCellsBatchingPeriod={Platform.OS === 'ios' ? 50 : 100} initialNumToRender={Platform.OS === 'ios' ? 15 : 10} windowSize={Platform.OS === 'ios' ? 10 : 5} getItemLayout={(data, index) => { const gap = 2 const itemWidth = (screenWidth - gap * 2) / 3 const rowIndex = Math.floor(index / 3) return { length: itemWidth, offset: rowIndex * (itemWidth + gap), index, } }} /> ) } const styles = StyleSheet.create({ bottomSheetBackground: { backgroundColor: '#16181B', }, handleIndicator: { backgroundColor: '#666666', }, container: { flex: 1, backgroundColor: '#16181B', paddingTop: 12, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingHorizontal: 16, paddingBottom: 12, position: 'relative', }, title: { color: '#F5F5F5', fontSize: 15, fontWeight: '600', }, closeButton: { position: 'absolute', right: 16, width: 24, height: 24, alignItems: 'center', justifyContent: 'center', zIndex: 10, }, imageGrid: { paddingHorizontal: 0, paddingBottom: Platform.OS === 'ios' ? 20 : 16, }, imageItem: { // aspectRatio = width / height // 1 : 1.3 (width : height) => 1 / 1.3 aspectRatio: 1 / 1.3, overflow: 'hidden', backgroundColor: '#262A31', }, image: { width: '100%', height: '100%', }, footerLoader: { paddingVertical: 20, alignItems: 'center', }, emptyContainer: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingTop: 100, }, emptyText: { color: '#666666', fontSize: 14, }, })