258 lines
7.5 KiB
TypeScript
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',
|
|
},
|
|
})
|