fix: bug
This commit is contained in:
256
app/likes.tsx
Normal file
256
app/likes.tsx
Normal file
@@ -0,0 +1,256 @@
|
||||
import { useRouter } from 'expo-router'
|
||||
import { StatusBar } from 'expo-status-bar'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
FlatList,
|
||||
StyleSheet,
|
||||
StatusBar as RNStatusBar,
|
||||
View,
|
||||
ActivityIndicator,
|
||||
RefreshControl,
|
||||
Dimensions,
|
||||
Platform,
|
||||
} from 'react-native'
|
||||
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
|
||||
import { TemplateCard } from '@/components/blocks/home'
|
||||
import ErrorState from '@/components/ErrorState'
|
||||
import LoadingState from '@/components/LoadingState'
|
||||
import { useUserLikes } from '@/hooks'
|
||||
import { useTemplateSocialStore } from '@/stores/templateSocialStore'
|
||||
|
||||
const NUM_COLUMNS = 2
|
||||
const HORIZONTAL_PADDING = 16
|
||||
const CARD_GAP = 5
|
||||
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 LikesScreen() {
|
||||
const insets = useSafeAreaInsets()
|
||||
const router = useRouter()
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
|
||||
// 使用 Store 中的点赞/收藏状态
|
||||
const { setLiked, setFavorited } = useTemplateSocialStore()
|
||||
|
||||
// 获取用户点赞列表
|
||||
const {
|
||||
likes,
|
||||
loading,
|
||||
loadingMore,
|
||||
error,
|
||||
execute,
|
||||
refetch,
|
||||
loadMore,
|
||||
hasMore,
|
||||
} = useUserLikes()
|
||||
|
||||
// 初始化加载
|
||||
useEffect(() => {
|
||||
execute()
|
||||
}, [])
|
||||
|
||||
// 下拉刷新处理
|
||||
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 showEmptyState = useMemo(() =>
|
||||
!loading && !error && likes.length === 0,
|
||||
[loading, error, likes.length]
|
||||
)
|
||||
|
||||
const showErrorState = useMemo(() =>
|
||||
!loading && !!error,
|
||||
[loading, error]
|
||||
)
|
||||
|
||||
const showTemplateList = useMemo(() =>
|
||||
!loading && !error && likes.length > 0,
|
||||
[loading, error, likes.length]
|
||||
)
|
||||
|
||||
// 渲染模板卡片
|
||||
const renderTemplateItem = useCallback(({ item }: { item: typeof likes[0] }) => {
|
||||
if (!item.template?.id) return null
|
||||
|
||||
return (
|
||||
<TemplateCard
|
||||
id={item.template.id}
|
||||
title={item.template.title || ''}
|
||||
titleEn={item.template.titleEn}
|
||||
previewUrl={item.template.previewUrl}
|
||||
webpPreviewUrl={item.template.webpPreviewUrl}
|
||||
coverImageUrl={item.template.coverImageUrl}
|
||||
aspectRatio={item.template.aspectRatio}
|
||||
cardWidth={CARD_WIDTH}
|
||||
onPress={handleTemplatePress}
|
||||
liked={item.template.isLiked}
|
||||
favorited={item.template.isFavorited}
|
||||
onLike={handleLike}
|
||||
onUnlike={handleUnlike}
|
||||
onFavorite={handleFavorite}
|
||||
onUnfavorite={handleUnfavorite}
|
||||
/>
|
||||
)
|
||||
}, [handleTemplatePress, handleLike, handleUnlike, handleFavorite, handleUnfavorite])
|
||||
|
||||
// 提取 key
|
||||
const keyExtractor = useCallback((item: typeof likes[0]) => item.id || '', [])
|
||||
|
||||
// 列表头部组件
|
||||
const ListHeaderComponent = useMemo(() => {
|
||||
// 加载状态
|
||||
if (loading) {
|
||||
return <LoadingState />
|
||||
}
|
||||
|
||||
// 错误状态
|
||||
if (showErrorState) {
|
||||
return (
|
||||
<ErrorState
|
||||
message="加载失败,请下拉刷新重试"
|
||||
onRetry={() => refetch()}
|
||||
variant="error"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// 空状态
|
||||
if (showEmptyState) {
|
||||
return (
|
||||
<ErrorState
|
||||
message="暂无点赞"
|
||||
onRetry={() => execute()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}, [loading, showErrorState, showEmptyState, refetch, execute])
|
||||
|
||||
// 列表底部组件
|
||||
const ListFooterComponent = useMemo(() => {
|
||||
if (loadingMore) {
|
||||
return (
|
||||
<View style={styles.loadingMoreContainer} testID="loading-more">
|
||||
<ActivityIndicator size="small" color="#FFFFFF" />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}, [loadingMore])
|
||||
|
||||
// 获取有效的点赞列表(过滤掉没有 template.id 的)
|
||||
const validLikes = useMemo(() =>
|
||||
likes.filter(item => !!item.template?.id),
|
||||
[likes]
|
||||
)
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['top']}>
|
||||
<StatusBar style="light" />
|
||||
<RNStatusBar barStyle="light-content" />
|
||||
|
||||
<FlatList
|
||||
data={showTemplateList ? validLikes : []}
|
||||
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}
|
||||
removeClippedSubviews={Platform.OS === 'android'}
|
||||
maxToRenderPerBatch={12}
|
||||
windowSize={5}
|
||||
initialNumToRender={12}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
tintColor="#9966FF"
|
||||
colors={['#9966FF', '#FF6699', '#FF9966']}
|
||||
progressBackgroundColor="#1C1E22"
|
||||
progressViewOffset={10}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</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',
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user