This commit is contained in:
imeepos 2025-12-26 18:26:11 +08:00
parent 935b1e92a3
commit 9afe55b2d9
2 changed files with 14 additions and 82 deletions

View File

@ -7,13 +7,11 @@ import { useAnimatedStyle } from 'react-native-reanimated'
import { FontAwesome, Fontisto, Ionicons } from '@expo/vector-icons' import { FontAwesome, Fontisto, Ionicons } from '@expo/vector-icons'
import { screenHeight, screenWidth } from '@/utils' import { screenHeight, screenWidth } from '@/utils'
import { useRouter } from 'expo-router' 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 { useTemplates } from '@/hooks/data/use-templates'
import { ActivityIndicator } from 'react-native' import { ActivityIndicator } from 'react-native'
import { ApiError } from '@/lib/types' import { ApiError } from '@/lib/types'
import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useSafeAreaInsets } from 'react-native-safe-area-context'
const CATEGORY_ID = `cmjmpwop30009dhdpu3qyfvuo`
const BACKGROUND_VIDEOS = [ const BACKGROUND_VIDEOS = [
'https://cdn.roasmax.cn/material/b46f380532e14cf58dd350dbacc7c34a.mp4', 'https://cdn.roasmax.cn/material/b46f380532e14cf58dd350dbacc7c34a.mp4',
'https://cdn.roasmax.cn/material/992e6c5d940c42feb71c27e556b754c0.mp4', 'https://cdn.roasmax.cn/material/992e6c5d940c42feb71c27e556b754c0.mp4',
@ -201,39 +199,6 @@ const TemplateItem = memo<TemplateItemProps>(function TemplateItem({ item, itemW
) )
}) })
type CategoryChipProps = {
name: string
isSelected: boolean
onSelect: () => void
}
const CategoryChip = memo<CategoryChipProps>(function CategoryChip({ name, isSelected, onSelect }) {
return (
<Block
className={`skew-x-[-6deg] border-[2px] px-[12px] py-[4px] ${isSelected ? 'border-accent bg-accent' : 'border-black bg-white'}`}
onClick={onSelect}
>
<Text className={`text-[10px] font-[900] italic ${isSelected ? 'text-black' : 'text-black/60'}`}>{name}</Text>
</Block>
)
})
type CategoryFilterProps = {
categories: Array<{ id: string; name: string }>
selectedId: string
onSelect: (id: string) => void
}
const CategoryFilter = memo<CategoryFilterProps>(function CategoryFilter({ categories, selectedId, onSelect }) {
return (
<Block className="mt-[16px]">
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 8 }}>
<CategoryChip name="全部" isSelected={selectedId === ''} onSelect={() => onSelect('')} />
{categories.map((cat) => (
<CategoryChip key={cat.id} name={cat.name} isSelected={selectedId === cat.id} onSelect={() => onSelect(cat.id)} />
))}
</ScrollView>
</Block>
)
})
type TemplateSectionProps = { type TemplateSectionProps = {
templates: Template[] templates: Template[]
@ -334,33 +299,19 @@ export default function Index() {
const [prompt, setPrompt] = useState(`${env} update`) const [prompt, setPrompt] = useState(`${env} update`)
const [selectedTemplateId, setSelectedTemplateId] = useState('') const [selectedTemplateId, setSelectedTemplateId] = useState('')
const [selectedCategoryId, setSelectedCategoryId] = useState('')
const [meImg, setMeImg] = useState('') const [meImg, setMeImg] = useState('')
const [friendImg, setFriendImg] = useState('') const [friendImg, setFriendImg] = useState('')
const [bgVideo] = useState(() => BACKGROUND_VIDEOS[Math.floor(Math.random() * BACKGROUND_VIDEOS.length)]) const [bgVideo] = useState(() => BACKGROUND_VIDEOS[Math.floor(Math.random() * BACKGROUND_VIDEOS.length)])
const recommendedTemplates = useRecommendedTemplates()
const categories = useCategories()
const templates = useTemplates() const templates = useTemplates()
useEffect(() => { useEffect(() => {
recommendedTemplates.execute({ isActive: true }) templates.execute({ categoryId: CATEGORY_ID, page: 1, limit: 12, sortBy: 'createdAt', sortOrder: 'desc' })
categories.load({ isActive: true })
}, []) }, [])
useEffect(() => {
const params = selectedCategoryId ? { categoryId: selectedCategoryId, isActive: true } : { isActive: true }
templates.execute(params)
}, [selectedCategoryId])
const displayTemplates = useMemo(() => { const displayTemplates = useMemo(() => {
const recommended = recommendedTemplates.data?.templates || []
const regular = templates.data?.templates || [] const regular = templates.data?.templates || []
const all = regular
const all = selectedCategoryId
? regular
: [...recommended.map((r: RecommendedTemplate) => r.template).filter(Boolean), ...regular]
return all.map((t: any): Template => ({ return all.map((t: any): Template => ({
id: t.id, id: t.id,
name: t.title, name: t.title,
@ -368,14 +319,7 @@ export default function Index() {
type: 'video' as const, type: 'video' as const,
data: t, data: t,
})) }))
}, [recommendedTemplates.data, templates.data, selectedCategoryId]) }, [templates.data])
const categoryList = useMemo(() => {
return (categories.data?.categories || []).map((c) => ({
id: c.id,
name: c.name,
}))
}, [categories.data])
const selectedTemplate = useMemo(() => { const selectedTemplate = useMemo(() => {
return displayTemplates.find((t) => t.id === selectedTemplateId) return displayTemplates.find((t) => t.id === selectedTemplateId)
@ -413,25 +357,15 @@ export default function Index() {
setSelectedTemplateId(t.id) setSelectedTemplateId(t.id)
}, []) }, [])
const handleSelectCategory = useCallback((id: string) => {
setSelectedCategoryId(id)
setSelectedTemplateId('')
}, [])
const onPickMe = useCallback(() => pickImage('me'), [pickImage]) const onPickMe = useCallback(() => pickImage('me'), [pickImage])
const onPickFriend = useCallback(() => pickImage('friend'), [pickImage]) const onPickFriend = useCallback(() => pickImage('friend'), [pickImage])
const isLoading = templates.loading || recommendedTemplates.loading const isLoading = templates.loading
const hasError = templates.error || recommendedTemplates.error const hasError = templates.error
const handleRetry = useCallback(() => { const handleRetry = useCallback(() => {
if (selectedCategoryId) { templates.refetch({ categoryId: CATEGORY_ID, page: 1, limit: 12, sortBy: 'createdAt', sortOrder: 'desc' })
templates.refetch({ categoryId: selectedCategoryId, isActive: true }) }, [])
} else {
recommendedTemplates.refetch({ isActive: true })
templates.refetch({ isActive: true })
}
}, [selectedCategoryId, templates, recommendedTemplates])
return ( return (
<Block className="relative flex-1 flex-col overflow-visible bg-black"> <Block className="relative flex-1 flex-col overflow-visible bg-black">
@ -444,9 +378,6 @@ export default function Index() {
<Block className="px-16px relative z-10 flex-1"> <Block className="px-16px relative z-10 flex-1">
<UploadSection meImg={meImg} friendImg={friendImg} onPickMe={onPickMe} onPickFriend={onPickFriend} /> <UploadSection meImg={meImg} friendImg={friendImg} onPickMe={onPickMe} onPickFriend={onPickFriend} />
<PromptSection prompt={prompt} onChangePrompt={setPrompt} /> <PromptSection prompt={prompt} onChangePrompt={setPrompt} />
{categoryList.length > 0 && (
<CategoryFilter categories={categoryList} selectedId={selectedCategoryId} onSelect={handleSelectCategory} />
)}
<TemplateSection <TemplateSection
templates={displayTemplates} templates={displayTemplates}
selectedTemplateId={selectedTemplateId} selectedTemplateId={selectedTemplateId}

View File

@ -7,7 +7,6 @@ import { LinearGradient } from 'expo-linear-gradient'
import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated' import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'
import { router, useRouter } from 'expo-router' import { router, useRouter } from 'expo-router'
import { IOS_UNIVERSAL_LINK } from '@/app.constants'
import { FlashList } from '@shopify/flash-list' import { FlashList } from '@shopify/flash-list'
import { screenHeight, screenWidth } from '@/utils' import { screenHeight, screenWidth } from '@/utils'
import { cn } from '@/utils/cn' import { cn } from '@/utils/cn'
@ -26,6 +25,8 @@ const BACKGROUND_VIDEOS = [
'https://cdn.roasmax.cn/material/e4947477843f4067be7c37569a33d17b.mp4', 'https://cdn.roasmax.cn/material/e4947477843f4067be7c37569a33d17b.mp4',
] ]
const CATEGORY_ID = `cat_iw83x5bg54fmjgvciju`
type MediaItem = { type MediaItem = {
id: string id: string
type: 'image' | 'video' type: 'image' | 'video'
@ -406,7 +407,7 @@ export default function Sync() {
newItems = data.favorites.filter((fav: GetUserFavoritesResponse['favorites'][number]) => fav.template && !fav.template.isDeleted).map((fav: GetUserFavoritesResponse['favorites'][number]) => transformTemplateToMediaItem(fav.template!)) newItems = data.favorites.filter((fav: GetUserFavoritesResponse['favorites'][number]) => fav.template && !fav.template.isDeleted).map((fav: GetUserFavoritesResponse['favorites'][number]) => transformTemplateToMediaItem(fav.template!))
} else if (activeTab === 'new') { } else if (activeTab === 'new') {
if (isAuthenticated) { if (isAuthenticated) {
const { data, error } = await loadTemplates({ limit, page: 1, sortBy: 'createdAt', sortOrder: 'desc' }) const { data, error } = await loadTemplates({ limit, page: 1, categoryId: CATEGORY_ID, sortBy: 'createdAt', sortOrder: 'desc' })
if (error || !data?.templates) { if (error || !data?.templates) {
setHasMore(false) setHasMore(false)
return return
@ -423,7 +424,7 @@ export default function Sync() {
} }
} else { } else {
if (isAuthenticated) { if (isAuthenticated) {
const { data, error } = await loadTemplates({ limit, page: 1, sortBy: 'likeCount', sortOrder: 'desc' }) const { data, error } = await loadTemplates({ limit, page: 1, categoryId: CATEGORY_ID, sortBy: 'likeCount', sortOrder: 'desc' })
if (error || !data?.templates) { if (error || !data?.templates) {
setHasMore(false) setHasMore(false)
return return
@ -490,7 +491,7 @@ export default function Sync() {
setHasMore(false) setHasMore(false)
return return
} }
const { data, error } = await loadTemplates({ limit, page: pageNum, sortBy: 'createdAt', sortOrder: 'desc' }) const { data, error } = await loadTemplates({ limit, page: pageNum, categoryId: CATEGORY_ID, sortBy: 'createdAt', sortOrder: 'desc' })
if (error || !data?.templates) { if (error || !data?.templates) {
setHasMore(false) setHasMore(false)
return return
@ -501,7 +502,7 @@ export default function Sync() {
setHasMore(false) setHasMore(false)
return return
} }
const { data, error } = await loadTemplates({ limit, page: pageNum, sortBy: 'likeCount', sortOrder: 'desc' }) const { data, error } = await loadTemplates({ limit, page: pageNum, categoryId: CATEGORY_ID, sortBy: 'likeCount', sortOrder: 'desc' })
if (error || !data?.templates) { if (error || !data?.templates) {
setHasMore(false) setHasMore(false)
return return