import { useState, useEffect, useCallback, useRef } from 'react' import { View, Text, StyleSheet, ScrollView, Dimensions, Pressable, StatusBar as RNStatusBar, RefreshControl, ActivityIndicator, NativeScrollEvent, NativeSyntheticEvent, } from 'react-native' import { StatusBar } from 'expo-status-bar' import { SafeAreaView } from 'react-native-safe-area-context' import { Image } from 'expo-image' import { useRouter } from 'expo-router' import { useTranslation } from 'react-i18next' import { PanGestureHandler } from 'react-native-gesture-handler' import { PointsIcon, SearchIcon, SettingsIcon } from '@/components/icon' import EditProfileDrawer from '@/components/drawer/EditProfileDrawer' import Dropdown from '@/components/ui/dropdown' import Toast from '@/components/ui/Toast' import { signOut, useSession } from '@/lib/auth' import { useTemplateGenerations, useUserFavorites, useUserLikes, type TemplateGeneration } from '@/hooks' import { MySkeleton } from '@/components/skeleton/MySkeleton' import { TabNavigation } from '@/components/blocks/home/TabNavigation' import { useSwipeNavigation } from '@/hooks/use-swipe-navigation' import { useUserBalance } from '@/hooks/use-user-balance' const { width: screenWidth } = Dimensions.get('window') const GALLERY_GAP = 2 const GALLERY_HORIZONTAL_PADDING = 0 const GALLERY_ITEM_SIZE = Math.floor( (screenWidth - GALLERY_HORIZONTAL_PADDING * 2 - GALLERY_GAP * 2) / 3 ) type TabType = 'works' | 'favorites' | 'likes' // 获取作品封面图 - Webp优先 const getCoverUrl = (item: TemplateGeneration) => item.webpPreviewUrl || item.resultUrl?.[0] || item.template?.coverImageUrl export default function My() { const router = useRouter() const { t, i18n } = useTranslation() const [editDrawerVisible, setEditDrawerVisible] = useState(false) // Tab 状态 const [activeTab, setActiveTab] = useState('works') const [activeTabIndex, setActiveTabIndex] = useState(0) // 获取积分余额 const { balance } = useUserBalance() // 获取当前登录用户信息 const { data: session } = useSession() const userName = session?.user?.name || session?.user?.username || '用户' const [profileName, setProfileName] = useState(userName) // 当 session 变化时更新用户名 useEffect(() => { if (session?.user?.name || session?.user?.username) { setProfileName(session?.user?.name || session?.user?.username || '用户') } }, [session]) // 使用 useTemplateGenerations hook 获取用户作品列表 const { generations, loading, loadingMore, refetch, loadMore, hasMore, } = useTemplateGenerations() // 获取收藏列表 const { favorites, loading: favoritesLoading, loadingMore: favoritesLoadingMore, refetch: favoritesRefetch, loadMore: favoritesLoadMore, hasMore: favoritesHasMore, } = useUserFavorites() // 获取点赞列表 const { likes, loading: likesLoading, loadingMore: likesLoadingMore, refetch: likesRefetch, loadMore: likesLoadMore, hasMore: likesHasMore, } = useUserLikes() // Tab 配置 const tabs = [ t('my.tabs.works'), t('my.tabs.favorites'), t('my.tabs.likes') ] // 处理 Tab 切换 const handleTabPress = useCallback((index: number) => { setActiveTabIndex(index) if (index === 0) setActiveTab('works') else if (index === 1) setActiveTab('favorites') else if (index === 2) setActiveTab('likes') }, []) // 左右滑动切换 Tab const handleSwipeLeft = useCallback(() => { if (activeTabIndex < tabs.length - 1) { handleTabPress(activeTabIndex + 1) } }, [activeTabIndex, tabs.length, handleTabPress]) const handleSwipeRight = useCallback(() => { if (activeTabIndex > 0) { handleTabPress(activeTabIndex - 1) } }, [activeTabIndex, handleTabPress]) const { handleGestureEvent, handleGestureStateChange } = useSwipeNavigation({ onSwipeLeft: handleSwipeLeft, onSwipeRight: handleSwipeRight, canSwipeLeft: activeTabIndex < tabs.length - 1, canSwipeRight: activeTabIndex > 0, }) // 调试日志 useEffect(() => { console.log('📊 作品列表状态:', { 总数: generations.length, 加载中: loading, 加载更多中: loadingMore, 还有更多: hasMore }) }, [generations.length, loading, loadingMore, hasMore]) // 初始化加载数据 useEffect(() => { refetch({ page: 1, limit: 20 }) favoritesRefetch() likesRefetch() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) // 下拉刷新状态 const [refreshing, setRefreshing] = useState(false) const [favoritesRefreshing, setFavoritesRefreshing] = useState(false) const [likesRefreshing, setLikesRefreshing] = useState(false) // 防止重复触发加载更多 const isLoadingMoreRef = useRef(false) const isFavoritesLoadingMoreRef = useRef(false) const isLikesLoadingMoreRef = useRef(false) // 下拉刷新处理 const onRefresh = useCallback(async () => { setRefreshing(true) try { await refetch({ page: 1, limit: 20 }) } finally { setRefreshing(false) } }, [refetch]) // 收藏Tab下拉刷新处理 const onFavoritesRefresh = useCallback(async () => { setFavoritesRefreshing(true) try { await favoritesRefetch() } finally { setFavoritesRefreshing(false) } }, [favoritesRefetch]) // 点赞Tab下拉刷新处理 const onLikesRefresh = useCallback(async () => { setLikesRefreshing(true) try { await likesRefetch() } finally { setLikesRefreshing(false) } }, [likesRefetch]) // 加载更多处理 const handleScroll = useCallback((event: NativeSyntheticEvent) => { const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent const paddingToBottom = 200 // 距离底部200px时触发加载更多 const isCloseToBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom // 使用 ref 防止重复触发 if (isCloseToBottom && !loadingMore && hasMore && !loading && !isLoadingMoreRef.current) { console.log('🔄 触发加载更多', { layoutHeight: layoutMeasurement.height, offsetY: contentOffset.y, contentHeight: contentSize.height, distance: contentSize.height - (layoutMeasurement.height + contentOffset.y) }) isLoadingMoreRef.current = true loadMore().finally(() => { isLoadingMoreRef.current = false }) } }, [loadingMore, hasMore, loadMore, loading]) // 收藏Tab加载更多处理 const handleFavoritesScroll = useCallback((event: NativeSyntheticEvent) => { const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent const paddingToBottom = 200 const isCloseToBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom if (isCloseToBottom && !favoritesLoadingMore && favoritesHasMore && !favoritesLoading && !isFavoritesLoadingMoreRef.current) { console.log('🔄 收藏Tab触发加载更多') isFavoritesLoadingMoreRef.current = true favoritesLoadMore().finally(() => { isFavoritesLoadingMoreRef.current = false }) } }, [favoritesLoadingMore, favoritesHasMore, favoritesLoadMore, favoritesLoading]) // 点赞Tab加载更多处理 const handleLikesScroll = useCallback((event: NativeSyntheticEvent) => { const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent const paddingToBottom = 200 const isCloseToBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom if (isCloseToBottom && !likesLoadingMore && likesHasMore && !likesLoading && !isLikesLoadingMoreRef.current) { console.log('🔄 点赞Tab触发加载更多') isLikesLoadingMoreRef.current = true likesLoadMore().finally(() => { isLikesLoadingMoreRef.current = false }) } }, [likesLoadingMore, likesHasMore, likesLoadMore, likesLoading]) // 处理设置菜单选择 const handleSettingsSelect = async (value: string) => { if (value === 'changePassword') { router.push('/changePassword' as any) } else if (value === 'language') { // 切换语言 const newLang = i18n.language === 'zh-CN' ? 'en-US' : 'zh-CN' i18n.changeLanguage(newLang) } else if (value === 'logout') { // 退出登录 console.log('🚪 点击退出登录') const confirmText = i18n.language === 'zh-CN' ? '确定' : 'OK' const cancelText = i18n.language === 'zh-CN' ? '取消' : 'Cancel' const message = i18n.language === 'zh-CN' ? '确定要退出登录吗?' : 'Are you sure you want to logout?' Toast.showActionSheet({ itemList: [message, confirmText, cancelText] }).then(async (index) => { // index 1 是确定按钮 if (index === 1) { console.log('🚪 开始执行退出登录') try { Toast.showLoading({ title: i18n.language === 'zh-CN' ? '退出中...' : 'Logging out...' }) // 调用 better-auth 的 signOut 方法 await signOut() Toast.hideLoading() console.log('✅ 退出登录成功,跳转到登录页') Toast.show(i18n.language === 'zh-CN' ? '退出登录成功' : 'Logged out successfully') // 跳转到登录页面(注意:路由是 /auth 不是 /login) router.replace('/auth') } catch (error) { Toast.hideLoading() console.error('❌ 退出登录失败:', error) Toast.show(i18n.language === 'zh-CN' ? '退出登录失败,请稍后重试' : 'Failed to logout, please try again later') } } }).catch(() => { console.log('❌ 取消退出登录') }) } } // 设置菜单选项 const getLanguageLabel = () => { if (i18n.language === 'zh-CN') { return t('my.languageSwitch') } else { return t('my.languageSwitchEn') } } const settingsOptions = [ { label: t('my.changePassword'), value: 'changePassword' }, { label: getLanguageLabel(), value: 'language' }, { label: t('my.logout'), value: 'logout' }, ] return ( {/* 顶部积分与设置 */} router.push('/membership' as any)} > {balance} handleSettingsSelect(value)} renderTrigger={(selectedOption, isOpen, toggle) => ( )} dropdownStyle={{ minWidth: 160, right: 10, backgroundColor: '#2A2A2A80', }} /> {/* 个人信息区 */} {profileName} ID {session?.user?.id?.slice(0, 8) || '--------'} { console.log('[my.tsx] Edit Profile button pressed') setEditDrawerVisible(true) }} > {t('my.editProfile')} {/* Tab 导航 */} {/* 作品九宫格 - 包裹在手势处理器中 */} {activeTab === 'works' && loading ? ( ) : activeTab === 'works' ? ( } > {generations.map((item, index) => ( { if (item.status === 'completed') { router.push({ pathname: '/generationRecord' as any, params: { id: item.id }, }) } }} disabled={item.status !== 'completed'} > {/* 遮罩:非完成状态 */} {item.status !== 'completed' && ( )} {/* 数量角标:已完成且有结果 */} {item.status === 'completed' && (item.resultUrl?.length || 0) > 0 && ( {item.resultUrl?.length || 1} )} {/* 状态角标 */} {item.status === 'running' && ( {t('my.generating')} )} {item.status === 'pending' && ( {t('my.queuing')} )} ))} {/* 加载更多指示器 */} {loadingMore && ( )} {/* 空状态提示 */} {!loading && generations.length === 0 && ( {t('my.noWorks')} )} ) : activeTab === 'favorites' && favoritesLoading ? ( ) : activeTab === 'favorites' ? ( } > {favorites.length === 0 && !favoritesLoading ? ( {t('my.noFavorites')} ) : ( <> {favorites.map((item, index) => ( { if (item.template?.id) { router.push({ pathname: '/templateDetail' as any, params: { id: item.template.id }, }) } }} > ))} {/* 加载更多指示器 */} {favoritesLoadingMore && ( )} )} ) : activeTab === 'likes' && likesLoading ? ( ) : activeTab === 'likes' ? ( } > {likes.length === 0 && !likesLoading ? ( {t('my.noLikes')} ) : ( <> {likes.map((item, index) => ( { if (item.template?.id) { router.push({ pathname: '/templateDetail' as any, params: { id: item.template.id }, }) } }} > ))} {/* 加载更多指示器 */} {likesLoadingMore && ( )} )} ) : null} {/* 编辑资料抽屉 */} setEditDrawerVisible(false)} initialName={profileName} initialAvatar={session?.user?.image} onSave={(data) => { setProfileName(data.name) }} /> ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#090A0B', }, gestureContainer: { flex: 1, }, topBar: { paddingHorizontal: GALLERY_HORIZONTAL_PADDING, paddingTop: 19, paddingRight: 16, flexDirection: 'row', justifyContent: 'flex-end', backgroundColor: '#090A0B', gap: 12, }, pointsPill: { flexDirection: 'row', alignItems: 'center', gap: 1, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 100, backgroundColor: '#1C1E22', }, pointsPillText: { color: '#FFCF00', fontSize: 12, fontWeight: '600', }, scrollView: { flex: 1, backgroundColor: '#090A0B', }, scrollContent: { backgroundColor: '#090A0B', paddingHorizontal: GALLERY_HORIZONTAL_PADDING, paddingBottom: 100, }, profileSection: { flexDirection: 'row', alignItems: 'center', paddingLeft: 16, paddingRight: 16, marginTop: 20, marginBottom: 32, backgroundColor: '#090A0B', }, avatar: { width: 64, height: 64, borderRadius: 32, overflow: 'hidden', marginRight: 16, }, profileInfo: { flex: 1, }, profileName: { color: '#F5F5F5', fontSize: 18, fontWeight: '600', marginBottom: 4, }, profileSubTitle: { color: '#FFFFFF', fontSize: 12, opacity: 0.7, }, editButton: { paddingHorizontal: 8, paddingVertical: 6, borderRadius: 6, backgroundColor: '#1C1E22', }, editButtonText: { color: '#FFFFFF', fontSize: 12, }, galleryGrid: { flexDirection: 'row', flexWrap: 'wrap', marginBottom: 10, }, galleryItem: { width: GALLERY_ITEM_SIZE, aspectRatio: 1, overflow: 'hidden', backgroundColor: '#1C1E22', position: 'relative', }, galleryItemMarginRight: { marginRight: GALLERY_GAP, }, galleryItemMarginBottom: { marginBottom: GALLERY_GAP, }, galleryImage: { width: '100%', height: undefined, aspectRatio: 1, }, generatingOverlay: { ...StyleSheet.absoluteFillObject, backgroundColor: '#00000080', }, counterBadge: { position: 'absolute', right: 8, bottom: 8, paddingHorizontal: 10, paddingVertical: 1, borderRadius: 6, backgroundColor: '#16181B1A', }, counterText: { color: '#FFFFFF', fontSize: 10, fontWeight: '600', }, generatingBadge: { position: 'absolute', left: 8, bottom: 8, }, generatingBadgeText: { color: '#F5F5F5', fontSize: 9, fontWeight: '500', }, loadingMoreContainer: { width: '100%', paddingVertical: 20, alignItems: 'center', justifyContent: 'center', }, emptyState: { width: '100%', alignItems: 'center', justifyContent: 'center', paddingVertical: 60, }, emptyStateText: { color: '#FFFFFF80', fontSize: 14, }, })