import React, { memo, useCallback, useMemo, useState, useEffect } from 'react' import { Block, Text, Img, Input, VideoBox } from '@/@share/components' import { imgPicker } from '@/@share/apis/imgPicker' import Svg, { Defs, Pattern, Rect, Circle } from 'react-native-svg' import { ScrollView } from 'react-native-gesture-handler' import { useAnimatedStyle } from 'react-native-reanimated' import { FontAwesome, Fontisto, Ionicons } from '@expo/vector-icons' import { screenHeight, screenWidth } from '@/utils' import { useRouter } from 'expo-router' import { useRecommendedTemplates, RecommendedTemplate } from '@/hooks/data/use-recommended-templates' import { useCategories } from '@/hooks/data/use-categories' import { useTemplates } from '@/hooks/data/use-templates' import { ActivityIndicator } from 'react-native' import { ApiError } from '@/lib/types' import { useSafeAreaInsets } from 'react-native-safe-area-context' const BACKGROUND_VIDEOS = [ 'https://cdn.roasmax.cn/material/b46f380532e14cf58dd350dbacc7c34a.mp4', 'https://cdn.roasmax.cn/material/992e6c5d940c42feb71c27e556b754c0.mp4', 'https://cdn.roasmax.cn/material/e4947477843f4067be7c37569a33d17b.mp4', ] /** ========================= * Small memo components * ========================= */ type BannerProps = { bgVideo: string } const Banner = memo(function Banner({ bgVideo }) { return ( CREATE ) }) type HeaderProps = { onSearch: () => void } const Header = memo(function Header({ onSearch }) { return ( 创造终端 GEN_STUDIO ) }) type UploadCardProps = { variant: 'me' | 'friend' img: string onPick: () => void } const UploadCard = memo(function UploadCard({ variant, img, onPick }) { const isMe = variant === 'me' return ( {isMe ? ( ) : ( 朋友 )} {img ? ( ) : ( {isMe ? ( + ) : ( + )} 点击上传 )} ) }) type UploadSectionProps = { meImg: string friendImg: string onPickMe: () => void onPickFriend: () => void } const UploadSection = memo(function UploadSection({ meImg, friendImg, onPickMe, onPickFriend }) { return ( ) }) type PromptSectionProps = { prompt: string onChangePrompt: (v: string) => void } const PromptSection = memo(function PromptSection({ prompt, onChangePrompt }) { return ( 提示词 ) }) type Template = { id: string name: string image: string type: 'video' data?: any } type TemplateItemProps = { item: Template itemWidth: number isSelected: boolean onSelect: () => void } const TemplateItem = memo(function TemplateItem({ item, itemWidth, isSelected, onSelect }) { return ( {isSelected && } {isSelected && } {item.name} {item.type === 'video' && ( )} ) }) type CategoryChipProps = { name: string isSelected: boolean onSelect: () => void } const CategoryChip = memo(function CategoryChip({ name, isSelected, onSelect }) { return ( {name} ) }) type CategoryFilterProps = { categories: Array<{ id: string; name: string }> selectedId: string onSelect: (id: string) => void } const CategoryFilter = memo(function CategoryFilter({ categories, selectedId, onSelect }) { return ( onSelect('')} /> {categories.map((cat) => ( onSelect(cat.id)} /> ))} ) }) type TemplateSectionProps = { templates: Template[] selectedTemplateId: string onSelectTemplate: (t: Template) => void onRandom: () => void loading?: boolean error?: ApiError | null onRetry?: () => void } const TemplateSection = memo(function TemplateSection({ templates, selectedTemplateId, onSelectTemplate, onRandom, loading, error, onRetry }) { const style = useAnimatedStyle(() => ({ transform: [{ skewX: '-6deg' }], backgroundColor: '#FFE500', })) const itemWidth = useMemo(() => { return Math.floor((screenWidth - 24 - 12 * 2) / 3) }, []) return ( 视频模版 换一波 {loading ? ( ) : error ? ( 加载失败 {onRetry && ( 重试 )} ) : templates.length === 0 ? ( 暂无模板 ) : ( {templates.map((item) => { const isSelected = selectedTemplateId === item.id return onSelectTemplate(item)} /> })} )} ) }) type GenerateSectionProps = { onGenerate: () => void } const GenerateSection = memo(function GenerateSection({ onGenerate }) { const insets = useSafeAreaInsets() return ( {/* 左侧文字 */} 立即生成 开始创作 {/* 右侧 Goo 标签 */} 3 Goo ) }) /** ========================= * Entry page * ========================= */ export default function Index() { const router = useRouter() const env = process.env.EXPO_PUBLIC_ENV const [prompt, setPrompt] = useState(`${env} update`) const [selectedTemplateId, setSelectedTemplateId] = useState('') const [selectedCategoryId, setSelectedCategoryId] = useState('') const [meImg, setMeImg] = useState('') const [friendImg, setFriendImg] = useState('') const [bgVideo] = useState(() => BACKGROUND_VIDEOS[Math.floor(Math.random() * BACKGROUND_VIDEOS.length)]) const recommendedTemplates = useRecommendedTemplates() const categories = useCategories() const templates = useTemplates() useEffect(() => { recommendedTemplates.execute({ isActive: true }) categories.load({ isActive: true }) }, []) useEffect(() => { const params = selectedCategoryId ? { categoryId: selectedCategoryId, isActive: true } : { isActive: true } templates.execute(params) }, [selectedCategoryId]) const displayTemplates = useMemo(() => { const recommended = recommendedTemplates.data?.templates || [] const regular = templates.data?.templates || [] const all = selectedCategoryId ? regular : [...recommended.map((r: RecommendedTemplate) => r.template).filter(Boolean), ...regular] return all.map((t: any): Template => ({ id: t.id, name: t.title, image: t.coverImageUrl, type: 'video' as const, data: t, })) }, [recommendedTemplates.data, templates.data, selectedCategoryId]) const categoryList = useMemo(() => { return (categories.data?.categories || []).map((c) => ({ id: c.id, name: c.name, })) }, [categories.data]) const selectedTemplate = useMemo(() => { return displayTemplates.find((t) => t.id === selectedTemplateId) }, [displayTemplates, selectedTemplateId]) useEffect(() => { if (displayTemplates.length > 0 && !selectedTemplateId) { setSelectedTemplateId(displayTemplates[0].id) } }, [displayTemplates, selectedTemplateId]) const handleSearch = useCallback(() => { router.push('/searchTemplate') }, [router]) const handleGenerate = useCallback(() => { if (!selectedTemplate) return setTimeout(() => { }, 2000) }, [selectedTemplate]) const pickImage = useCallback(async (target: 'me' | 'friend') => { const [uri] = (await imgPicker({ maxImages: 1, resultType: 'uri' })) as string[] if (!uri) return if (target === 'me') setMeImg(uri) else setFriendImg(uri) }, []) const handleRandom = useCallback(() => { if (displayTemplates.length === 0) return const random = displayTemplates[Math.floor(Math.random() * displayTemplates.length)] setSelectedTemplateId(random.id) }, [displayTemplates]) const handleSelectTemplate = useCallback((t: Template) => { setSelectedTemplateId(t.id) }, []) const handleSelectCategory = useCallback((id: string) => { setSelectedCategoryId(id) setSelectedTemplateId('') }, []) const onPickMe = useCallback(() => pickImage('me'), [pickImage]) const onPickFriend = useCallback(() => pickImage('friend'), [pickImage]) const isLoading = templates.loading || recommendedTemplates.loading const hasError = templates.error || recommendedTemplates.error const handleRetry = useCallback(() => { if (selectedCategoryId) { templates.refetch({ categoryId: selectedCategoryId, isActive: true }) } else { recommendedTemplates.refetch({ isActive: true }) templates.refetch({ isActive: true }) } }, [selectedCategoryId, templates, recommendedTemplates]) return (
{categoryList.length > 0 && ( )} ) }