expo-popcore-app/app/favorites.tsx

258 lines
7.5 KiB
TypeScript

import { StatusBar } from 'expo-status-bar'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import {
FlatList,
StyleSheet,
StatusBar as RNStatusBar,
View,
ActivityIndicator,
RefreshControl,
Dimensions,
} from 'react-native'
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
import { useFocusEffect } from '@react-navigation/native'
import { useRouter } from 'expo-router'
import { TitleBar, TemplateCard } from '@/components/blocks/home'
import ErrorState from '@/components/ErrorState'
import LoadingState from '@/components/LoadingState'
import { useUserFavorites } from '@/hooks/use-user-favorites'
import { useTemplateSocialStore } from '@/stores/templateSocialStore'
const NUM_COLUMNS = 2
const HORIZONTAL_PADDING = 16
const CARD_GAP = 8
const SCREEN_WIDTH = Dimensions.get('window').width
// 计算卡片宽度
const calculateCardWidth = () => {
return (SCREEN_WIDTH - HORIZONTAL_PADDING * 2 - CARD_GAP * (NUM_COLUMNS - 1)) / NUM_COLUMNS
}
const CARD_WIDTH = calculateCardWidth()
export default function FavoritesScreen() {
const insets = useSafeAreaInsets()
const router = useRouter()
// 使用 Store 中的点赞/收藏状态
const { setLiked, setFavorited } = useTemplateSocialStore()
// 获取收藏列表
const {
favorites,
loading,
loadingMore,
error,
execute,
refetch,
loadMore,
hasMore,
} = useUserFavorites()
// 页面聚焦时刷新数据
useFocusEffect(
useCallback(() => {
refetch()
}, [refetch])
)
// 初始加载
useEffect(() => {
execute()
}, [])
const [refreshing, setRefreshing] = useState(false)
// 下拉刷新处理
const handleRefresh = useCallback(async () => {
setRefreshing(true)
await refetch()
setRefreshing(false)
}, [refetch])
// 加载更多处理
const handleEndReached = useCallback(() => {
if (!loadingMore && hasMore && !loading) {
loadMore()
}
}, [loadingMore, hasMore, loading, loadMore])
// 导航处理
const handleTemplatePress = useCallback((id: string) => {
router.push({
pathname: '/templateDetail' as any,
params: { id },
})
}, [router])
// 点赞/取消点赞处理
const handleLike = useCallback((id: string) => {
setLiked(id, true)
console.log('Like template:', id)
}, [setLiked])
const handleUnlike = useCallback((id: string) => {
setLiked(id, false)
console.log('Unlike template:', id)
}, [setLiked])
// 收藏/取消收藏处理
const handleFavorite = useCallback((id: string) => {
setFavorited(id, true)
console.log('Favorite template:', id)
}, [setFavorited])
const handleUnfavorite = useCallback((id: string) => {
setFavorited(id, false)
console.log('Unfavorite template:', id)
}, [setFavorited])
// 状态判断
const isLoading = useMemo(() => loading, [loading])
const showEmptyState = useMemo(() =>
!isLoading && favorites.length === 0 && !error,
[isLoading, favorites.length, error]
)
const showErrorState = useMemo(() =>
!isLoading && error,
[isLoading, error]
)
// 渲染模板卡片
const renderTemplateItem = useCallback(({ item }: { item: typeof favorites[0] }) => {
if (!item.template?.id) return null
const template = item.template
return (
<TemplateCard
id={template.id}
title={template.title}
titleEn={template.titleEn}
previewUrl={template.previewUrl}
webpPreviewUrl={template.webpPreviewUrl}
coverImageUrl={template.coverImageUrl}
aspectRatio={template.aspectRatio}
cardWidth={CARD_WIDTH}
onPress={handleTemplatePress}
liked={template.isLiked}
favorited={template.isFavorited}
onLike={handleLike}
onUnlike={handleUnlike}
onFavorite={handleFavorite}
onUnfavorite={handleUnfavorite}
/>
)
}, [handleTemplatePress, handleLike, handleUnlike, handleFavorite, handleUnfavorite])
// 提取 key
const keyExtractor = useCallback((item: typeof favorites[0]) => item.id, [])
// 列表头部组件
const ListHeaderComponent = useMemo(() => {
if (isLoading) {
return <LoadingState />
}
if (showErrorState) {
return (
<ErrorState
message="加载收藏失败"
onRetry={() => execute()}
variant="error"
/>
)
}
if (showEmptyState) {
return (
<ErrorState
message="暂无收藏"
onRetry={() => execute()}
variant="empty"
/>
)
}
return null
}, [isLoading, showErrorState, showEmptyState, execute])
// 列表底部组件
const ListFooterComponent = useMemo(() => {
if (loadingMore) {
return (
<View style={styles.loadingMoreContainer}>
<ActivityIndicator size="small" color="#FFFFFF" />
</View>
)
}
return null
}, [loadingMore])
// 只有在非加载状态且有数据时才显示列表
const showTemplateList = !isLoading && !showEmptyState && !showErrorState
return (
<SafeAreaView style={styles.container} edges={['top']}>
<StatusBar style="light" />
<RNStatusBar barStyle="light-content" />
{/* 标题栏 */}
<TitleBar
points={0}
onPointsPress={() => {}}
onSearchPress={() => {}}
/>
<FlatList
data={showTemplateList ? favorites : []}
keyExtractor={keyExtractor}
renderItem={renderTemplateItem}
numColumns={NUM_COLUMNS}
columnWrapperStyle={styles.columnWrapper}
style={styles.scrollView}
contentContainerStyle={[styles.scrollContent, { paddingBottom: 60 + insets.bottom + 20 }]}
showsVerticalScrollIndicator={false}
onEndReached={handleEndReached}
onEndReachedThreshold={0.5}
ListHeaderComponent={ListHeaderComponent}
ListFooterComponent={ListFooterComponent}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
tintColor="#9966FF"
colors={['#9966FF', '#FF6699', '#FF9966']}
progressBackgroundColor="#1C1E22"
/>
}
/>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#090A0B',
},
scrollView: {
flex: 1,
backgroundColor: '#090A0B',
},
scrollContent: {
paddingHorizontal: HORIZONTAL_PADDING,
paddingBottom: 20,
backgroundColor: '#090A0B',
},
columnWrapper: {
gap: CARD_GAP,
marginBottom: CARD_GAP,
},
loadingMoreContainer: {
paddingVertical: 20,
alignItems: 'center',
justifyContent: 'center',
},
})