import React, { useMemo, useRef, useState } from "react"; import { Dimensions, Platform, View } from "react-native"; import { responsiveHeight } from "react-native-responsive-dimensions"; import { SwiperFlatList } from "react-native-swiper-flatlist"; import { background } from "../../assets"; import GradientButton from "../../components/GradientButton"; import MusicLandHeader from "../../components/MusicLandHeader"; import { OTHER_OBJECTIVE_OPTION, OTHER_STYLE_OPTION } from "../../data/data"; import useLayoutType, { 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 } from "../../styles"; import CreatingLyrics from "./CreatingLyrics"; import CustomizeSongStructure from "./CustomizeSongStructure"; import EmotionConvey from "./EmotionConvey"; import Goals from "./Goals"; import Rhymes from "./Rhymes"; import SongStructure from "./SongStructure"; import SongStyle from "./SongStyle"; import SongTo from "./SongTo"; import SpecificityContext from "./SpecificityContext"; const { width: windowWidth } = Dimensions.get("window"); const CreateLyricsWithAi = () => { const { isWeb } = useLayoutType(); const { selectedProject, updateProjectData } = useUser(); const hasLyrics = selectedProject?.hasLyrics === true; const scrollRef = useRef(null); const [selectedIndex, setSelectedIndex] = useState(hasLyrics ? 5 : 0); const [progress, setProgress] = useState(16); const [parentLayout, setparentLayout] = useState(null); const containerWidth = windowWidth; // Collected state across steps const [objective, setObjective] = useState(null); // from Goals list const [otherObjective, setOtherObjective] = useState(""); const [context, setContext] = useState(""); const [emotion, setEmotion] = useState(null); const [style, setStyle] = useState(null); // from list const [otherStyle, setOtherStyle] = useState(""); const [audience, setAudience] = useState(""); const [structure, setStructure] = useState(null); // selected structure string const [rhymes, setRhymes] = useState(null); const [customStructure, setCustomStructure] = useState(null); // array like ['couplet','refrain'] const trimmedOtherObjective = typeof otherObjective === "string" ? otherObjective.trim() : ""; const shouldUseOtherObjective = objective === OTHER_OBJECTIVE_OPTION; const persistedOtherObjective = shouldUseOtherObjective ? trimmedOtherObjective : ""; const trimmedOtherStyle = typeof otherStyle === "string" ? otherStyle.trim() : ""; const shouldUseOtherStyle = style === OTHER_STYLE_OPTION; const persistedOtherStyle = shouldUseOtherStyle ? trimmedOtherStyle : ""; // Pré-remplir les états depuis le projet sélectionné si disponibles React.useEffect(() => { if (!selectedProject) return; const sel = selectedProject?.selections || {}; const cfg = selectedProject?.config || {}; const rawOtherObjective = typeof sel.otherObjective === "string" ? sel.otherObjective : ""; const trimmedSavedOtherObjective = rawOtherObjective.trim(); const hasSavedOtherObjective = trimmedSavedOtherObjective.length > 0; const savedObjective = typeof sel.objective === "string" && sel.objective.trim().length > 0 ? sel.objective.trim() : null; if (!otherObjective && hasSavedOtherObjective) { setOtherObjective(rawOtherObjective); } const shouldSelectOtherObjective = hasSavedOtherObjective && (!savedObjective || savedObjective === OTHER_OBJECTIVE_OPTION); const resolvedSavedObjective = shouldSelectOtherObjective ? OTHER_OBJECTIVE_OPTION : savedObjective; if (resolvedSavedObjective && objective !== resolvedSavedObjective) { setObjective(resolvedSavedObjective); } else if (!resolvedSavedObjective && objective !== null) { setObjective(null); } if (!context && typeof sel.context === "string") setContext(sel.context); // Emotion: accepter objet {title, description} ou string "Titre : description" if (emotion == null && sel.emotion) { if (typeof sel.emotion === "object" && sel.emotion.title) { setEmotion({ title: sel.emotion.title, description: sel.emotion.description || "", }); } else if (typeof sel.emotion === "string") { const [t, d] = sel.emotion.split(":"); const title = (t || "").trim(); const description = (d || "").trim(); if (title) setEmotion({ title, description }); } } const rawOtherStyle = typeof sel.otherStyle === "string" ? sel.otherStyle : ""; const trimmedSavedOtherStyle = rawOtherStyle.trim(); const hasSavedOtherStyle = trimmedSavedOtherStyle.length > 0; const savedStyle = typeof sel.style === "string" && sel.style.trim().length > 0 ? sel.style.trim() : null; if (!otherStyle && hasSavedOtherStyle) { setOtherStyle(rawOtherStyle); } const shouldSelectOtherStyle = hasSavedOtherStyle && (!savedStyle || savedStyle === OTHER_STYLE_OPTION); const resolvedSavedStyle = shouldSelectOtherStyle ? OTHER_STYLE_OPTION : savedStyle; if (resolvedSavedStyle && style !== resolvedSavedStyle) { setStyle(resolvedSavedStyle); } else if (!resolvedSavedStyle && style !== null) { setStyle(null); } if (!audience && typeof sel.audience === "string") setAudience(sel.audience); // Structure choisie (string) si déjà enregistrée dans selections if (structure == null && typeof sel.structure === "string" && sel.structure) setStructure(sel.structure); // Rimes if (rhymes == null && typeof sel.rhymes === "string" && sel.rhymes) setRhymes(sel.rhymes); // Structure personnalisée: prioriser selections.customStructure puis config.structure const savedCustom = Array.isArray(sel.customStructure) ? sel.customStructure : null; const cfgStructure = Array.isArray(cfg.structure) ? cfg.structure : null; if (!Array.isArray(customStructure) && (savedCustom || cfgStructure)) { setCustomStructure(savedCustom || cfgStructure); } }, [selectedProject]); const parsedStructure = useMemo(() => { // Parses strings like "1 couplet, 1 refrain, 1 couplet, 1 refrain" try { if (!structure || typeof structure !== "string") return null; const parts = structure.split(","); const result = []; parts.forEach((seg) => { const s = seg.trim().toLowerCase(); const coupletMatch = s.match(/(\d+)\s+couplet/); const refrainMatch = s.match(/(\d+)\s+refrain/); if (coupletMatch) { const count = parseInt(coupletMatch[1], 10); for (let i = 0; i < count; i++) result.push("couplet"); } if (refrainMatch) { const count = parseInt(refrainMatch[1], 10); for (let i = 0; i < count; i++) result.push("refrain"); } }); return result.length ? result : null; } catch (e) { return null; } }, [structure]); // Si déjà des paroles, forcer l'accès à partir de l'étape 5 et ignorer 0-4 React.useEffect(() => { if (hasLyrics && selectedIndex < 5) { setSelectedIndex(5); } }, [hasLyrics]); const lyricsConfig = useMemo(() => { const resolvedObjective = shouldUseOtherObjective ? persistedOtherObjective || undefined : objective || undefined; const resolvedStyle = shouldUseOtherStyle ? persistedOtherStyle || undefined : style || undefined; return { objective: resolvedObjective, context: context?.trim() ? context.trim() : undefined, emotion: emotion?.title && emotion?.description ? `${emotion.title} : ${emotion.description}` : undefined, style: resolvedStyle, audience: audience?.trim() ? audience.trim() : undefined, structure: (customStructure && parsedStructure && customStructure.length === parsedStructure.length ? customStructure : parsedStructure) || undefined, rhymes: rhymes || undefined, }; }, [ shouldUseOtherObjective, persistedOtherObjective, objective, shouldUseOtherStyle, persistedOtherStyle, context, emotion, style, audience, parsedStructure, rhymes, customStructure, ]); const isNextDisabled = React.useMemo(() => { switch (selectedIndex) { case 0: { const hasObjective = typeof objective === "string" && objective.length > 0; const hasOther = trimmedOtherObjective.length > 0; return !(hasObjective || hasOther); } case 1: { const hasContext = typeof context === "string" && context.trim().length > 0; return !hasContext; } case 2: { const hasEmotion = (emotion && typeof emotion === "object" && !!emotion.title) || (typeof emotion === "string" && emotion.trim().length > 0); return !hasEmotion; } case 3: { const hasStyle = typeof style === "string" && style.length > 0; const hasOther = trimmedOtherStyle.length > 0; return !(hasStyle || hasOther); } case 4: { const hasAudience = typeof audience === "string" && audience.trim().length > 0; return !hasAudience; } case 5: { const hasStructure = typeof structure === "string" && structure.length > 0; return !hasStructure; } case 7: { const hasRhymes = typeof rhymes === "string" && rhymes.length > 0; return !hasRhymes; } default: return false; } }, [ selectedIndex, objective, trimmedOtherObjective, context, emotion, style, trimmedOtherStyle, audience, structure, rhymes, ]); const onPressNext = async () => { // Si l'utilisateur a déjà des paroles et vient de finir CustomizeSongStructure (index 6), // sauter Rhymes et CreatingLyrics et aller directement sur Lyrics if (hasLyrics && selectedIndex === 6) { const chosenStructure = (customStructure && parsedStructure && customStructure.length === parsedStructure.length ? customStructure : parsedStructure) || []; await updateProjectData({ config: { structure: chosenStructure }, selections: { objective, otherObjective: persistedOtherObjective, context, emotion, style, otherStyle: persistedOtherStyle, audience, structure, parsedStructure, customStructure, rhymes, }, hasLyrics: true, }); navigate(Routes.Lyrics); return; } setSelectedIndex((idx) => idx + 1); }; const onPressBack = () => { // If currently on SongStructure, go back to previous screen instead of previous step if (selectedIndex === 5) { goBack(); } else if (selectedIndex > 0) { setSelectedIndex((idx) => Math.max(0, idx - 1)); } else { goBack(); } }; // Sync scroll position and progress with selectedIndex React.useEffect(() => { if (!parentLayout?.height) return; try { const nextProgress = 16 + selectedIndex * 9; setProgress(nextProgress); scrollRef.current?.scrollToIndex?.({ index: selectedIndex, animated: true, }); } catch (_) {} }, [selectedIndex, parentLayout?.height]); return ( setparentLayout(e.nativeEvent.layout)} > {selectedIndex !== 8 && ( )} ); }; export default CreateLyricsWithAi;