import { FontAwesome, Fontisto, Ionicons } from '@expo/vector-icons'
import { useRouter } from 'expo-router'
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { ActivityIndicator } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler'
import { useAnimatedStyle } from 'react-native-reanimated'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import Svg, { Circle, Defs, Pattern, Rect } from 'react-native-svg'
import { imgPicker } from '@/@share/apis/imgPicker'
import { Block, Img, Input, Text, VideoBox } from '@/@share/components'
import BannerSection from '@/components/BannerSection'
import { useCategories } from '@/hooks/data/use-categories'
import { useRecommendedTemplates } from '@/hooks/data/use-recommended-templates'
import { useTemplates } from '@/hooks/data/use-templates'
import { type ApiError } from '@/lib/types'
import { screenHeight, screenWidth } from '@/utils'
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 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 && (
)}
)
}
/** =========================
* 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: { 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
)
})