global loading for lyrics

This commit is contained in:
2026-02-12 11:53:36 +01:00
parent 8693b96367
commit d69577b4ce
+41 -8
View File
@@ -1,6 +1,5 @@
import React, { useCallback, useMemo, useRef, useState } from 'react' import React, { useCallback, useMemo, useRef, useState } from 'react'
import { ScrollView, StyleSheet, Text, View } from 'react-native' import { ScrollView, StyleSheet, Text, View } from 'react-native'
import useMinuit from 'react-native-minuit/src/hooks/useMinuit.js'
import { background, icons } from '../../assets' import { background, icons } from '../../assets'
import alert from '../../components/Alert' import alert from '../../components/Alert'
import AppCheckbox from '../../components/AppCheckbox' import AppCheckbox from '../../components/AppCheckbox'
@@ -15,6 +14,7 @@ import { isWeb } from '../../hooks/useLayoutType'
import Page from '../../layouts/Page' import Page from '../../layouts/Page'
import { Routes } from '../../navigation' import { Routes } from '../../navigation'
import { navigate } from '../../navigation/NavigationService' import { navigate } from '../../navigation/NavigationService'
import { LoaderIndicator } from '../../providers/LoadingProvider'
import { useUser } from '../../providers/UserDataProvider' import { useUser } from '../../providers/UserDataProvider'
import { Palette, gutters } from '../../styles' import { Palette, gutters } from '../../styles'
import { FONT_FAMILY } from '../../styles/Fonts' import { FONT_FAMILY } from '../../styles/Fonts'
@@ -35,7 +35,6 @@ const RESPONSIBILITY_CHECKBOX_LABEL =
const Lyrics = ({ navigation }) => { const Lyrics = ({ navigation }) => {
const scrollRef = useRef(null) const scrollRef = useRef(null)
const [containerLayout, setContainerLayout] = useState(null) const [containerLayout, setContainerLayout] = useState(null)
const { setIsLoading } = useMinuit()
const { selectedProjectId, selectedProject } = useUser() const { selectedProjectId, selectedProject } = useUser()
const hasExistingMusicDraft = useMemo(() => { const hasExistingMusicDraft = useMemo(() => {
if (!Array.isArray(selectedProject?.musicUrls)) return false if (!Array.isArray(selectedProject?.musicUrls)) return false
@@ -43,6 +42,9 @@ const Lyrics = ({ navigation }) => {
}, [selectedProject?.musicUrls]) }, [selectedProject?.musicUrls])
const [isFocus, setIsFocus] = useState(null) const [isFocus, setIsFocus] = useState(null)
const [itemsContainerLayout, setItemsContainerLayout] = useState([]) const [itemsContainerLayout, setItemsContainerLayout] = useState([])
const [isSubmitting, setIsSubmitting] = useState(false)
const [isBlockingUI, setIsBlockingUI] = useState(false)
const isSubmittingRef = useRef(false)
const [sensitiveContentModal, setSensitiveContentModal] = useState({ const [sensitiveContentModal, setSensitiveContentModal] = useState({
visible: false, visible: false,
title: '', title: '',
@@ -194,10 +196,14 @@ const Lyrics = ({ navigation }) => {
}, [closeSensitiveContentModal, isSensitiveContentAcknowledged]) }, [closeSensitiveContentModal, isSensitiveContentAcknowledged])
const onValidate = useCallback(async () => { const onValidate = useCallback(async () => {
if (isSubmittingRef.current) return
isSubmittingRef.current = true
setIsSubmitting(true)
setIsBlockingUI(true)
try { try {
await setIsLoading(true)
const titleTrimmed = (titleValue || '').trim() const titleTrimmed = (titleValue || '').trim()
if (!titleTrimmed) { if (!titleTrimmed) {
setIsBlockingUI(false)
alertMessage('Titre manquant', 'Veuillez renseigner un titre.') alertMessage('Titre manquant', 'Veuillez renseigner un titre.')
return return
} }
@@ -207,6 +213,7 @@ const Lyrics = ({ navigation }) => {
return !(s?.lyrics || '').trim() return !(s?.lyrics || '').trim()
}) })
if (invalid) { if (invalid) {
setIsBlockingUI(false)
alertMessage('Champs incomplets', 'Chaque section doit contenir du texte.') alertMessage('Champs incomplets', 'Chaque section doit contenir du texte.')
return return
} }
@@ -247,11 +254,13 @@ const Lyrics = ({ navigation }) => {
.map((e) => `${e.quote}`) .map((e) => `${e.quote}`)
.join('\n') .join('\n')
: null : null
setIsBlockingUI(false)
alertMessage('Contenu interdit', [data?.message, quotes].filter(Boolean).join('\n\n')) alertMessage('Contenu interdit', [data?.message, quotes].filter(Boolean).join('\n\n'))
return // stop here return // stop here
} }
// Erreur d'analyse: informer et arrêter // Erreur d'analyse: informer et arrêter
if (data?.errorCode === 'ANALYSE_FAILED') { if (data?.errorCode === 'ANALYSE_FAILED') {
setIsBlockingUI(false)
alertMessage( alertMessage(
'Analyse indisponible', 'Analyse indisponible',
'Impossible de vérifier la toxicité pour le moment. Réessayez plus tard.' 'Impossible de vérifier la toxicité pour le moment. Réessayez plus tard.'
@@ -270,11 +279,14 @@ const Lyrics = ({ navigation }) => {
: null : null
const msg = [data?.message, quotes].filter(Boolean).join('\n\n') const msg = [data?.message, quotes].filter(Boolean).join('\n\n')
setIsBlockingUI(false)
const proceed = await confirmSensitiveContent('Contenu potentiellement sensible', msg) const proceed = await confirmSensitiveContent('Contenu potentiellement sensible', msg)
if (!proceed) return if (!proceed) return
setIsBlockingUI(true)
} }
} catch (moderationError) { } catch (moderationError) {
console.log('Moderation call failed', moderationError?.message || moderationError) console.log('Moderation call failed', moderationError?.message || moderationError)
setIsBlockingUI(false)
alertMessage( alertMessage(
'Analyse indisponible', 'Analyse indisponible',
'Impossible de vérifier la toxicité pour le moment. Réessayez plus tard.' 'Impossible de vérifier la toxicité pour le moment. Réessayez plus tard.'
@@ -310,7 +322,7 @@ const Lyrics = ({ navigation }) => {
projectForStage.musicStatus = undefined projectForStage.musicStatus = undefined
} }
const beatmakerStage = getStageAction('beatmaker', projectForStage) const beatmakerStage = getStageAction('beatmaker', projectForStage)
await setIsLoading(false) setIsBlockingUI(false)
if (hasExistingMusicDraft) { if (hasExistingMusicDraft) {
navigate(Routes.ComposeSong, { isRegeneration: true }) navigate(Routes.ComposeSong, { isRegeneration: true })
return return
@@ -343,9 +355,12 @@ const Lyrics = ({ navigation }) => {
return return
} catch (e) { } catch (e) {
console.log(e) console.log(e)
setIsBlockingUI(false)
alertMessage('Erreur', "Échec de l'enregistrement dans le projet.") alertMessage('Erreur', "Échec de l'enregistrement dans le projet.")
} finally { } finally {
await setIsLoading(false) isSubmittingRef.current = false
setIsSubmitting(false)
setIsBlockingUI(false)
} }
}, [ }, [
titleValue, titleValue,
@@ -355,7 +370,6 @@ const Lyrics = ({ navigation }) => {
alertMessage, alertMessage,
confirmSensitiveContent, confirmSensitiveContent,
sanitize, sanitize,
setIsLoading,
selectedProjectId, selectedProjectId,
projectHasLyrics, projectHasLyrics,
projectLyrics, projectLyrics,
@@ -461,10 +475,22 @@ const Lyrics = ({ navigation }) => {
}} }}
> >
{!projectHasLyrics && ( {!projectHasLyrics && (
<BorderGradientButton title="Générer d'autres paroles" onPress={regenerate} /> <BorderGradientButton
title="Générer d'autres paroles"
onPress={regenerate}
disabled={isSubmitting}
/>
)} )}
<GradientButton title="Valider" onPress={onValidate} /> <GradientButton title="Valider" onPress={onValidate} disabled={isSubmitting} />
</View> </View>
{isBlockingUI ? (
<View
pointerEvents="auto"
style={[styles.loadingOverlay, { position: isWeb ? 'fixed' : 'absolute' }]}
>
<LoaderIndicator />
</View>
) : null}
<Overlay <Overlay
isVisible={sensitiveContentModal.visible} isVisible={sensitiveContentModal.visible}
setIsVisible={(visible) => { setIsVisible={(visible) => {
@@ -578,4 +604,11 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
gap: 12, gap: 12,
}, },
loadingOverlay: {
...StyleSheet.absoluteFillObject,
zIndex: 10000,
backgroundColor: Palette.transparentBlack,
justifyContent: 'center',
alignItems: 'center',
},
}) })