feat: fixes and formatter
This commit is contained in:
+223
-264
@@ -1,23 +1,23 @@
|
||||
import React, { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { ScrollView, StyleSheet, Text, View } from "react-native";
|
||||
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
|
||||
import { background, icons } from "../../assets";
|
||||
import alert from "../../components/Alert";
|
||||
import AppCheckbox from "../../components/AppCheckbox";
|
||||
import BorderGradientButton from "../../components/BorderGradientButton";
|
||||
import GradientButton from "../../components/GradientButton";
|
||||
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||
import Overlay from "../../components/Overlay";
|
||||
import firebase, { projectsRef } from "../../config/firebase";
|
||||
import { strings } from "../../constants/strings";
|
||||
import { isWeb } from "../../hooks/useLayoutType";
|
||||
import Page from "../../layouts/Page";
|
||||
import { Routes } from "../../navigation";
|
||||
import { navigate } from "../../navigation/NavigationService";
|
||||
import { useUser } from "../../providers/UserDataProvider";
|
||||
import { Palette, gutters } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { ScrollView, StyleSheet, Text, View } from 'react-native'
|
||||
import useMinuit from 'react-native-minuit/src/hooks/useMinuit.js'
|
||||
import { background, icons } from '../../assets'
|
||||
import alert from '../../components/Alert'
|
||||
import AppCheckbox from '../../components/AppCheckbox'
|
||||
import BorderGradientButton from '../../components/BorderGradientButton'
|
||||
import GradientButton from '../../components/GradientButton'
|
||||
import ItemContainer from '../../components/ItemContainer/ItemContainer'
|
||||
import MusicLandHeader from '../../components/MusicLandHeader'
|
||||
import Overlay from '../../components/Overlay'
|
||||
import firebase, { projectsRef } from '../../config/firebase'
|
||||
import { strings } from '../../constants/strings'
|
||||
import { isWeb } from '../../hooks/useLayoutType'
|
||||
import Page from '../../layouts/Page'
|
||||
import { Routes } from '../../navigation'
|
||||
import { navigate } from '../../navigation/NavigationService'
|
||||
import { useUser } from '../../providers/UserDataProvider'
|
||||
import { Palette, gutters } from '../../styles'
|
||||
import { FONT_FAMILY } from '../../styles/Fonts'
|
||||
import {
|
||||
formatStructureLabel,
|
||||
getPromptLabelForStructure,
|
||||
@@ -25,284 +25,261 @@ import {
|
||||
normalizeStructureType,
|
||||
sanitizeStructureList,
|
||||
segmentRequiresLyrics,
|
||||
} from "../../utils/songStructure";
|
||||
import { getStageAction } from "../../utils/projectStages";
|
||||
import CustomInput from "./components/CustomInput";
|
||||
} from '../../utils/songStructure'
|
||||
import { getStageAction } from '../../utils/projectStages'
|
||||
import CustomInput from './components/CustomInput'
|
||||
|
||||
const RESPONSIBILITY_CHECKBOX_LABEL =
|
||||
"Vous êtes responsable du contenu que vous validez et Musicland ne sera en aucun cas tenu responsable du contenu que vous validez.";
|
||||
'Vous êtes responsable du contenu que vous validez et Musicland ne sera en aucun cas tenu responsable du contenu que vous validez.'
|
||||
|
||||
const Lyrics = ({ navigation }) => {
|
||||
const scrollRef = useRef(null);
|
||||
const [containerLayout, setContainerLayout] = useState(null);
|
||||
const { setIsLoading } = useMinuit();
|
||||
const { selectedProjectId, selectedProject } = useUser();
|
||||
const scrollRef = useRef(null)
|
||||
const [containerLayout, setContainerLayout] = useState(null)
|
||||
const { setIsLoading } = useMinuit()
|
||||
const { selectedProjectId, selectedProject } = useUser()
|
||||
const hasExistingMusicDraft = useMemo(() => {
|
||||
if (!Array.isArray(selectedProject?.musicUrls)) return false;
|
||||
return selectedProject.musicUrls.some(
|
||||
(url) => typeof url === "string" && url.trim(),
|
||||
);
|
||||
}, [selectedProject?.musicUrls]);
|
||||
const [isFocus, setIsFocus] = useState(null);
|
||||
const [itemsContainerLayout, setItemsContainerLayout] = useState([]);
|
||||
if (!Array.isArray(selectedProject?.musicUrls)) return false
|
||||
return selectedProject.musicUrls.some((url) => typeof url === 'string' && url.trim())
|
||||
}, [selectedProject?.musicUrls])
|
||||
const [isFocus, setIsFocus] = useState(null)
|
||||
const [itemsContainerLayout, setItemsContainerLayout] = useState([])
|
||||
const [sensitiveContentModal, setSensitiveContentModal] = useState({
|
||||
visible: false,
|
||||
title: "",
|
||||
message: "",
|
||||
});
|
||||
const [isSensitiveContentAcknowledged, setIsSensitiveContentAcknowledged] =
|
||||
useState(false);
|
||||
const sensitiveContentResolverRef = useRef(null);
|
||||
title: '',
|
||||
message: '',
|
||||
})
|
||||
const [isSensitiveContentAcknowledged, setIsSensitiveContentAcknowledged] = useState(false)
|
||||
const sensitiveContentResolverRef = useRef(null)
|
||||
const updateLayoutAtIndex = useCallback((index, layout) => {
|
||||
if (typeof index !== "number" || !layout) return;
|
||||
if (typeof index !== 'number' || !layout) return
|
||||
setItemsContainerLayout((prev) => {
|
||||
const next = [...prev];
|
||||
next[index] = layout;
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
const next = [...prev]
|
||||
next[index] = layout
|
||||
return next
|
||||
})
|
||||
}, [])
|
||||
const handleSectionFocus = useCallback(
|
||||
(idx) => {
|
||||
setIsFocus(idx);
|
||||
if (isWeb) return;
|
||||
const targetLayout = itemsContainerLayout[idx + 1];
|
||||
if (typeof targetLayout?.y === "number") {
|
||||
setIsFocus(idx)
|
||||
if (isWeb) return
|
||||
const targetLayout = itemsContainerLayout[idx + 1]
|
||||
if (typeof targetLayout?.y === 'number') {
|
||||
scrollRef?.current?.scrollTo({
|
||||
y: targetLayout.y,
|
||||
animated: true,
|
||||
});
|
||||
})
|
||||
}
|
||||
},
|
||||
[itemsContainerLayout, isWeb],
|
||||
);
|
||||
[itemsContainerLayout, isWeb]
|
||||
)
|
||||
|
||||
const closeSensitiveContentModal = useCallback((result) => {
|
||||
setSensitiveContentModal((prev) => ({ ...prev, visible: false }));
|
||||
setIsSensitiveContentAcknowledged(false);
|
||||
setSensitiveContentModal((prev) => ({ ...prev, visible: false }))
|
||||
setIsSensitiveContentAcknowledged(false)
|
||||
if (sensitiveContentResolverRef.current) {
|
||||
sensitiveContentResolverRef.current(result);
|
||||
sensitiveContentResolverRef.current = null;
|
||||
sensitiveContentResolverRef.current(result)
|
||||
sensitiveContentResolverRef.current = null
|
||||
}
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
// Effective sources from provider only
|
||||
const projectTitle = selectedProject?.title || "";
|
||||
const projectLyrics = Array.isArray(selectedProject?.lyrics)
|
||||
? selectedProject.lyrics
|
||||
: [];
|
||||
const projectConfig = selectedProject?.config || null;
|
||||
const projectSelections = selectedProject?.selections || null;
|
||||
const projectHasLyrics = !!selectedProject?.hasLyrics;
|
||||
const projectTitle = selectedProject?.title || ''
|
||||
const projectLyrics = Array.isArray(selectedProject?.lyrics) ? selectedProject.lyrics : []
|
||||
const projectConfig = selectedProject?.config || null
|
||||
const projectSelections = selectedProject?.selections || null
|
||||
const projectHasLyrics = !!selectedProject?.hasLyrics
|
||||
|
||||
const initial = useMemo(() => {
|
||||
const title = projectTitle || "";
|
||||
const title = projectTitle || ''
|
||||
const normalizedSections = Array.isArray(projectLyrics)
|
||||
? projectLyrics.map((s) => ({
|
||||
type: normalizeStructureType(s?.type),
|
||||
lyrics: s?.lyrics || "",
|
||||
lyrics: s?.lyrics || '',
|
||||
}))
|
||||
: [];
|
||||
: []
|
||||
const targetStructure = Array.isArray(projectConfig?.structure)
|
||||
? sanitizeStructureList(projectConfig.structure)
|
||||
: null;
|
||||
: null
|
||||
|
||||
const structureToUse =
|
||||
targetStructure && targetStructure.length
|
||||
? targetStructure
|
||||
: normalizedSections.map((s) => s.type);
|
||||
: normalizedSections.map((s) => s.type)
|
||||
|
||||
if (!structureToUse.length && normalizedSections.length) {
|
||||
return { title, sections: normalizedSections };
|
||||
return { title, sections: normalizedSections }
|
||||
}
|
||||
|
||||
const remaining = [...normalizedSections];
|
||||
const remaining = [...normalizedSections]
|
||||
const takeMatching = (type) => {
|
||||
const index = remaining.findIndex((s) => s.type === type);
|
||||
const index = remaining.findIndex((s) => s.type === type)
|
||||
if (index !== -1) {
|
||||
return remaining.splice(index, 1)[0];
|
||||
return remaining.splice(index, 1)[0]
|
||||
}
|
||||
return remaining.shift() || null;
|
||||
};
|
||||
return remaining.shift() || null
|
||||
}
|
||||
|
||||
const sections = structureToUse.map((segmentType) => {
|
||||
const normalizedType = normalizeStructureType(segmentType);
|
||||
const normalizedType = normalizeStructureType(segmentType)
|
||||
if (!segmentRequiresLyrics(normalizedType)) {
|
||||
return { type: normalizedType, lyrics: "" };
|
||||
return { type: normalizedType, lyrics: '' }
|
||||
}
|
||||
const matched = takeMatching(normalizedType);
|
||||
const matched = takeMatching(normalizedType)
|
||||
return {
|
||||
type: normalizedType,
|
||||
lyrics: matched?.lyrics || "",
|
||||
};
|
||||
});
|
||||
lyrics: matched?.lyrics || '',
|
||||
}
|
||||
})
|
||||
|
||||
const remainingTextual = remaining.filter((item) =>
|
||||
segmentRequiresLyrics(item.type),
|
||||
);
|
||||
sections.push(...remainingTextual);
|
||||
const remainingTextual = remaining.filter((item) => segmentRequiresLyrics(item.type))
|
||||
sections.push(...remainingTextual)
|
||||
|
||||
return { title, sections };
|
||||
}, [projectTitle, projectLyrics, projectConfig]);
|
||||
return { title, sections }
|
||||
}, [projectTitle, projectLyrics, projectConfig])
|
||||
|
||||
const [titleValue, setTitleValue] = useState(initial.title || "");
|
||||
const [sections, setSections] = useState(initial.sections || []);
|
||||
const [titleValue, setTitleValue] = useState(initial.title || '')
|
||||
const [sections, setSections] = useState(initial.sections || [])
|
||||
const setSectionAt = (index, value) => {
|
||||
setSections((prev) => {
|
||||
const next = [...prev];
|
||||
if (next[index]) next[index] = { ...next[index], lyrics: value };
|
||||
return next;
|
||||
});
|
||||
};
|
||||
const next = [...prev]
|
||||
if (next[index]) next[index] = { ...next[index], lyrics: value }
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
// Plus d'appel direct à la cloud function ici: on retourne à l'étape précédente
|
||||
const regenerate = useCallback(() => {
|
||||
navigate(Routes.CreateLyricsWithAi);
|
||||
}, []);
|
||||
navigate(Routes.CreateLyricsWithAi)
|
||||
}, [])
|
||||
|
||||
const sanitize = (obj) => {
|
||||
if (obj === undefined) return null;
|
||||
if (obj === null) return null;
|
||||
if (Array.isArray(obj)) return obj.map((v) => sanitize(v));
|
||||
if (typeof obj === "object") {
|
||||
const out = {};
|
||||
if (obj === undefined) return null
|
||||
if (obj === null) return null
|
||||
if (Array.isArray(obj)) return obj.map((v) => sanitize(v))
|
||||
if (typeof obj === 'object') {
|
||||
const out = {}
|
||||
Object.keys(obj).forEach((k) => {
|
||||
const v = obj[k];
|
||||
if (v === undefined) return; // omit undefined
|
||||
out[k] = sanitize(v);
|
||||
});
|
||||
return out;
|
||||
const v = obj[k]
|
||||
if (v === undefined) return // omit undefined
|
||||
out[k] = sanitize(v)
|
||||
})
|
||||
return out
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
return obj
|
||||
}
|
||||
|
||||
const alertMessage = useCallback((title, message) => {
|
||||
alert(title, message);
|
||||
}, []);
|
||||
alert(title, message)
|
||||
}, [])
|
||||
|
||||
const confirmSensitiveContent = useCallback((title, message) => {
|
||||
return new Promise((resolve) => {
|
||||
sensitiveContentResolverRef.current = resolve;
|
||||
sensitiveContentResolverRef.current = resolve
|
||||
setSensitiveContentModal({
|
||||
visible: true,
|
||||
title,
|
||||
message,
|
||||
});
|
||||
setIsSensitiveContentAcknowledged(false);
|
||||
});
|
||||
}, []);
|
||||
})
|
||||
setIsSensitiveContentAcknowledged(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleSensitiveCancel = useCallback(() => {
|
||||
closeSensitiveContentModal(false);
|
||||
}, [closeSensitiveContentModal]);
|
||||
closeSensitiveContentModal(false)
|
||||
}, [closeSensitiveContentModal])
|
||||
|
||||
const handleSensitiveConfirm = useCallback(() => {
|
||||
if (!isSensitiveContentAcknowledged) return;
|
||||
closeSensitiveContentModal(true);
|
||||
}, [closeSensitiveContentModal, isSensitiveContentAcknowledged]);
|
||||
if (!isSensitiveContentAcknowledged) return
|
||||
closeSensitiveContentModal(true)
|
||||
}, [closeSensitiveContentModal, isSensitiveContentAcknowledged])
|
||||
|
||||
const onValidate = useCallback(async () => {
|
||||
try {
|
||||
await setIsLoading(true);
|
||||
const titleTrimmed = (titleValue || "").trim();
|
||||
await setIsLoading(true)
|
||||
const titleTrimmed = (titleValue || '').trim()
|
||||
if (!titleTrimmed) {
|
||||
alertMessage("Titre manquant", "Veuillez renseigner un titre.");
|
||||
return;
|
||||
alertMessage('Titre manquant', 'Veuillez renseigner un titre.')
|
||||
return
|
||||
}
|
||||
const invalid = (sections || []).some((s) => {
|
||||
const type = normalizeStructureType(s?.type);
|
||||
if (!segmentRequiresLyrics(type)) return false;
|
||||
return !(s?.lyrics || "").trim();
|
||||
});
|
||||
const type = normalizeStructureType(s?.type)
|
||||
if (!segmentRequiresLyrics(type)) return false
|
||||
return !(s?.lyrics || '').trim()
|
||||
})
|
||||
if (invalid) {
|
||||
alertMessage(
|
||||
"Champs incomplets",
|
||||
"Chaque section doit contenir du texte.",
|
||||
);
|
||||
return;
|
||||
alertMessage('Champs incomplets', 'Chaque section doit contenir du texte.')
|
||||
return
|
||||
}
|
||||
const normalizedNewLyrics = (sections || []).map((s) => ({
|
||||
type: normalizeStructureType(s?.type),
|
||||
lyrics: segmentRequiresLyrics(normalizeStructureType(s?.type))
|
||||
? (s?.lyrics || "").trim()
|
||||
: "",
|
||||
}));
|
||||
? (s?.lyrics || '').trim()
|
||||
: '',
|
||||
}))
|
||||
const normalizedOldLyrics = Array.isArray(projectLyrics)
|
||||
? projectLyrics.map((s) => ({
|
||||
type: normalizeStructureType(s?.type),
|
||||
lyrics: (s?.lyrics || "").trim(),
|
||||
lyrics: (s?.lyrics || '').trim(),
|
||||
}))
|
||||
: [];
|
||||
const sameLength =
|
||||
normalizedOldLyrics.length === normalizedNewLyrics.length;
|
||||
: []
|
||||
const sameLength = normalizedOldLyrics.length === normalizedNewLyrics.length
|
||||
const isSame =
|
||||
sameLength &&
|
||||
normalizedOldLyrics.every(
|
||||
(s, i) =>
|
||||
s.type === normalizedNewLyrics[i]?.type &&
|
||||
s.lyrics === normalizedNewLyrics[i]?.lyrics,
|
||||
);
|
||||
s.type === normalizedNewLyrics[i]?.type && s.lyrics === normalizedNewLyrics[i]?.lyrics
|
||||
)
|
||||
|
||||
// 1) Appel de la Cloud Function de modération avant tout enregistrement
|
||||
try {
|
||||
const callable = firebase
|
||||
.functions()
|
||||
.httpsCallable("lyrics-analyseLyricsToxicity");
|
||||
const callable = firebase.functions().httpsCallable('lyrics-analyseLyricsToxicity')
|
||||
const { data } = await callable({
|
||||
title: titleTrimmed,
|
||||
lyrics: normalizedNewLyrics,
|
||||
});
|
||||
})
|
||||
|
||||
if (!data?.success) {
|
||||
// Blocage dur: afficher le message et ne pas sauvegarder
|
||||
if (data?.errorCode === "TOXIC_CONTENT_BLOCKED") {
|
||||
if (data?.errorCode === 'TOXIC_CONTENT_BLOCKED') {
|
||||
const quotes = Array.isArray(data?.result?.excerpts)
|
||||
? data.result.excerpts
|
||||
.slice(0, 3)
|
||||
.map((e) => `• ${e.quote}`)
|
||||
.join("\n")
|
||||
: null;
|
||||
alertMessage(
|
||||
"Contenu interdit",
|
||||
[data?.message, quotes].filter(Boolean).join("\n\n"),
|
||||
);
|
||||
return; // stop here
|
||||
.join('\n')
|
||||
: null
|
||||
alertMessage('Contenu interdit', [data?.message, quotes].filter(Boolean).join('\n\n'))
|
||||
return // stop here
|
||||
}
|
||||
// Erreur d'analyse: informer et arrêter
|
||||
if (data?.errorCode === "ANALYSE_FAILED") {
|
||||
if (data?.errorCode === 'ANALYSE_FAILED') {
|
||||
alertMessage(
|
||||
"Analyse indisponible",
|
||||
"Impossible de vérifier la toxicité pour le moment. Réessayez plus tard.",
|
||||
);
|
||||
return;
|
||||
'Analyse indisponible',
|
||||
'Impossible de vérifier la toxicité pour le moment. Réessayez plus tard.'
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Cas signalé mais non bloquant: demander confirmation
|
||||
if (data?.errorCode === "TOXIC_CONTENT_FLAGGED") {
|
||||
if (data?.errorCode === 'TOXIC_CONTENT_FLAGGED') {
|
||||
const quotes = Array.isArray(data?.result?.excerpts)
|
||||
? data.result.excerpts
|
||||
.slice(0, 3)
|
||||
.map((e) => `• ${e.quote}`)
|
||||
.join("\n")
|
||||
: null;
|
||||
const msg = [data?.message, quotes].filter(Boolean).join("\n\n");
|
||||
.join('\n')
|
||||
: null
|
||||
const msg = [data?.message, quotes].filter(Boolean).join('\n\n')
|
||||
|
||||
const proceed = await confirmSensitiveContent(
|
||||
"Contenu potentiellement sensible",
|
||||
msg,
|
||||
);
|
||||
if (!proceed) return;
|
||||
const proceed = await confirmSensitiveContent('Contenu potentiellement sensible', msg)
|
||||
if (!proceed) return
|
||||
}
|
||||
} catch (moderationError) {
|
||||
console.log(
|
||||
"Moderation call failed",
|
||||
moderationError?.message || moderationError,
|
||||
);
|
||||
console.log('Moderation call failed', moderationError?.message || moderationError)
|
||||
alertMessage(
|
||||
"Analyse indisponible",
|
||||
"Impossible de vérifier la toxicité pour le moment. Réessayez plus tard.",
|
||||
);
|
||||
return;
|
||||
'Analyse indisponible',
|
||||
'Impossible de vérifier la toxicité pour le moment. Réessayez plus tard.'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const baseData = {
|
||||
@@ -312,14 +289,14 @@ const Lyrics = ({ navigation }) => {
|
||||
selections: sanitize(projectSelections),
|
||||
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
|
||||
hasLyrics: projectHasLyrics,
|
||||
};
|
||||
|
||||
const updateData = { ...baseData };
|
||||
if (!isSame) {
|
||||
updateData.musicUrls = firebase.firestore.FieldValue.delete();
|
||||
updateData.musicStatus = firebase.firestore.FieldValue.delete();
|
||||
}
|
||||
await projectsRef.doc(selectedProjectId).set(updateData, { merge: true });
|
||||
|
||||
const updateData = { ...baseData }
|
||||
if (!isSame) {
|
||||
updateData.musicUrls = firebase.firestore.FieldValue.delete()
|
||||
updateData.musicStatus = firebase.firestore.FieldValue.delete()
|
||||
}
|
||||
await projectsRef.doc(selectedProjectId).set(updateData, { merge: true })
|
||||
const projectForStage = {
|
||||
...selectedProject,
|
||||
title: titleTrimmed,
|
||||
@@ -327,28 +304,28 @@ const Lyrics = ({ navigation }) => {
|
||||
config: sanitize(projectConfig),
|
||||
selections: sanitize(projectSelections),
|
||||
hasLyrics: true,
|
||||
};
|
||||
if (!isSame) {
|
||||
projectForStage.musicUrls = undefined;
|
||||
projectForStage.musicStatus = undefined;
|
||||
}
|
||||
const beatmakerStage = getStageAction("beatmaker", projectForStage);
|
||||
await setIsLoading(false);
|
||||
if (!isSame) {
|
||||
projectForStage.musicUrls = undefined
|
||||
projectForStage.musicStatus = undefined
|
||||
}
|
||||
const beatmakerStage = getStageAction('beatmaker', projectForStage)
|
||||
await setIsLoading(false)
|
||||
if (hasExistingMusicDraft) {
|
||||
navigate(Routes.ComposeSong, { isRegeneration: true });
|
||||
return;
|
||||
navigate(Routes.ComposeSong, { isRegeneration: true })
|
||||
return
|
||||
}
|
||||
const handleNavigateToStudio = () => {
|
||||
const targetRoute = beatmakerStage?.route || Routes.Compose;
|
||||
navigate(targetRoute, beatmakerStage?.params);
|
||||
};
|
||||
const targetRoute = beatmakerStage?.route || Routes.Compose
|
||||
navigate(targetRoute, beatmakerStage?.params)
|
||||
}
|
||||
alert(
|
||||
"Céline",
|
||||
"Bravo ! Vous venez de terminer de créer les paroles de votre musique ! Maintenant vous pouvez passer à la prochaine étape : le Studio pour donner vie à votre chanson !",
|
||||
'Céline',
|
||||
'Bravo ! Vous venez de terminer de créer les paroles de votre musique ! Maintenant vous pouvez passer à la prochaine étape : le Studio pour donner vie à votre chanson !',
|
||||
[
|
||||
{
|
||||
text: "Retour à l'accueil",
|
||||
style: "cancel",
|
||||
style: 'cancel',
|
||||
onPress: () =>
|
||||
navigate(Routes.BottomTab, {
|
||||
screen: Routes.HomeStack,
|
||||
@@ -358,17 +335,17 @@ const Lyrics = ({ navigation }) => {
|
||||
}),
|
||||
},
|
||||
{
|
||||
text: "Continuer vers le Studio",
|
||||
text: 'Continuer vers le Studio',
|
||||
onPress: handleNavigateToStudio,
|
||||
},
|
||||
],
|
||||
);
|
||||
return;
|
||||
]
|
||||
)
|
||||
return
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
alertMessage("Erreur", "Échec de l'enregistrement dans le projet.");
|
||||
console.log(e)
|
||||
alertMessage('Erreur', "Échec de l'enregistrement dans le projet.")
|
||||
} finally {
|
||||
await setIsLoading(false);
|
||||
await setIsLoading(false)
|
||||
}
|
||||
}, [
|
||||
titleValue,
|
||||
@@ -384,19 +361,13 @@ const Lyrics = ({ navigation }) => {
|
||||
projectLyrics,
|
||||
selectedProject,
|
||||
hasExistingMusicDraft,
|
||||
]);
|
||||
])
|
||||
|
||||
const typeOccurrences = {};
|
||||
const typeOccurrences = {}
|
||||
|
||||
return (
|
||||
<Page
|
||||
backgroundImg={isWeb ? background.libraryBgWeb : background.writingBG}
|
||||
headerType="NONE"
|
||||
>
|
||||
<MusicLandHeader
|
||||
onPressBack={() => navigate(Routes.Home)}
|
||||
progress={95}
|
||||
/>
|
||||
<Page backgroundImg={isWeb ? background.libraryBgWeb : background.writingBG} headerType="NONE">
|
||||
<MusicLandHeader onPressBack={() => navigate(Routes.Home)} progress={95} />
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
@@ -419,15 +390,11 @@ const Lyrics = ({ navigation }) => {
|
||||
}}
|
||||
>
|
||||
<View style={styles.headerContainer}>
|
||||
<Text style={styles.headerTitle}>
|
||||
{strings.writing.lyrics.title}
|
||||
</Text>
|
||||
<Text style={styles.instructions}>
|
||||
{strings.writing.lyrics.instructions}
|
||||
</Text>
|
||||
<Text style={styles.headerTitle}>{strings.writing.lyrics.title}</Text>
|
||||
<Text style={styles.instructions}>{strings.writing.lyrics.instructions}</Text>
|
||||
<Text style={styles.personalizationText}>
|
||||
N’hesites pas a personnaliser les paroles proposées en ajoutant
|
||||
ta touche personnelle mettre mieux en évidence
|
||||
N’hesites pas a personnaliser les paroles proposées en ajoutant ta touche
|
||||
personnelle mettre mieux en évidence
|
||||
</Text>
|
||||
</View>
|
||||
<CustomInput
|
||||
@@ -439,34 +406,33 @@ const Lyrics = ({ navigation }) => {
|
||||
multiline={false}
|
||||
maxLength={60}
|
||||
onLayout={(e) => {
|
||||
updateLayoutAtIndex(0, e?.nativeEvent?.layout);
|
||||
updateLayoutAtIndex(0, e?.nativeEvent?.layout)
|
||||
}}
|
||||
/>
|
||||
{sections.map((s, idx) => {
|
||||
const normalizedType = normalizeStructureType(s?.type);
|
||||
const key = normalizedType || "section";
|
||||
typeOccurrences[key] = (typeOccurrences[key] || 0) + 1;
|
||||
const occurrence = typeOccurrences[key];
|
||||
const label = formatStructureLabel(key, occurrence);
|
||||
const meta = getSegmentMeta(key);
|
||||
const normalizedType = normalizeStructureType(s?.type)
|
||||
const key = normalizedType || 'section'
|
||||
typeOccurrences[key] = (typeOccurrences[key] || 0) + 1
|
||||
const occurrence = typeOccurrences[key]
|
||||
const label = formatStructureLabel(key, occurrence)
|
||||
const meta = getSegmentMeta(key)
|
||||
const inputHeight =
|
||||
typeof meta?.inputHeight === "number"
|
||||
typeof meta?.inputHeight === 'number'
|
||||
? meta.inputHeight
|
||||
: key === "refrain"
|
||||
: key === 'refrain'
|
||||
? 170
|
||||
: 225;
|
||||
const placeholder = meta?.label || label;
|
||||
const requiresLyrics = segmentRequiresLyrics(key);
|
||||
: 225
|
||||
const placeholder = meta?.label || label
|
||||
const requiresLyrics = segmentRequiresLyrics(key)
|
||||
if (!requiresLyrics) {
|
||||
return (
|
||||
<View key={idx} style={styles.instrumentalBlock}>
|
||||
<Text style={styles.instrumentalTitle}>{label}</Text>
|
||||
<Text style={styles.instrumentalText}>
|
||||
{getPromptLabelForStructure(key)} — section instrumentale
|
||||
sans paroles.
|
||||
{getPromptLabelForStructure(key)} — section instrumentale sans paroles.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
)
|
||||
}
|
||||
return (
|
||||
<CustomInput
|
||||
@@ -474,14 +440,14 @@ const Lyrics = ({ navigation }) => {
|
||||
label={label}
|
||||
placeholder={placeholder}
|
||||
height={inputHeight}
|
||||
value={s?.lyrics || ""}
|
||||
value={s?.lyrics || ''}
|
||||
setValue={(val) => setSectionAt(idx, val)}
|
||||
onFocus={() => handleSectionFocus(idx)}
|
||||
onLayout={(e) => {
|
||||
updateLayoutAtIndex(idx + 1, e?.nativeEvent?.layout);
|
||||
updateLayoutAtIndex(idx + 1, e?.nativeEvent?.layout)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</ScrollView>
|
||||
</ItemContainer>
|
||||
@@ -495,10 +461,7 @@ const Lyrics = ({ navigation }) => {
|
||||
}}
|
||||
>
|
||||
{!projectHasLyrics && (
|
||||
<BorderGradientButton
|
||||
title="Générer d'autres paroles"
|
||||
onPress={regenerate}
|
||||
/>
|
||||
<BorderGradientButton title="Générer d'autres paroles" onPress={regenerate} />
|
||||
)}
|
||||
<GradientButton title="Valider" onPress={onValidate} />
|
||||
</View>
|
||||
@@ -506,21 +469,17 @@ const Lyrics = ({ navigation }) => {
|
||||
isVisible={sensitiveContentModal.visible}
|
||||
setIsVisible={(visible) => {
|
||||
if (visible === false) {
|
||||
handleSensitiveCancel();
|
||||
handleSensitiveCancel()
|
||||
}
|
||||
}}
|
||||
contentContainerStyle={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<View style={styles.sensitiveModal}>
|
||||
<Text style={styles.sensitiveModalTitle}>
|
||||
{sensitiveContentModal.title}
|
||||
</Text>
|
||||
<Text style={styles.sensitiveModalMessage}>
|
||||
{sensitiveContentModal.message}
|
||||
</Text>
|
||||
<Text style={styles.sensitiveModalTitle}>{sensitiveContentModal.title}</Text>
|
||||
<Text style={styles.sensitiveModalMessage}>{sensitiveContentModal.message}</Text>
|
||||
<View style={styles.sensitiveCheckboxWrapper}>
|
||||
<AppCheckbox
|
||||
selected={isSensitiveContentAcknowledged}
|
||||
@@ -544,10 +503,10 @@ const Lyrics = ({ navigation }) => {
|
||||
</View>
|
||||
</Overlay>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default Lyrics;
|
||||
export default Lyrics
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
headerContainer: {
|
||||
@@ -590,7 +549,7 @@ const styles = StyleSheet.create({
|
||||
opacity: 0.8,
|
||||
},
|
||||
sensitiveModal: {
|
||||
width: "90%",
|
||||
width: '90%',
|
||||
maxWidth: 460,
|
||||
backgroundColor: Palette.lightPurple,
|
||||
borderRadius: 18,
|
||||
@@ -602,21 +561,21 @@ const styles = StyleSheet.create({
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
fontSize: 20,
|
||||
color: Palette.white,
|
||||
textAlign: "center",
|
||||
textAlign: 'center',
|
||||
},
|
||||
sensitiveModalMessage: {
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 15,
|
||||
color: Palette.white,
|
||||
lineHeight: 20,
|
||||
textAlign: "left",
|
||||
textAlign: 'left',
|
||||
opacity: 0.9,
|
||||
},
|
||||
sensitiveCheckboxWrapper: {
|
||||
paddingVertical: 4,
|
||||
},
|
||||
sensitiveModalActions: {
|
||||
flexDirection: "row",
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user