198 lines
6.6 KiB
TypeScript
198 lines
6.6 KiB
TypeScript
import { useLocalSearchParams, useRouter } from 'expo-router'
|
|
import { StatusBar } from 'expo-status-bar'
|
|
import { useEffect } from 'react'
|
|
import {
|
|
Platform,
|
|
ScrollView,
|
|
StyleSheet,
|
|
StatusBar as RNStatusBar,
|
|
View
|
|
} from 'react-native'
|
|
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
|
|
import { TitleBar, HeroSlider, TabNavigation, TemplateGrid } from '@/components/blocks/home'
|
|
import ErrorState from '@/components/ErrorState'
|
|
import LoadingState from '@/components/LoadingState'
|
|
import { useActivates } from '@/hooks/use-activates'
|
|
import { useCategories } from '@/hooks/use-categories'
|
|
import { useStickyTabs } from '@/hooks/use-sticky-tabs'
|
|
import { useTabNavigation } from '@/hooks/use-tab-navigation'
|
|
import { useTemplateFilter } from '@/hooks/use-template-filter'
|
|
|
|
export default function HomeScreen() {
|
|
const insets = useSafeAreaInsets()
|
|
const router = useRouter()
|
|
const params = useLocalSearchParams()
|
|
|
|
// 数据加载
|
|
const { load: loadCategories, data: categoriesData, loading: categoriesLoading, error: categoriesError } = useCategories()
|
|
const { load: loadActivates, data: activatesData } = useActivates()
|
|
|
|
// 标签导航状态
|
|
const {
|
|
activeIndex,
|
|
selectedCategoryId,
|
|
currentCategory,
|
|
tabs,
|
|
selectTab,
|
|
selectCategoryById,
|
|
} = useTabNavigation({
|
|
categories: categoriesData?.categories || [],
|
|
initialCategoryId: params.categoryId as string | undefined,
|
|
})
|
|
|
|
// 吸顶状态
|
|
const {
|
|
isSticky,
|
|
tabsHeight,
|
|
titleBarHeightRef,
|
|
handleScroll,
|
|
handleTabsLayout,
|
|
handleTitleBarLayout,
|
|
} = useStickyTabs()
|
|
|
|
// 模板过滤
|
|
const { filteredTemplates } = useTemplateFilter({
|
|
templates: currentCategory?.templates || [],
|
|
excludeVideo: true,
|
|
})
|
|
|
|
// 初始化加载
|
|
useEffect(() => {
|
|
loadCategories()
|
|
loadActivates()
|
|
}, [])
|
|
|
|
// 路由参数同步
|
|
useEffect(() => {
|
|
if (params.categoryId) {
|
|
selectCategoryById(params.categoryId as string)
|
|
}
|
|
}, [params.categoryId])
|
|
|
|
// 状态判断
|
|
const showLoading = categoriesLoading
|
|
const showEmptyState = !categoriesLoading && categoriesData?.categories?.length === 0
|
|
const showEmptyTemplates = !categoriesLoading && !showEmptyState && filteredTemplates.length === 0
|
|
|
|
// 导航处理
|
|
const handlePointsPress = () => router.push('/membership' as any)
|
|
const handleSearchPress = () => router.push('/searchTemplate')
|
|
const handleActivityPress = (link: string) => router.push(link as any)
|
|
const handleArrowPress = () => router.push({
|
|
pathname: '/channels' as any,
|
|
params: selectedCategoryId ? { categoryId: selectedCategoryId } : undefined,
|
|
})
|
|
const handleTemplatePress = (id: string) => router.push({
|
|
pathname: '/templateDetail' as any,
|
|
params: { id },
|
|
})
|
|
|
|
return (
|
|
<SafeAreaView style={styles.container} edges={['top']}>
|
|
<StatusBar style="light" />
|
|
<RNStatusBar barStyle="light-content" />
|
|
|
|
{/* 标题栏 */}
|
|
<TitleBar
|
|
onPointsPress={handlePointsPress}
|
|
onSearchPress={handleSearchPress}
|
|
onLayout={handleTitleBarLayout}
|
|
/>
|
|
|
|
{/* 吸顶标签导航 */}
|
|
{isSticky && !showLoading && !showEmptyState && (
|
|
<View style={[styles.stickyTabsWrapper, { top: titleBarHeightRef.current + insets.top }]}>
|
|
<TabNavigation
|
|
tabs={tabs}
|
|
activeIndex={activeIndex}
|
|
onTabPress={selectTab}
|
|
showArrow={true}
|
|
onArrowPress={handleArrowPress}
|
|
isSticky={true}
|
|
/>
|
|
</View>
|
|
)}
|
|
|
|
<ScrollView
|
|
style={styles.scrollView}
|
|
contentContainerStyle={styles.scrollContent}
|
|
showsVerticalScrollIndicator={false}
|
|
onScroll={(e) => handleScroll(e.nativeEvent.contentOffset.y)}
|
|
scrollEventThrottle={Platform.OS === 'ios' ? 16 : 50}
|
|
>
|
|
{/* 活动轮播图 */}
|
|
<HeroSlider
|
|
activities={activatesData?.activities || []}
|
|
onActivityPress={handleActivityPress}
|
|
/>
|
|
|
|
{/* 标签导航 */}
|
|
{!showLoading && !showEmptyState && (
|
|
<View
|
|
onLayout={(e) => handleTabsLayout(e.nativeEvent.layout.y, e.nativeEvent.layout.height)}
|
|
style={isSticky ? { opacity: 0, height: tabsHeight } : undefined}
|
|
>
|
|
<TabNavigation
|
|
tabs={tabs}
|
|
activeIndex={activeIndex}
|
|
onTabPress={selectTab}
|
|
showArrow={true}
|
|
onArrowPress={handleArrowPress}
|
|
/>
|
|
</View>
|
|
)}
|
|
|
|
{/* 加载状态 */}
|
|
{showLoading && <LoadingState />}
|
|
|
|
{/* 错误状态 */}
|
|
{categoriesError && (
|
|
<ErrorState message="加载分类失败" onRetry={() => loadCategories()} />
|
|
)}
|
|
|
|
{/* 空状态 - 分类数据为空 */}
|
|
{showEmptyState && !categoriesError && (
|
|
<ErrorState message="暂无分类数据" onRetry={() => loadCategories()} />
|
|
)}
|
|
|
|
{/* 空状态 - 当前分类下没有模板 */}
|
|
{showEmptyTemplates && (
|
|
<ErrorState message="该分类暂无模板" onRetry={() => loadCategories()} />
|
|
)}
|
|
|
|
{/* 模板网格 */}
|
|
{!showLoading && !showEmptyState && !showEmptyTemplates && (
|
|
<TemplateGrid
|
|
templates={filteredTemplates}
|
|
onTemplatePress={handleTemplatePress}
|
|
/>
|
|
)}
|
|
</ScrollView>
|
|
</SafeAreaView>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#090A0B',
|
|
},
|
|
scrollView: {
|
|
flex: 1,
|
|
backgroundColor: '#090A0B',
|
|
},
|
|
scrollContent: {
|
|
flexGrow: 1,
|
|
backgroundColor: '#090A0B',
|
|
},
|
|
stickyTabsWrapper: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
zIndex: 100,
|
|
backgroundColor: '#090A0B',
|
|
},
|
|
})
|