diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 2929901..448b0d0 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -13,7 +13,7 @@ import { } from 'react-native' import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context' -import { TitleBar, HeroSlider, TabNavigation, TemplateCard } from '@/components/blocks/home' +import { TitleBar, HeroSlider, TabNavigation, TemplateCard, TemplateGrid } from '@/components/blocks/home' import ErrorState from '@/components/ErrorState' import LoadingState from '@/components/LoadingState' import { useActivates } from '@/hooks/use-activates' @@ -23,6 +23,8 @@ import { useStickyTabs } from '@/hooks/use-sticky-tabs' import { useTabNavigation } from '@/hooks/use-tab-navigation' import { useTemplateFilter } from '@/hooks/use-template-filter' import { useUserBalance } from '@/hooks/use-user-balance' +import { useTemplateLike, useTemplateFavorite } from '@/hooks' +import { useTemplateSocialStore } from '@/stores/templateSocialStore' const NUM_COLUMNS = 3 const HORIZONTAL_PADDING = 16 @@ -151,6 +153,37 @@ export default function HomeScreen() { params: { id }, }), [router]) + // 使用 Store 中的点赞/收藏状态 + const { setLiked, setFavorited } = useTemplateSocialStore() + + // 点赞处理 + const handleLike = useCallback((id: string) => { + const currentState = setLiked(id, true) // 乐观更新 + // TODO: 调用 API + console.log('Like template:', id) + }, [setLiked]) + + // 取消点赞处理 + const handleUnlike = useCallback((id: string) => { + const currentState = setLiked(id, false) // 乐观更新 + // TODO: 调用 API + console.log('Unlike template:', id) + }, [setLiked]) + + // 收藏处理 + const handleFavorite = useCallback((id: string) => { + const currentState = setFavorited(id, true) // 乐观更新 + // TODO: 调用 API + console.log('Favorite template:', id) + }, [setFavorited]) + + // 取消收藏处理 + const handleUnfavorite = useCallback((id: string) => { + const currentState = setFavorited(id, false) // 乐观更新 + // TODO: 调用 API + console.log('Unfavorite template:', id) + }, [setFavorited]) + // 渲染模板卡片 const renderTemplateItem = useCallback(({ item }: { item: typeof filteredTemplates[0] }) => { if (!item.id) return null @@ -167,9 +200,13 @@ export default function HomeScreen() { onPress={handleTemplatePress} liked={'isLiked' in item ? item.isLiked : undefined} favorited={'isFavorited' in item ? item.isFavorited : undefined} + onLike={handleLike} + onUnlike={handleUnlike} + onFavorite={handleFavorite} + onUnfavorite={handleUnfavorite} /> ) - }, [handleTemplatePress]) + }, [handleTemplatePress, handleLike, handleUnlike, handleFavorite, handleUnfavorite]) // 提取 key const keyExtractor = useCallback((item: typeof filteredTemplates[0]) => item.id || '', []) diff --git a/app/(tabs)/favorites.tsx b/app/favorites.tsx similarity index 85% rename from app/(tabs)/favorites.tsx rename to app/favorites.tsx index e61fdc4..c5aa500 100644 --- a/app/(tabs)/favorites.tsx +++ b/app/favorites.tsx @@ -17,6 +17,7 @@ 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 @@ -34,6 +35,9 @@ export default function FavoritesScreen() { const insets = useSafeAreaInsets() const router = useRouter() + // 使用 Store 中的点赞/收藏状态 + const { setLiked, setFavorited } = useTemplateSocialStore() + // 获取收藏列表 const { favorites, @@ -82,6 +86,28 @@ export default function FavoritesScreen() { }) }, [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(() => @@ -111,9 +137,13 @@ export default function FavoritesScreen() { onPress={handleTemplatePress} liked={template.isLiked} favorited={template.isFavorited} + onLike={handleLike} + onUnlike={handleUnlike} + onFavorite={handleFavorite} + onUnfavorite={handleUnfavorite} /> ) - }, [handleTemplatePress]) + }, [handleTemplatePress, handleLike, handleUnlike, handleFavorite, handleUnfavorite]) // 提取 key const keyExtractor = useCallback((item: typeof favorites[0]) => item.id, []) diff --git a/app/(tabs)/likes.test.tsx b/app/likes.test.tsx similarity index 100% rename from app/(tabs)/likes.test.tsx rename to app/likes.test.tsx diff --git a/app/(tabs)/likes.tsx b/app/likes.tsx similarity index 85% rename from app/(tabs)/likes.tsx rename to app/likes.tsx index 3bd9260..df1d65c 100644 --- a/app/(tabs)/likes.tsx +++ b/app/likes.tsx @@ -17,6 +17,7 @@ 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 @@ -35,6 +36,9 @@ export default function LikesScreen() { const router = useRouter() const [refreshing, setRefreshing] = useState(false) + // 使用 Store 中的点赞/收藏状态 + const { setLiked, setFavorited } = useTemplateSocialStore() + // 获取用户点赞列表 const { likes, @@ -74,6 +78,28 @@ export default function LikesScreen() { }) }, [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, @@ -107,9 +133,13 @@ export default function LikesScreen() { onPress={handleTemplatePress} liked={item.template.isLiked} favorited={item.template.isFavorited} + onLike={handleLike} + onUnlike={handleUnlike} + onFavorite={handleFavorite} + onUnfavorite={handleUnfavorite} /> ) - }, [handleTemplatePress]) + }, [handleTemplatePress, handleLike, handleUnlike, handleFavorite, handleUnfavorite]) // 提取 key const keyExtractor = useCallback((item: typeof likes[0]) => item.id || '', []) diff --git a/components/blocks/home/TemplateCard.tsx b/components/blocks/home/TemplateCard.tsx index 7bbca0b..5718cd4 100644 --- a/components/blocks/home/TemplateCard.tsx +++ b/components/blocks/home/TemplateCard.tsx @@ -4,6 +4,7 @@ import { Image } from 'expo-image' import { LinearGradient } from 'expo-linear-gradient' import { Ionicons } from '@expo/vector-icons' import { useTranslation } from 'react-i18next' +import { useTemplateSocialStore } from '@/stores/templateSocialStore' export interface TemplateCardProps { id?: string @@ -65,8 +66,8 @@ const TemplateCardComponent: React.FC = ({ aspectRatio: aspectRatioString, cardWidth, onPress, - liked, - favorited, + liked: likedProp, + favorited: favoritedProp, onLike, onUnlike, onFavorite, @@ -74,6 +75,14 @@ const TemplateCardComponent: React.FC = ({ testID, }) => { const { i18n } = useTranslation() + + // 获取 Store 中的状态(用于本地状态覆盖) + const { isLiked: isLikedInStore, isFavorited: isFavoritedInStore } = useTemplateSocialStore() + + // 合并 props 状态和 store 状态:store 优先(乐观更新) + const liked = id !== undefined ? isLikedInStore(id) ?? likedProp : likedProp + const favorited = id !== undefined ? isFavoritedInStore(id) ?? favoritedProp : favoritedProp + const aspectRatio = useMemo(() => parseAspectRatio(aspectRatioString), [aspectRatioString]) const imageUri = useMemo(() => getImageUri(webpPreviewUrl, previewUrl, coverImageUrl), [webpPreviewUrl, previewUrl, coverImageUrl]) diff --git a/components/blocks/home/TemplateGrid.tsx b/components/blocks/home/TemplateGrid.tsx index 09908e9..64e9400 100644 --- a/components/blocks/home/TemplateGrid.tsx +++ b/components/blocks/home/TemplateGrid.tsx @@ -9,6 +9,10 @@ export type Template = CategoryTemplate | TemplateDetail export interface TemplateGridProps { templates: Template[] onTemplatePress: (id: string) => void + onLike?: (id: string) => void + onUnlike?: (id: string) => void + onFavorite?: (id: string) => void + onUnfavorite?: (id: string) => void numColumns?: number horizontalPadding?: number cardGap?: number @@ -34,6 +38,10 @@ export function calculateCardWidth( const TemplateGridComponent: React.FC = ({ templates, onTemplatePress, + onLike, + onUnlike, + onFavorite, + onUnfavorite, numColumns = 3, horizontalPadding = 16, cardGap = 5, @@ -75,6 +83,10 @@ const TemplateGridComponent: React.FC = ({ onPress={onTemplatePress} liked={'isLiked' in template ? template.isLiked : undefined} favorited={'isFavorited' in template ? template.isFavorited : undefined} + onLike={onLike} + onUnlike={onUnlike} + onFavorite={onFavorite} + onUnfavorite={onUnfavorite} testID={`template-card-${template.id}`} /> ))} diff --git a/components/blocks/ui/FavoriteButton.tsx b/components/blocks/ui/FavoriteButton.tsx index be31add..b28e67d 100644 --- a/components/blocks/ui/FavoriteButton.tsx +++ b/components/blocks/ui/FavoriteButton.tsx @@ -26,8 +26,7 @@ const FavoriteButtonComponent: React.FC = ({ } }, [loading, onPress]) - // 缓存样式计算 - const iconStyle = useMemo(() => [styles.icon, { fontSize: size }], [size]) + // 缓存样式计算 - 移除不必要的 iconStyle const containerStyle = useMemo( () => [styles.container, loading && styles.disabled], [loading] @@ -45,7 +44,7 @@ const FavoriteButtonComponent: React.FC = ({ style={containerStyle} > - + {count !== undefined && ( {count} )} @@ -70,9 +69,6 @@ const styles = StyleSheet.create({ alignItems: 'center', gap: 4, }, - icon: { - lineHeight: (size: number) => size, - }, count: { color: '#8E8E93', fontWeight: '600',