import { router } from 'expo-router'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { StyleSheet, TouchableOpacity, View, AppState } from 'react-native'; import { Image } from 'expo-image'; import { FlashList } from '@shopify/flash-list'; import VideoPlayer from '@/components/sker/video-player/video-player' import { CategoryTabs, FeatureCarousel, Header, PageLayout, SectionHeader, StatusBarSpacer, type FeatureItem } from '@/components/bestai'; import type { FeatureItem as FeatureItemType } from '@/components/bestai/feature-carousel'; import { useAuth } from '@/hooks/use-auth'; import { useAuthGuard } from '@/hooks/use-auth-guard'; import { getActivities, type Activity } from '@/lib/api/activities'; import { categoriesWithChildren } from '@/lib/api/categories-with-children'; import type { CategoryWithChildren } from '@/lib/types/template'; type ListItem = | { type: 'feature'; data: FeatureItem[] } | { type: 'section'; categoryId: string; title: string; tagName: string } | { type: 'template-row'; categoryId: string; items: Array<{ id: string; template: any; tagName: string }>; itemWidth: number }; type TemplateItemProps = { template: any; tagName: string; itemWidth: number; marginRight: number; onPress: (tagName: string) => void; }; const TemplateItem = memo(({ template, tagName, itemWidth, marginRight, onPress }: TemplateItemProps) => { const aspectRatio = template.aspectRatio || '3:4'; const [w, h] = aspectRatio.split(':'); const height = itemWidth * parseInt(h) / parseInt(w); const handlePress = useCallback(() => { onPress(tagName); }, [tagName, onPress]); return ( ); }); TemplateItem.displayName = 'TemplateItem'; type TemplateRowProps = { items: Array<{ id: string; template: any; tagName: string }>; itemWidth: number; onPress: (tagName: string) => void; }; const TemplateRow = memo(({ items, itemWidth, onPress }: TemplateRowProps) => ( {items.map((item, index) => { if (!item.template) return null; return ( ); })} )); TemplateRow.displayName = 'TemplateRow'; function coalesceText(...values: Array): string { for (const value of values) { const trimmed = (value ?? '').trim(); if (trimmed.length > 0) { return trimmed; } } return ''; } function translateActivity(activity: Activity): FeatureItem | null { const image = (activity.coverUrl || '').trim(); const title = (activity.titleEn || '').trim() || (activity.title || '').trim(); if (!image || !title) { return null; } const subtitle = (activity.descEn || '').trim() || (activity.desc || '').trim(); return { id: activity.id, title, subtitle, image, }; } export default function ExploreScreen() { const { isAuthenticated } = useAuth(); const { requireAuth } = useAuthGuard(); const [activeCategory, setActiveCategory] = useState(''); const [activityFeatures, setActivityFeatures] = useState([]); const [categoryCollection, setCategoryCollection] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [containerWidth, setContainerWidth] = useState(0); useEffect(() => { const subscription = AppState.addEventListener('change', (nextAppState) => { if (nextAppState === 'background' || nextAppState === 'inactive') { Image.clearMemoryCache(); if (__DEV__) { console.log('[MemoryManager] Cleared image cache on app background'); } } }); return () => { subscription.remove(); }; }, []); useEffect(() => { const hydrateFeatureCarousel = async () => { try { const activities = await getActivities({ isActive: 'true' }); const curatedFeatures = activities .map(translateActivity) .filter((feature: any): feature is FeatureItem => feature !== null); if (curatedFeatures.length > 0) { setActivityFeatures(curatedFeatures); } } catch (error) { const detail = error instanceof Error ? error.message : String(error); console.warn('FeatureCarousel activities feed unavailable:', detail); } }; hydrateFeatureCarousel(); }, []); useEffect(() => { let isActive = true; const hydrateCategories = async () => { try { const response = await categoriesWithChildren(); if (!isActive) { return; } const payload = response?.success && Array.isArray(response?.data) ? response.data : []; const curated = payload.filter((category: any) => category.id && (category.isActive ?? true)); setCategoryCollection(curated.length > 0 ? curated : payload); } catch (error) { if (isActive) { const detail = error instanceof Error ? error.message : String(error); console.warn('Categories feed unavailable:', detail); } } }; hydrateCategories(); return () => { isActive = false; }; }, [isAuthenticated]); useEffect(() => { if (categoryCollection.length === 0) { return; } const hasActive = categoryCollection.some((category) => category.id === activeCategory); if (!hasActive) { setActiveCategory(categoryCollection[0].id); } }, [categoryCollection, activeCategory]); const categoryOptions = useMemo(() => { if (categoryCollection.length === 0) { return []; } const options = categoryCollection .map((category) => { const label = coalesceText(category.nameEn, category.name); return label ? { id: category.id, label, } : null; }) .filter((category): category is { id: string; label: string } => category !== null); return options.length > 0 ? options : []; }, [categoryCollection]); const flattenedData = useMemo(() => { const result: ListItem[] = []; const itemWidth = containerWidth > 0 ? (containerWidth - 8) / 2 : 0; if (activityFeatures.length > 0) { result.push({ type: 'feature', data: activityFeatures }); } categoryCollection.forEach((category) => { result.push({ type: 'section', categoryId: category.id, title: category.nameEn || category.name || '', tagName: category.tags[0]?.name || '' }); const tags = category.tags || []; for (let i = 0; i < tags.length; i += 2) { const rowItems = tags.slice(i, i + 2) .map(tag => ({ id: tag.templates[0]?.id || tag.id, template: tag.templates[0], tagName: tag.nameEn || tag.name || '' })) .filter(item => item.template); if (rowItems.length > 0) { result.push({ type: 'template-row', categoryId: category.id, items: rowItems, itemWidth }); } } }); return result; }, [categoryCollection, activityFeatures, containerWidth]); const handleCategoryPress = useCallback((tagName: string) => { router.push(`/category/${tagName}` as any); }, []); const handleFeaturePress = useCallback((item: FeatureItemType) => { requireAuth(() => { // TODO: 实现功能逻辑 }); }, [requireAuth]); const handleTemplatePress = useCallback((templateId: string) => { router.push(`/templates/${templateId}` as any); }, []); const renderItem = useCallback(({ item }: { item: ListItem }) => { if (item.type === 'feature') { return ( ); } if (item.type === 'section') { return ( handleCategoryPress(item.tagName)} /> ); } if (item.type === 'template-row') { return ( ); } return null; }, [handleFeaturePress, handleCategoryPress, handleTemplatePress]); return (
{ setContainerWidth(event.nativeEvent.layout.width); }} > {containerWidth > 0 && ( { if (item.type === 'feature') return 'feature-carousel'; if (item.type === 'section') return `section-${item.categoryId}`; if (item.type === 'template-row') return `row-${item.categoryId}-${index}`; return `item-${index}`; }} /> )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, row: { flexDirection: 'row', paddingHorizontal: 0, marginBottom: 8, }, templateItem: { backgroundColor: '#2E3031', borderRadius: 12, overflow: 'hidden', }, videoPlayer: { borderRadius: 8 } }); /** ( )} /> */