324 lines
10 KiB
TypeScript
324 lines
10 KiB
TypeScript
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
ScrollView,
|
|
Dimensions,
|
|
Pressable,
|
|
StatusBar as RNStatusBar,
|
|
Platform,
|
|
} from 'react-native'
|
|
import { StatusBar } from 'expo-status-bar'
|
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
|
import { Image } from 'expo-image'
|
|
import { useRouter, useLocalSearchParams } from 'expo-router'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'
|
|
|
|
import { LeftArrowIcon, WhitePointsIcon } from '@/components/icon'
|
|
import UploadReferenceImageDrawer from '@/components/drawer/UploadReferenceImageDrawer'
|
|
import { StartGeneratingNotification } from '@/components/ui'
|
|
import { DynamicForm, type DynamicFormRef, type FormSchema } from '@/components/DynamicForm'
|
|
import { useTemplateActions } from '@/hooks/use-template-actions'
|
|
import { useTemplateDetail } from '@/hooks/use-template-detail'
|
|
import Toast from '@/components/ui/Toast'
|
|
|
|
const { height: screenHeight } = Dimensions.get('window')
|
|
|
|
export default function GenerateVideoScreen() {
|
|
const { t } = useTranslation()
|
|
const router = useRouter()
|
|
const params = useLocalSearchParams()
|
|
const { runTemplate, loading } = useTemplateActions()
|
|
const { data: templateDetail, loading: templateLoading, execute: fetchTemplate } = useTemplateDetail()
|
|
|
|
const [drawerVisible, setDrawerVisible] = useState(false)
|
|
const [showNotification, setShowNotification] = useState(false)
|
|
const [currentNodeId, setCurrentNodeId] = useState<string | null>(null)
|
|
const dynamicFormRef = useRef<DynamicFormRef>(null)
|
|
|
|
useEffect(() => {
|
|
if (params.templateId && typeof params.templateId === 'string') {
|
|
fetchTemplate({ id: params.templateId })
|
|
}
|
|
}, [params.templateId, fetchTemplate])
|
|
|
|
const formSchema = useMemo<FormSchema>(() => ({
|
|
startNodes: templateDetail?.formSchema?.startNodes || []
|
|
}), [templateDetail])
|
|
|
|
const handleOpenDrawer = useCallback((nodeId: string) => {
|
|
setCurrentNodeId(nodeId)
|
|
setDrawerVisible(true)
|
|
}, [])
|
|
|
|
const handleSelectImage = useCallback(async (imageUri: string, mimeType?: string, fileName?: string) => {
|
|
if (!currentNodeId) return
|
|
|
|
if (dynamicFormRef.current) {
|
|
dynamicFormRef.current.updateFieldValue(currentNodeId, imageUri, imageUri)
|
|
}
|
|
setDrawerVisible(false)
|
|
setCurrentNodeId(null)
|
|
}, [currentNodeId])
|
|
|
|
const handleFormSubmit = useCallback(async (data: Record<string, string>) => {
|
|
if (!templateDetail) return { error: { message: 'Template not found' } }
|
|
|
|
const result = await runTemplate({
|
|
templateId: templateDetail.id,
|
|
data,
|
|
})
|
|
|
|
if (result.generationId) {
|
|
setShowNotification(true)
|
|
setTimeout(() => {
|
|
setShowNotification(false)
|
|
router.back()
|
|
}, 3000)
|
|
}
|
|
|
|
return result
|
|
}, [templateDetail, runTemplate, router])
|
|
|
|
return (
|
|
<SafeAreaView
|
|
style={styles.container}
|
|
edges={Platform.OS === 'ios' ? ['top'] : ['top', 'bottom']}
|
|
>
|
|
<StatusBar style="light" />
|
|
{Platform.OS === 'android' && (
|
|
<RNStatusBar
|
|
barStyle="light-content"
|
|
backgroundColor="transparent"
|
|
translucent
|
|
/>
|
|
)}
|
|
<KeyboardAwareScrollView
|
|
style={styles.scrollView}
|
|
contentContainerStyle={styles.scrollContent}
|
|
bottomOffset={50}
|
|
>
|
|
{/* 顶部导航栏 */}
|
|
<View style={styles.header}>
|
|
<Pressable
|
|
style={styles.backButton}
|
|
onPress={() => router.back()}
|
|
>
|
|
<LeftArrowIcon />
|
|
</Pressable>
|
|
</View>
|
|
|
|
{/* 主要内容区域 */}
|
|
<View style={styles.content}>
|
|
{/* 模板预览卡片 */}
|
|
<View style={styles.templatePreviewCard}>
|
|
<View style={styles.templateThumbnailContainer}>
|
|
<Image
|
|
source={templateDetail?.coverImageUrl || ''}
|
|
style={styles.templateThumbnail}
|
|
contentFit="cover"
|
|
/>
|
|
<View style={styles.templateInfo}>
|
|
<Text style={styles.templateTitle}>{templateDetail?.title}</Text>
|
|
<Text style={styles.templateDescription}>{templateDetail?.description}</Text>
|
|
</View>
|
|
</View>
|
|
{/* 价格标签 */}
|
|
<View style={styles.priceTag}>
|
|
<WhitePointsIcon />
|
|
<Text style={styles.priceText}>{templateDetail?.price || 10}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* 动态表单 */}
|
|
<View style={styles.formContainer}>
|
|
{templateLoading ? (
|
|
<View style={styles.loadingContainer}>
|
|
<Text style={styles.loadingText}>{t('generateVideo.loading') || '加载中...'}</Text>
|
|
</View>
|
|
) : formSchema.startNodes && formSchema.startNodes.length > 0 ? (
|
|
<DynamicForm
|
|
ref={dynamicFormRef}
|
|
formSchema={formSchema}
|
|
onSubmit={handleFormSubmit}
|
|
loading={loading}
|
|
onOpenDrawer={handleOpenDrawer}
|
|
points={templateDetail?.price}
|
|
disableScroll
|
|
/>
|
|
) : (
|
|
<View style={styles.emptyContainer}>
|
|
<Text style={styles.emptyText}>
|
|
{t('generateVideo.noFormFields') || '暂无可填写的表单项'}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
</View>
|
|
</KeyboardAwareScrollView>
|
|
|
|
{/* 图片上传抽屉 */}
|
|
<UploadReferenceImageDrawer
|
|
visible={drawerVisible}
|
|
onClose={() => {
|
|
setDrawerVisible(false)
|
|
setCurrentNodeId(null)
|
|
}}
|
|
onSelectImage={handleSelectImage}
|
|
/>
|
|
|
|
{/* 通知组件 */}
|
|
{showNotification && (
|
|
<View style={styles.notificationContainer}>
|
|
<StartGeneratingNotification
|
|
count={1}
|
|
visible={showNotification}
|
|
title={t('generateVideo.startGenerating')}
|
|
message={t('generateVideo.generatingMessage')}
|
|
onPress={() => setShowNotification(false)}
|
|
style={styles.notification}
|
|
/>
|
|
</View>
|
|
)}
|
|
</SafeAreaView>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#090A0B',
|
|
},
|
|
scrollView: {
|
|
flex: 1,
|
|
backgroundColor: '#090A0B',
|
|
},
|
|
scrollContent: {
|
|
flexGrow: 1,
|
|
backgroundColor: '#090A0B',
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingTop: Platform.select({
|
|
ios: 17,
|
|
android: 12,
|
|
default: 17,
|
|
}),
|
|
paddingHorizontal: 12,
|
|
paddingBottom: 20,
|
|
},
|
|
backButton: {
|
|
width: 22,
|
|
height: 22,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
content: {
|
|
paddingHorizontal: 12,
|
|
paddingTop: 16,
|
|
backgroundColor: '#1C1E20',
|
|
borderTopLeftRadius: 20,
|
|
borderTopRightRadius: 20,
|
|
gap: 16,
|
|
minHeight: Platform.select({
|
|
ios: screenHeight - 100,
|
|
android: screenHeight - 100,
|
|
default: screenHeight - 60,
|
|
}),
|
|
},
|
|
// 模板预览卡片样式
|
|
templatePreviewCard: {
|
|
backgroundColor: '#262A31',
|
|
borderRadius: 16,
|
|
padding: 12,
|
|
borderWidth: 1,
|
|
borderColor: '#2F3134',
|
|
},
|
|
templateThumbnailContainer: {
|
|
flexDirection: 'row',
|
|
gap: 12,
|
|
alignItems: 'center',
|
|
},
|
|
templateThumbnail: {
|
|
width: 80,
|
|
height: 80,
|
|
borderRadius: 12,
|
|
backgroundColor: '#1C1E20',
|
|
},
|
|
templateInfo: {
|
|
flex: 1,
|
|
gap: 4,
|
|
},
|
|
templateTitle: {
|
|
color: '#F5F5F5',
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
lineHeight: 22,
|
|
},
|
|
templateDescription: {
|
|
color: '#ABABAB',
|
|
fontSize: 13,
|
|
lineHeight: 18,
|
|
},
|
|
priceTag: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'flex-end',
|
|
gap: 6,
|
|
marginTop: 8,
|
|
paddingTop: 8,
|
|
borderTopWidth: 1,
|
|
borderTopColor: '#2F3134',
|
|
},
|
|
priceText: {
|
|
color: '#FFCF00',
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
},
|
|
// 表单容器样式
|
|
formContainer: {
|
|
gap: 12,
|
|
},
|
|
loadingContainer: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
padding: 40,
|
|
},
|
|
loadingText: {
|
|
color: '#8A8A8A',
|
|
fontSize: 14,
|
|
},
|
|
emptyContainer: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
padding: 40,
|
|
backgroundColor: '#262A31',
|
|
borderRadius: 12,
|
|
},
|
|
emptyText: {
|
|
color: '#8A8A8A',
|
|
fontSize: 14,
|
|
textAlign: 'center',
|
|
},
|
|
// 通知容器样式
|
|
notificationContainer: {
|
|
position: 'absolute',
|
|
top: Platform.select({
|
|
ios: 60,
|
|
android: 50,
|
|
default: 60,
|
|
}),
|
|
left: 0,
|
|
right: 0,
|
|
paddingHorizontal: 8,
|
|
zIndex: 1000,
|
|
},
|
|
notification: {
|
|
width: '100%',
|
|
},
|
|
})
|
|
|