import { useLocalSearchParams, useRouter } from 'expo-router' import { StatusBar } from 'expo-status-bar' import React, { useEffect, useCallback, useState, useMemo } from 'react' import { Platform, FlatList, StyleSheet, StatusBar as RNStatusBar, View, ActivityIndicator, RefreshControl, Dimensions, } from 'react-native' import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context' 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' import { useCategoriesWithTags } from '@/hooks/use-categories-with-tags' import { useCategoryTemplates } from '@/hooks/use-category-templates' 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' import { root } from '@repo/core' import { TemplateSocialController } from '@repo/sdk' import { handleError } from '@/hooks/use-error' const NUM_COLUMNS = 3 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 HomeScreen() { const insets = useSafeAreaInsets() const router = useRouter() const params = useLocalSearchParams() // 获取积分余额 const { balance } = useUserBalance() // 分类数据加载 const { load: loadCategories, data: categoriesData, loading: categoriesLoading, error: categoriesError } = useCategoriesWithTags() const { load: loadActivates, data: activatesData } = useActivates() // 模板数据加载(分页) const { templates, loading: templatesLoading, loadingMore, execute: loadTemplates, loadMore, hasMore, } = useCategoryTemplates() // 标签导航状态 const { activeIndex, selectedCategoryId, tabs, selectTab, selectCategoryById, } = useTabNavigation({ categories: categoriesData?.categories || [], initialCategoryId: params.categoryId as string | undefined, }) // 吸顶状态 const { isSticky, tabsHeight, titleBarHeightRef, handleScroll, handleTabsLayout, handleTitleBarLayout, } = useStickyTabs() // 模板过滤 - 使用 useMemo 缓存 const { filteredTemplates } = useTemplateFilter({ templates, excludeVideo: true, }) // 初始化加载 useEffect(() => { loadCategories() loadActivates() }, []) // 当分类变化时加载模板 useEffect(() => { if (selectedCategoryId) { loadTemplates({ categoryId: selectedCategoryId }) } }, [selectedCategoryId]) // 路由参数同步 useEffect(() => { if (params.categoryId) { selectCategoryById(params.categoryId as string) } }, [params.categoryId]) // 同步模板的点赞数量到 store(用于初始化 store 中的数据) useEffect(() => { if (filteredTemplates.length > 0) { const likeCountMap: Record = {} filteredTemplates.forEach(template => { if (template.id && 'likeCount' in template && template.likeCount !== undefined) { likeCountMap[template.id] = template.likeCount } }) if (Object.keys(likeCountMap).length > 0) { setLikeCountStates(likeCountMap) } } }, [filteredTemplates, setLikeCountStates]) // 状态判断 - 使用 useMemo 缓存 // 统一 loading 状态:只要有任何一个在加载,就显示 loading const isLoading = useMemo(() => categoriesLoading || templatesLoading, [categoriesLoading, templatesLoading] ) const showEmptyState = useMemo(() => !isLoading && categoriesData?.categories?.length === 0, [isLoading, categoriesData?.categories?.length] ) const showEmptyTemplates = useMemo(() => !isLoading && !showEmptyState && filteredTemplates.length === 0, [isLoading, showEmptyState, filteredTemplates.length] ) const [refreshing, setRefreshing] = useState(false) // 下拉刷新处理 - 只刷新当前分类的模板 const handleRefresh = useCallback(async () => { if (!selectedCategoryId) return setRefreshing(true) await loadTemplates({ categoryId: selectedCategoryId }) setRefreshing(false) }, [selectedCategoryId, loadTemplates]) // 加载更多处理 const handleEndReached = useCallback(() => { if (!loadingMore && hasMore && !isLoading) { loadMore() } }, [loadingMore, hasMore, isLoading, loadMore]) // 导航处理 - 使用 useCallback memoize const handlePointsPress = useCallback(() => router.push('/membership' as any), [router]) const handleSearchPress = useCallback(() => router.push('/searchTemplate'), [router]) const handleActivityPress = useCallback((link: string) => router.push(link as any), [router]) const handleArrowPress = useCallback(() => router.push({ pathname: '/channels' as any, params: selectedCategoryId ? { categoryId: selectedCategoryId } : undefined, }), [router, selectedCategoryId]) const handleTemplatePress = useCallback((id: string) => router.push({ pathname: '/templateDetail' as any, params: { id }, }), [router]) // 使用 Store 中的点赞/收藏状态 const { setLiked, setFavorited, incrementLikeCount, decrementLikeCount, setLikeCountStates } = useTemplateSocialStore() // 获取 social controller const getSocialController = useCallback(() => { return root.get(TemplateSocialController) }, []) // 点赞处理 const handleLike = useCallback(async (id: string) => { setLiked(id, true) // 乐观更新状态 incrementLikeCount(id) // 乐观更新数量 try { const social = getSocialController() await handleError(() => social.like({ templateId: id })) console.log('Liked template:', id) } catch (e) { // 失败时回滚状态 setLiked(id, false) decrementLikeCount(id) console.error('Like failed:', e) } }, [setLiked, incrementLikeCount, decrementLikeCount, getSocialController]) // 取消点赞处理 const handleUnlike = useCallback(async (id: string) => { setLiked(id, false) // 乐观更新状态 decrementLikeCount(id) // 乐观更新数量 try { const social = getSocialController() await handleError(() => social.unlike({ templateId: id })) console.log('Unliked template:', id) } catch (e) { // 失败时回滚状态 setLiked(id, true) incrementLikeCount(id) console.error('Unlike failed:', e) } }, [setLiked, incrementLikeCount, decrementLikeCount, getSocialController]) // 收藏处理 const handleFavorite = useCallback(async (id: string) => { setFavorited(id, true) // 乐观更新 try { const social = getSocialController() await handleError(() => social.favorite({ templateId: id })) console.log('Favorited template:', id) } catch (e) { // 失败时回滚状态 setFavorited(id, false) console.error('Favorite failed:', e) } }, [setFavorited, getSocialController]) // 取消收藏处理 const handleUnfavorite = useCallback(async (id: string) => { setFavorited(id, false) // 乐观更新 try { const social = getSocialController() await handleError(() => social.unfavorite({ templateId: id })) console.log('Unfavorited template:', id) } catch (e) { // 失败时回滚状态 setFavorited(id, true) console.error('Unfavorite failed:', e) } }, [setFavorited, getSocialController]) // 渲染模板卡片 const renderTemplateItem = useCallback(({ item }: { item: typeof filteredTemplates[0] }) => { if (!item.id) return null return ( ) }, [handleTemplatePress, handleLike, handleUnlike, handleFavorite, handleUnfavorite]) // 提取 key const keyExtractor = useCallback((item: typeof filteredTemplates[0]) => item.id || '', []) // 列表头部组件 const ListHeaderComponent = useMemo(() => ( <> {/* 活动轮播图 */} {/* 标签导航 */} {!isLoading && !showEmptyState && ( handleTabsLayout(e.nativeEvent.layout.y, e.nativeEvent.layout.height)} style={isSticky ? { opacity: 0, height: tabsHeight } : undefined} > )} {/* 统一的加载状态 */} {isLoading && } {/* 错误状态 */} {categoriesError && ( loadCategories()} variant="error" /> )} {/* 空状态 - 分类数据为空 */} {showEmptyState && !categoriesError && ( loadCategories()} /> )} {/* 空状态 - 当前分类下没有模板 */} {showEmptyTemplates && ( loadTemplates({ categoryId: selectedCategoryId! })} /> )} ), [ activatesData?.activities, handleActivityPress, isLoading, showEmptyState, isSticky, tabsHeight, tabs, activeIndex, selectTab, handleArrowPress, categoriesError, showEmptyTemplates, selectedCategoryId, handleTabsLayout, loadCategories, loadTemplates, ]) // 列表底部组件 const ListFooterComponent = useMemo(() => { if (loadingMore) { return ( ) } return null }, [loadingMore]) // 获取有效的模板列表(过滤掉没有 id 的) const validTemplates = useMemo(() => filteredTemplates.filter(t => !!t.id), [filteredTemplates] ) // 只有在非加载状态且有数据时才显示列表 const showTemplateList = !isLoading && !showEmptyState && !showEmptyTemplates return ( {/* 标题栏 */} {/* 吸顶标签导航 */} {isSticky && !isLoading && !showEmptyState && ( )} handleScroll(e.nativeEvent.contentOffset.y)} scrollEventThrottle={Platform.OS === 'ios' ? 16 : 50} onEndReached={handleEndReached} onEndReachedThreshold={0.5} ListHeaderComponent={ListHeaderComponent} ListFooterComponent={ListFooterComponent} removeClippedSubviews={Platform.OS === 'android'} maxToRenderPerBatch={12} windowSize={5} initialNumToRender={12} getItemLayout={(_, index) => ({ length: CARD_WIDTH * 1.5, offset: CARD_WIDTH * 1.5 * Math.floor(index / NUM_COLUMNS), index, })} refreshControl={ } /> ) } 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, }, stickyTabsWrapper: { position: 'absolute', top: 0, left: 0, right: 0, zIndex: 100, backgroundColor: '#090A0B', }, templatesLoadingContainer: { paddingVertical: 40, alignItems: 'center', justifyContent: 'center', }, loadingMoreContainer: { paddingVertical: 20, alignItems: 'center', justifyContent: 'center', }, })