import { useState, useEffect, useCallback, useMemo } from 'react' import { View, Text, StyleSheet, ScrollView, Dimensions, Pressable, TextInput, StatusBar as RNStatusBar, Platform, KeyboardAvoidingView, } 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 { LinearGradient } from 'expo-linear-gradient' import { LeftArrowIcon, UploadIcon, WhitePointsIcon } from '@/components/icon' import UploadReferenceImageDrawer from '@/components/drawer/UploadReferenceImageDrawer' import { StartGeneratingNotification } from '@/components/ui' import { useTemplateActions } from '@/hooks/use-template-actions' import { uploadFile } from '@/lib/uploadFile' import Toast from '@/components/ui/Toast' const { height: screenHeight } = Dimensions.get('window') interface TemplateData { id: string videoUrl: any thumbnailUrl: any title: string duration: string price?: number formSchema?: { startNodes?: Array<{ id: string; type: string }> } } export default function GenerateVideoScreen() { const { t } = useTranslation() const router = useRouter() const params = useLocalSearchParams() const { runTemplate, loading } = useTemplateActions() const [description, setDescription] = useState('') const [uploadedImageUrl, setUploadedImageUrl] = useState('') const [previewImageUri, setPreviewImageUri] = useState('') const [templateData, setTemplateData] = useState(null) const [drawerVisible, setDrawerVisible] = useState(false) const [showNotification, setShowNotification] = useState(false) useEffect(() => { if (params.template && typeof params.template === 'string') { try { const parsed = JSON.parse(params.template) as TemplateData setTemplateData(parsed) if (parsed.thumbnailUrl) { setPreviewImageUri(parsed.thumbnailUrl) } } catch (error) { console.error('Failed to parse template data:', error) } } }, [params.template]) const startNodes = useMemo(() => templateData?.formSchema?.startNodes || [], [templateData]) const hasImageNode = useMemo(() => startNodes.some((node) => node.type === 'image'), [startNodes]) const handleSelectImage = useCallback(async (imageUri: string, mimeType?: string, fileName?: string) => { try { setPreviewImageUri(imageUri) const url = await uploadFile({ uri: imageUri, mimeType, fileName }) setUploadedImageUrl(url) } catch (error) { console.error('Upload failed:', error) } finally { setDrawerVisible(false) } }, []) const handleGenerate = useCallback(async () => { if (!templateData) return if (hasImageNode && !uploadedImageUrl) { Toast.show({ title: t('generateVideo.pleaseUploadImage') || '请上传参考图片' }) return } Toast.showLoading() try { const data: Record = {} startNodes.forEach((node) => { data[node.id] = node.type === 'text' ? description : uploadedImageUrl }) const { generationId, error } = await runTemplate({ templateId: templateData.id, data, }) Toast.hideLoading() if (error) { Toast.show({ title: error.message || t('generateVideo.generateFailed') || '生成失败' }) return } if (generationId) { setShowNotification(true) setTimeout(() => { setShowNotification(false) router.back() }, 3000) } } catch (error) { Toast.hideLoading() Toast.show({ title: t('generateVideo.generateFailed') || '生成失败' }) } }, [templateData, uploadedImageUrl, description, runTemplate, router, t, hasImageNode, startNodes]) return ( {Platform.OS === 'android' && ( )} {/* 顶部导航栏 */} router.back()} > {/* 标题区域 */} {templateData?.title} {templateData?.title } {previewImageUri ? ( ) : ( )} {/* 上传区域 */} { setDrawerVisible(true) }} > {t('generateVideo.uploadReference')} {/* 描述输入区域 */} {/* 底部生成按钮 */} {loading ? (t('generateVideo.generating') || '生成中...') : (t('generateVideo.generate') || '生成')} {templateData?.price || 10} setDrawerVisible(false)} onSelectImage={handleSelectImage} /> {/* 通知组件 */} {showNotification && ( setShowNotification(false)} style={styles.notification} /> )} ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#090A0B', }, keyboardAvoidingView: { flex: 1, }, scrollView: { flex: 1, backgroundColor: '#090A0B', }, scrollContent: { flexGrow: 1, backgroundColor: '#090A0B', }, generateButtonContainer: { marginTop: 'auto', paddingTop: 12, paddingBottom: Platform.select({ ios: 10, android: 10, default: 10, }), }, content: { paddingHorizontal: 12, paddingTop: 16, backgroundColor: '#1C1E20', borderTopLeftRadius: 20, borderTopRightRadius: 20, gap: 8, minHeight: Platform.select({ ios: screenHeight - 100, android: screenHeight - 100, default: screenHeight - 60, }), }, 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', }, titleSection: { paddingHorizontal:4, marginBottom: 11, }, title: { color: '#F5F5F5', fontSize: 16, fontWeight: '600', marginBottom: 5, marginLeft: -4, }, subtitle: { color: '#CCCCCC', fontSize: 12, }, uploadContainer: { height: 140, borderRadius: 12, backgroundColor: '#262A31', overflow: 'hidden', position: 'relative', alignItems: 'center', justifyContent: 'center', }, thumbnailContainer: { position: 'absolute', left: 8, bottom: 5, width: 56, height: 56, borderRadius: 8, overflow: 'hidden', borderWidth: 2, borderColor: '#FFFFFF', backgroundColor: '#090A0B', }, thumbnail: { width: '100%', height: '100%', }, avatarPlaceholder: { width: 56, height: 56, borderRadius: 28, backgroundColor: '#2F3134', alignItems: 'center', justifyContent: 'center', }, avatarCircle: { width: 40, height: 40, borderRadius: 20, backgroundColor: '#4A4C4F', }, uploadedImage: { width: '100%', height: '100%', }, uploadReferenceButton: { height: 110, backgroundColor: '#262A31', borderRadius: 12, alignItems: 'center', justifyContent: 'center', gap: 6, }, uploadReferenceText: { color: '#F5F5F5', fontSize: 12, fontWeight: '500', }, descriptionInput: { minHeight: 150, backgroundColor: '#262A31', borderRadius: 12, padding: 12, color: '#F5F5F5', fontSize: 14, lineHeight: 20, ...Platform.select({ android: { textAlignVertical: 'top', }, }), }, generateButton: { width: '100%', height: 48, backgroundColor: 'red', borderRadius: 12, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, }, generateButtonText: { color: '#F5F5F5', fontSize: 16, fontWeight: '500', }, pointsBadge: { flexDirection: 'row', alignItems: 'center', }, pointsText: { color: '#f5f5f5', fontSize: 14, fontWeight: '500', }, notificationContainer: { position: 'absolute', top: Platform.select({ ios: 60, android: 50, default: 60, }), left: 0, right: 0, paddingHorizontal: 8, zIndex: 1000, }, notification: { width: '100%', }, generateButtonDisabled: { opacity: 0.6, }, })