284 lines
8.5 KiB
JavaScript
284 lines
8.5 KiB
JavaScript
import useMinuit from 'react-native-minuit/src/hooks/useMinuit.js'
|
||
import React, { useMemo, useState } from 'react'
|
||
import { Image, Text, View } from 'react-native'
|
||
import { background } from '../../assets'
|
||
import useDataFromRef from '../../hooks/useDataFromRef'
|
||
import ActivityLoader from '../../components/ActivityLoader'
|
||
import BorderGradientButton from '../../components/BorderGradientButton'
|
||
import GradientButton from '../../components/GradientButton'
|
||
import MusicLandHeader from '../../components/MusicLandHeader'
|
||
import { getFunctionsClient, projectsRef } from '../../config/firebase'
|
||
import Page from '../../layouts/Page'
|
||
import { Routes } from '../../navigation'
|
||
import { goBack, navigate, push } from '../../navigation/NavigationService'
|
||
import { useUserData } from '../../providers/UserDataProvider'
|
||
import { gutters, Palette } from '../../styles'
|
||
import { FONT_FAMILY } from '../../styles/Fonts'
|
||
import CreateLyricsHeader from '../Writing/components/CreateLyricsHeader'
|
||
import { PLAYBACK_AI_STATUS } from '../../utils/playbackAi'
|
||
|
||
const FUNCTIONS_REGION = 'europe-west1'
|
||
|
||
const resolveProject = ({ routeProject, selectedProject, liveProject }) => {
|
||
if (liveProject?.id) return liveProject
|
||
if (routeProject?.id && selectedProject?.id === routeProject.id) {
|
||
return selectedProject
|
||
}
|
||
return routeProject || null
|
||
}
|
||
|
||
const callValidatePlaybackDraft = (payload) => {
|
||
const callable = getFunctionsClient(FUNCTIONS_REGION).httpsCallable('heygen-validatePlaybackDraft')
|
||
return callable(payload)
|
||
}
|
||
|
||
const PlaybackAiStatus = ({ route }) => {
|
||
const { project: routeProject } = route.params || {}
|
||
const { selectedProject } = useUserData() || {}
|
||
const { setTooltip } = useMinuit()
|
||
const [isValidating, setIsValidating] = useState(false)
|
||
const projectId = routeProject?.id || selectedProject?.id || null
|
||
const { data: liveProject = null } = useDataFromRef({
|
||
ref: projectId ? projectsRef.doc(projectId) : null,
|
||
simpleRef: true,
|
||
listener: true,
|
||
condition: !!projectId,
|
||
refreshArray: [projectId],
|
||
})
|
||
const project = useMemo(
|
||
() => resolveProject({ routeProject, selectedProject, liveProject }),
|
||
[liveProject, routeProject, selectedProject]
|
||
)
|
||
const status =
|
||
project?.playbackStatus ||
|
||
(routeProject?.playbackGenerating ? PLAYBACK_AI_STATUS.GENERATING : null)
|
||
const draftUrl = project?.playbackDraftUrl || routeProject?.playbackDraftUrl || null
|
||
|
||
const handleValidate = async () => {
|
||
try {
|
||
if (!project?.id) {
|
||
throw new Error('Projet introuvable')
|
||
}
|
||
|
||
setIsValidating(true)
|
||
await callValidatePlaybackDraft({ projectId: project.id })
|
||
|
||
const latestProjectSnap = await projectsRef.doc(project.id).get()
|
||
const latestProject = latestProjectSnap.exists
|
||
? { ...latestProjectSnap.data(), id: latestProjectSnap.id }
|
||
: project
|
||
|
||
navigate(Routes.PlaybackDownload, {
|
||
action: 'playback',
|
||
project: latestProject,
|
||
})
|
||
} catch (error) {
|
||
setTooltip({
|
||
type: 'error',
|
||
text: error?.message || 'Impossible de valider le playback IA',
|
||
})
|
||
} finally {
|
||
setIsValidating(false)
|
||
}
|
||
}
|
||
|
||
const handleRetry = () => {
|
||
push(Routes.PlaybackAi, {
|
||
project,
|
||
initialPhotoUrl: project?.playbackPhotoUrl || null,
|
||
})
|
||
}
|
||
|
||
return (
|
||
<Page
|
||
backgroundImg={background.playbackBG2}
|
||
backgroundColor="#07111B"
|
||
headerType="NONE"
|
||
>
|
||
<MusicLandHeader progress={38} onPressBack={goBack} />
|
||
<View style={{ flex: 1, paddingBottom: gutters * 2 }}>
|
||
<CreateLyricsHeader title="Playback IA" />
|
||
|
||
<View style={styles.content}>
|
||
<View style={styles.headerCard}>
|
||
{project?.coverUrl ? (
|
||
<Image source={{ uri: project.coverUrl }} style={styles.cover} resizeMode="cover" />
|
||
) : null}
|
||
<View style={{ flex: 1, gap: 4 }}>
|
||
<Text style={styles.title}>{project?.title || 'Sans titre'}</Text>
|
||
<Text style={styles.subtitle}>Format vertical 9:16 généré avec HeyGen</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{status === PLAYBACK_AI_STATUS.GENERATING ? (
|
||
<View style={styles.stateCard}>
|
||
<ActivityLoader defaultMessage="HeyGen génère ton playback IA..." />
|
||
<Text style={styles.helperText}>
|
||
Tu peux fermer cet écran et revenir plus tard. Une notification te sera envoyée dès
|
||
que ta vidéo sera prête.
|
||
</Text>
|
||
</View>
|
||
) : null}
|
||
|
||
{status === PLAYBACK_AI_STATUS.FAILED ? (
|
||
<View style={styles.stateCard}>
|
||
<Text style={styles.errorTitle}>La génération a échoué</Text>
|
||
<Text style={styles.helperText}>
|
||
{project?.playbackError || 'HeyGen n’a pas réussi à générer la vidéo.'}
|
||
</Text>
|
||
<BorderGradientButton title="Réessayer" onPress={handleRetry} />
|
||
</View>
|
||
) : null}
|
||
|
||
{status === PLAYBACK_AI_STATUS.DRAFT_READY ? (
|
||
<>
|
||
<View style={styles.videoCard}>
|
||
{draftUrl ? (
|
||
<video
|
||
src={draftUrl}
|
||
controls
|
||
playsInline
|
||
style={styles.video}
|
||
/>
|
||
) : (
|
||
<View style={[styles.videoFallback, styles.emptyVideo]}>
|
||
<Text style={styles.helperText}>La vidéo HeyGen est introuvable.</Text>
|
||
</View>
|
||
)}
|
||
</View>
|
||
|
||
<View style={styles.actions}>
|
||
<GradientButton
|
||
title="Valider ce playback"
|
||
onPress={handleValidate}
|
||
disabled={isValidating || !draftUrl}
|
||
/>
|
||
<BorderGradientButton
|
||
title="Recommencer"
|
||
onPress={handleRetry}
|
||
disabled={isValidating}
|
||
/>
|
||
{isValidating ? (
|
||
<ActivityLoader defaultMessage="Validation du playback IA..." />
|
||
) : null}
|
||
</View>
|
||
</>
|
||
) : null}
|
||
|
||
{status === PLAYBACK_AI_STATUS.READY && project?.playbackUrl ? (
|
||
<View style={styles.stateCard}>
|
||
<Text style={styles.successTitle}>Le playback IA est déjà validé</Text>
|
||
<GradientButton
|
||
title="Continuer"
|
||
onPress={() =>
|
||
navigate(Routes.PlaybackDownload, {
|
||
action: 'playback',
|
||
project,
|
||
})
|
||
}
|
||
/>
|
||
</View>
|
||
) : null}
|
||
</View>
|
||
</View>
|
||
</Page>
|
||
)
|
||
}
|
||
|
||
const styles = {
|
||
content: {
|
||
flex: 1,
|
||
width: '92%',
|
||
alignSelf: 'center',
|
||
gap: 16,
|
||
},
|
||
headerCard: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
gap: 12,
|
||
padding: 16,
|
||
borderRadius: 18,
|
||
borderWidth: 1,
|
||
borderColor: 'rgba(255,255,255,0.14)',
|
||
backgroundColor: 'rgba(5,12,24,0.76)',
|
||
},
|
||
cover: {
|
||
width: 68,
|
||
height: 68,
|
||
borderRadius: 14,
|
||
},
|
||
title: {
|
||
color: Palette.white,
|
||
fontSize: 18,
|
||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||
},
|
||
subtitle: {
|
||
color: Palette.white,
|
||
opacity: 0.7,
|
||
fontFamily: FONT_FAMILY.InterRegular,
|
||
},
|
||
stateCard: {
|
||
flex: 1,
|
||
minHeight: 240,
|
||
borderRadius: 20,
|
||
borderWidth: 1,
|
||
borderColor: 'rgba(255,255,255,0.14)',
|
||
backgroundColor: 'rgba(5,12,24,0.8)',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
gap: 14,
|
||
paddingHorizontal: 20,
|
||
},
|
||
videoCard: {
|
||
width: '60%',
|
||
maxWidth: 360,
|
||
aspectRatio: 9 / 16,
|
||
alignSelf: 'center',
|
||
borderRadius: 22,
|
||
overflow: 'hidden',
|
||
borderWidth: 1,
|
||
borderColor: 'rgba(255,255,255,0.14)',
|
||
backgroundColor: 'rgba(5,12,24,0.8)',
|
||
padding: 12,
|
||
},
|
||
video: {
|
||
width: '100%',
|
||
height: '100%',
|
||
borderRadius: 18,
|
||
backgroundColor: '#0A0A0A',
|
||
objectFit: 'contain',
|
||
},
|
||
videoFallback: {
|
||
width: '100%',
|
||
height: '100%',
|
||
borderRadius: 18,
|
||
backgroundColor: '#0A0A0A',
|
||
},
|
||
emptyVideo: {
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
helperText: {
|
||
color: Palette.white,
|
||
opacity: 0.72,
|
||
fontFamily: FONT_FAMILY.InterRegular,
|
||
lineHeight: 20,
|
||
textAlign: 'center',
|
||
},
|
||
errorTitle: {
|
||
color: Palette.white,
|
||
fontSize: 18,
|
||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||
},
|
||
successTitle: {
|
||
color: Palette.white,
|
||
fontSize: 18,
|
||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||
},
|
||
actions: {
|
||
gap: 12,
|
||
},
|
||
}
|
||
|
||
export default PlaybackAiStatus
|