Files
musicland/src/screens/cover/PouchReady.js
T
2026-09-08 14:20:23 +02:00

280 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import firebase, { tasksRef } from '../../config/firebase'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { ActivityIndicator, Alert, StyleSheet, Text, View } from 'react-native'
import { background } from '../../assets'
import BorderGradientButton from '../../components/BorderGradientButton'
import GradientButton from '../../components/GradientButton'
import MusiclandPlaybackCover from '../../components/MusiclandPlaybackCover'
import MusicLandHeader from '../../components/MusicLandHeader'
import { uploadFileToFirebase } from '../../helpers/uploadToFirebase'
import { isWeb } from '../../hooks/useLayoutType'
import Page from '../../layouts/Page'
import { Routes } from '../../navigation'
import { goBack, navigate } from '../../navigation/NavigationService'
import { useUser } from '../../providers/UserDataProvider'
import { gutters, Palette, Style } from '../../styles'
import { FONT_FAMILY } from '../../styles/Fonts'
import { createMusiclandPlaybackCover } from '../../utils/createMusiclandPlaybackCover'
import CreateLyricsHeader from '../Writing/components/CreateLyricsHeader'
const COVER_TEMPLATE = 'MUSICLAND_PLAYBACKER'
const COVER_OPTION_ID = 'musicland-playbacker'
const PouchReady = () => {
const {
selectedProjectId,
selectedProject,
currentUserData,
updateProjectData,
} = useUser()
const requestStartedRef = useRef(false)
const [isSaving, setIsSaving] = useState(false)
const templateCover =
selectedProject?.cover?.template === COVER_TEMPLATE ? selectedProject.cover : null
const coverUrl = templateCover?.result || null
const isPreparing = selectedProject?.coverStatus === 'GENERATING'
const hasPreparationError = selectedProject?.coverStatus === 'ERROR'
const artistName =
typeof selectedProject?.userName === 'string' && selectedProject.userName.trim()
? selectedProject.userName.trim()
: currentUserData?.userName || 'MusicLand'
const profilePictureURL = currentUserData?.profilePictureURL || null
const centerImageURL = selectedProject?.coverCenterImageURL || profilePictureURL
const selectedOption = useMemo(() => {
if (!coverUrl) {
return isWeb
? {
id: COVER_OPTION_ID,
finalUrl: null,
generatedUrl: null,
}
: null
}
const savedOption = Array.isArray(templateCover?.options) ? templateCover.options[0] : null
return (
savedOption || {
id: COVER_TEMPLATE,
finalUrl: coverUrl,
generatedUrl: null,
}
)
}, [coverUrl, templateCover?.options])
const requestCoverPreparation = useCallback(async () => {
if (!selectedProjectId || requestStartedRef.current || isPreparing) {
return
}
requestStartedRef.current = true
try {
await updateProjectData(
{
coverStatus: 'PENDING',
cover: {
template: COVER_TEMPLATE,
result: null,
selectedOptionId: COVER_TEMPLATE,
options: [],
},
},
{ merge: true }
)
await tasksRef.add({
type: 'cover',
projectId: selectedProjectId,
status: 'PENDING',
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
})
} catch (error) {
requestStartedRef.current = false
console.log('PouchReady: unable to prepare cover', error?.message)
}
}, [isPreparing, selectedProjectId, updateProjectData])
useEffect(() => {
if (isWeb || coverUrl || isPreparing || hasPreparationError) {
return
}
requestCoverPreparation()
}, [coverUrl, hasPreparationError, isPreparing, requestCoverPreparation])
const retryPreparation = useCallback(() => {
requestStartedRef.current = false
requestCoverPreparation()
}, [requestCoverPreparation])
const onValidatePicture = useCallback(async () => {
if (!selectedOption) {
return
}
try {
setIsSaving(true)
let optionToSave = selectedOption
let projectToSave = selectedProject
if (isWeb && !coverUrl) {
const userId = selectedProject?.userId
if (!userId || !selectedProjectId) {
throw new Error('cover_project_identity_missing')
}
const coverBlob = await createMusiclandPlaybackCover({
artistName,
profilePictureURL: centerImageURL,
})
const storagePath = `users/${userId}/projects/${selectedProjectId}/cover-musicland-playbacker.png`
const { resultURI } = await uploadFileToFirebase({
path: storagePath,
fileType: 'IMAGE',
blob: coverBlob,
})
optionToSave = {
id: COVER_OPTION_ID,
finalUrl: resultURI,
generatedUrl: null,
}
const cover = {
template: COVER_TEMPLATE,
result: resultURI,
generatedBackground: null,
selectedOptionId: COVER_OPTION_ID,
options: [optionToSave],
}
await updateProjectData(
{
cover,
coverStatus: 'GENERATED',
},
{ merge: true }
)
projectToSave = {
...selectedProject,
cover,
coverStatus: 'GENERATED',
}
}
navigate(Routes.SongDownload, {
project: projectToSave,
selectedOption: optionToSave,
coverOptions: [optionToSave],
backRoute: Routes.PouchReady,
})
} catch (error) {
console.log('PouchReady: unable to save cover', error?.message)
Alert.alert(
'Enregistrement impossible',
'La pochette n’a pas pu être enregistrée. Réessaie dans quelques instants.'
)
} finally {
setIsSaving(false)
}
}, [
artistName,
coverUrl,
centerImageURL,
selectedOption,
selectedProject,
selectedProjectId,
updateProjectData,
])
return (
<Page backgroundImg={background.studioBG2} headerType="NONE">
<MusicLandHeader onPressBack={goBack} progress={90} />
<View style={styles.pageContent}>
<CreateLyricsHeader
title="Ta pochette MusicLand"
subTitle={
selectedProject?.coverCenterImageURL
? "L’image choisie et ton nom d’artiste apparaissent automatiquement."
: "Ta photo de profil et ton nom d’artiste apparaissent automatiquement."
}
/>
<View style={[styles.previewArea, Style.containerCenter]}>
<MusiclandPlaybackCover
artistName={artistName}
profilePictureURL={centerImageURL}
coverUrl={coverUrl}
style={isWeb ? styles.coverWeb : styles.coverMobile}
/>
{!isWeb && !coverUrl && !hasPreparationError ? (
<View style={styles.preparingRow}>
<ActivityIndicator color={Palette.white} />
<Text style={styles.preparingText}>Préparation de ta pochette…</Text>
</View>
) : null}
{hasPreparationError ? (
<View style={styles.errorPanel}>
<Text style={styles.errorText}>
La pochette n’a pas pu être préparée. Tu peux réessayer.
</Text>
<BorderGradientButton title="Réessayer" onPress={retryPreparation} />
</View>
) : null}
</View>
</View>
<View style={styles.footer}>
<GradientButton
title={isSaving ? 'Enregistrement…' : 'Valider la pochette'}
onPress={onValidatePicture}
disabled={isSaving || (!isWeb && (!selectedOption || isPreparing))}
/>
</View>
</Page>
)
}
const styles = StyleSheet.create({
pageContent: {
flex: 1,
marginTop: 8,
},
previewArea: {
flex: 1,
width: '100%',
paddingHorizontal: gutters,
gap: 16,
},
coverWeb: {
width: 360,
maxWidth: '70%',
},
coverMobile: {
width: '86%',
maxWidth: 380,
},
preparingRow: {
minHeight: 24,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 10,
},
preparingText: {
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
fontSize: 14,
},
errorPanel: {
width: isWeb ? 360 : '86%',
maxWidth: 380,
gap: 12,
},
errorText: {
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
fontSize: 14,
lineHeight: 20,
textAlign: 'center',
},
footer: {
width: '80%',
maxWidth: 520,
alignSelf: 'center',
paddingBottom: gutters * 2,
},
})
export default PouchReady