create flow
This commit is contained in:
@@ -30,7 +30,7 @@ const CreateLyricsWithAi = () => {
|
|||||||
const [selectedIndex, setSelectedIndex] = useState(hasLyrics ? 5 : 0);
|
const [selectedIndex, setSelectedIndex] = useState(hasLyrics ? 5 : 0);
|
||||||
const [progress, setProgress] = useState(16);
|
const [progress, setProgress] = useState(16);
|
||||||
const [parentLayout, setparentLayout] = useState(null);
|
const [parentLayout, setparentLayout] = useState(null);
|
||||||
const containerWidth = isWeb ? parentLayout?.width : windowWidth;
|
const containerWidth = windowWidth;
|
||||||
|
|
||||||
// Collected state across steps
|
// Collected state across steps
|
||||||
const [objective, setObjective] = useState(null); // from Goals list
|
const [objective, setObjective] = useState(null); // from Goals list
|
||||||
@@ -50,11 +50,23 @@ const CreateLyricsWithAi = () => {
|
|||||||
const sel = selectedProject?.selections || {};
|
const sel = selectedProject?.selections || {};
|
||||||
const cfg = selectedProject?.config || {};
|
const cfg = selectedProject?.config || {};
|
||||||
|
|
||||||
// Text inputs / choix simples
|
const rawOtherObjective =
|
||||||
if (objective == null && typeof sel.objective === "string" && sel.objective)
|
typeof sel.otherObjective === "string" ? sel.otherObjective : "";
|
||||||
setObjective(sel.objective);
|
const hasSavedOtherObjective = rawOtherObjective.trim().length > 0;
|
||||||
if (!otherObjective && typeof sel.otherObjective === "string")
|
const savedObjective =
|
||||||
setOtherObjective(sel.otherObjective);
|
typeof sel.objective === "string" && sel.objective.trim().length > 0
|
||||||
|
? sel.objective
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!otherObjective && hasSavedOtherObjective) {
|
||||||
|
setOtherObjective(rawOtherObjective);
|
||||||
|
if (objective !== null) {
|
||||||
|
setObjective(null);
|
||||||
|
}
|
||||||
|
} else if (!hasSavedOtherObjective && objective == null && savedObjective) {
|
||||||
|
setObjective(savedObjective);
|
||||||
|
}
|
||||||
|
|
||||||
if (!context && typeof sel.context === "string") setContext(sel.context);
|
if (!context && typeof sel.context === "string") setContext(sel.context);
|
||||||
|
|
||||||
// Emotion: accepter objet {title, description} ou string "Titre : description"
|
// Emotion: accepter objet {title, description} ou string "Titre : description"
|
||||||
@@ -72,10 +84,22 @@ const CreateLyricsWithAi = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style == null && typeof sel.style === "string" && sel.style)
|
const rawOtherStyle =
|
||||||
setStyle(sel.style);
|
typeof sel.otherStyle === "string" ? sel.otherStyle : "";
|
||||||
if (!otherStyle && typeof sel.otherStyle === "string")
|
const hasSavedOtherStyle = rawOtherStyle.trim().length > 0;
|
||||||
setOtherStyle(sel.otherStyle);
|
const savedStyle =
|
||||||
|
typeof sel.style === "string" && sel.style.trim().length > 0
|
||||||
|
? sel.style
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!otherStyle && hasSavedOtherStyle) {
|
||||||
|
setOtherStyle(rawOtherStyle);
|
||||||
|
if (style !== null) {
|
||||||
|
setStyle(null);
|
||||||
|
}
|
||||||
|
} else if (!hasSavedOtherStyle && style == null && savedStyle) {
|
||||||
|
setStyle(savedStyle);
|
||||||
|
}
|
||||||
if (!audience && typeof sel.audience === "string")
|
if (!audience && typeof sel.audience === "string")
|
||||||
setAudience(sel.audience);
|
setAudience(sel.audience);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,457 @@
|
|||||||
|
import React, { useMemo, useRef, useState } from "react";
|
||||||
|
import { Dimensions, FlatList, Platform, View } from "react-native";
|
||||||
|
import { responsiveHeight } from "react-native-responsive-dimensions";
|
||||||
|
import { background } from "../../assets";
|
||||||
|
import GradientButton from "../../components/GradientButton";
|
||||||
|
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||||
|
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 { 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 = parentLayout?.width || windowWidth || 1;
|
||||||
|
|
||||||
|
// 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']
|
||||||
|
|
||||||
|
// 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 hasSavedOtherObjective = rawOtherObjective.trim().length > 0;
|
||||||
|
const savedObjective =
|
||||||
|
typeof sel.objective === "string" && sel.objective.trim().length > 0
|
||||||
|
? sel.objective
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!otherObjective && hasSavedOtherObjective) {
|
||||||
|
setOtherObjective(rawOtherObjective);
|
||||||
|
if (objective !== null) {
|
||||||
|
setObjective(null);
|
||||||
|
}
|
||||||
|
} else if (!hasSavedOtherObjective && objective == null && savedObjective) {
|
||||||
|
setObjective(savedObjective);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 hasSavedOtherStyle = rawOtherStyle.trim().length > 0;
|
||||||
|
const savedStyle =
|
||||||
|
typeof sel.style === "string" && sel.style.trim().length > 0
|
||||||
|
? sel.style
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!otherStyle && hasSavedOtherStyle) {
|
||||||
|
setOtherStyle(rawOtherStyle);
|
||||||
|
if (style !== null) {
|
||||||
|
setStyle(null);
|
||||||
|
}
|
||||||
|
} else if (!hasSavedOtherStyle && style == null && savedStyle) {
|
||||||
|
setStyle(savedStyle);
|
||||||
|
}
|
||||||
|
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(() => {
|
||||||
|
return {
|
||||||
|
objective: otherObjective?.trim()
|
||||||
|
? otherObjective.trim()
|
||||||
|
: objective || undefined,
|
||||||
|
context: context?.trim() ? context.trim() : undefined,
|
||||||
|
emotion:
|
||||||
|
emotion?.title && emotion?.description
|
||||||
|
? `${emotion.title} : ${emotion.description}`
|
||||||
|
: undefined,
|
||||||
|
style: otherStyle?.trim() ? otherStyle.trim() : style || undefined,
|
||||||
|
audience: audience?.trim() ? audience.trim() : undefined,
|
||||||
|
structure:
|
||||||
|
(customStructure &&
|
||||||
|
parsedStructure &&
|
||||||
|
customStructure.length === parsedStructure.length
|
||||||
|
? customStructure
|
||||||
|
: parsedStructure) || undefined,
|
||||||
|
rhymes: rhymes || undefined,
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
objective,
|
||||||
|
otherObjective,
|
||||||
|
context,
|
||||||
|
emotion,
|
||||||
|
style,
|
||||||
|
otherStyle,
|
||||||
|
audience,
|
||||||
|
parsedStructure,
|
||||||
|
rhymes,
|
||||||
|
customStructure,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const isNextDisabled = React.useMemo(() => {
|
||||||
|
switch (selectedIndex) {
|
||||||
|
case 0: {
|
||||||
|
const hasObjective =
|
||||||
|
typeof objective === "string" && objective.length > 0;
|
||||||
|
const hasOther =
|
||||||
|
typeof otherObjective === "string" &&
|
||||||
|
otherObjective.trim().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 =
|
||||||
|
typeof otherStyle === "string" && otherStyle.trim().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,
|
||||||
|
otherObjective,
|
||||||
|
context,
|
||||||
|
emotion,
|
||||||
|
style,
|
||||||
|
otherStyle,
|
||||||
|
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,
|
||||||
|
context,
|
||||||
|
emotion,
|
||||||
|
style,
|
||||||
|
otherStyle,
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
key: "goals",
|
||||||
|
render: () => (
|
||||||
|
<Goals
|
||||||
|
selected={objective}
|
||||||
|
setSelected={setObjective}
|
||||||
|
otherObjective={otherObjective}
|
||||||
|
setOtherObjective={setOtherObjective}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "context",
|
||||||
|
render: () => (
|
||||||
|
<SpecificityContext context={context} setContext={setContext} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "emotion",
|
||||||
|
render: () => (
|
||||||
|
<EmotionConvey selected={emotion} setSelected={setEmotion} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "style",
|
||||||
|
render: () => (
|
||||||
|
<SongStyle
|
||||||
|
selected={style}
|
||||||
|
setSelected={setStyle}
|
||||||
|
otherStyle={otherStyle}
|
||||||
|
setOtherStyle={setOtherStyle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "audience",
|
||||||
|
render: () => <SongTo audience={audience} setAudience={setAudience} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "structure",
|
||||||
|
render: () => (
|
||||||
|
<SongStructure selected={structure} setSelected={setStructure} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "customStructure",
|
||||||
|
render: () => (
|
||||||
|
<CustomizeSongStructure
|
||||||
|
baseStructure={
|
||||||
|
parsedStructure ||
|
||||||
|
(Array.isArray(customStructure)
|
||||||
|
? customStructure
|
||||||
|
: Array.isArray(selectedProject?.config?.structure)
|
||||||
|
? selectedProject.config.structure
|
||||||
|
: [])
|
||||||
|
}
|
||||||
|
onChange={setCustomStructure}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "rhymes",
|
||||||
|
render: () => <Rhymes selected={rhymes} setSelected={setRhymes} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "creating",
|
||||||
|
render: () => (
|
||||||
|
<CreatingLyrics
|
||||||
|
active={selectedIndex === 8}
|
||||||
|
config={lyricsConfig}
|
||||||
|
selections={{
|
||||||
|
objective,
|
||||||
|
otherObjective,
|
||||||
|
context,
|
||||||
|
emotion,
|
||||||
|
style,
|
||||||
|
otherStyle,
|
||||||
|
audience,
|
||||||
|
structure,
|
||||||
|
parsedStructure,
|
||||||
|
customStructure,
|
||||||
|
rhymes,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getItemLayout = React.useCallback(
|
||||||
|
(_, index) => ({
|
||||||
|
length: containerWidth,
|
||||||
|
offset: containerWidth * index,
|
||||||
|
index,
|
||||||
|
}),
|
||||||
|
[containerWidth],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
viewPosition: 0,
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
}, [selectedIndex, parentLayout?.height, containerWidth]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page
|
||||||
|
headerType="NONE"
|
||||||
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||||
|
keyboardVerticalOffset={Platform.OS === "ios" ? 64 : 100}
|
||||||
|
backgroundImg={background.homeBG}
|
||||||
|
>
|
||||||
|
<MusicLandHeader onPressBack={onPressBack} progress={progress} />
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
paddingBottom: gutters,
|
||||||
|
gap: responsiveHeight(5),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
onLayout={(e) => setParentLayout(e.nativeEvent.layout)}
|
||||||
|
>
|
||||||
|
<FlatList
|
||||||
|
ref={scrollRef}
|
||||||
|
data={steps}
|
||||||
|
keyExtractor={(item) => item.key}
|
||||||
|
horizontal
|
||||||
|
pagingEnabled
|
||||||
|
scrollEnabled={false}
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
initialScrollIndex={selectedIndex}
|
||||||
|
getItemLayout={getItemLayout}
|
||||||
|
style={{ width: containerWidth }}
|
||||||
|
renderItem={({ item }) => (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: containerWidth,
|
||||||
|
height: parentLayout?.height,
|
||||||
|
paddingHorizontal: gutters,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.render()}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
{selectedIndex !== 8 && (
|
||||||
|
<GradientButton
|
||||||
|
maxWidth={500}
|
||||||
|
title={selectedIndex === 7 ? "Générer" : "Suivant"}
|
||||||
|
disabled={isNextDisabled}
|
||||||
|
onPress={onPressNext}
|
||||||
|
containerStyle={{ alignSelf: "center", width: "100%" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateLyricsWithAi;
|
||||||
@@ -19,6 +19,13 @@ const Goals = ({
|
|||||||
const selected = selectedProp ?? internalSelected;
|
const selected = selectedProp ?? internalSelected;
|
||||||
const setSelected = setSelectedProp ?? setInternalSelected;
|
const setSelected = setSelectedProp ?? setInternalSelected;
|
||||||
|
|
||||||
|
const handleOtherObjectiveChange = (value) => {
|
||||||
|
setOtherObjective?.(value);
|
||||||
|
if (value?.trim()?.length && selected != null) {
|
||||||
|
setSelected(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Selection handled by ListSelection
|
// Selection handled by ListSelection
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -41,7 +48,7 @@ const Goals = ({
|
|||||||
label="Tu as un autre objectif?"
|
label="Tu as un autre objectif?"
|
||||||
placeholder="Décrire l’objectif"
|
placeholder="Décrire l’objectif"
|
||||||
value={otherObjective}
|
value={otherObjective}
|
||||||
setValue={setOtherObjective}
|
setValue={handleOtherObjectiveChange}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ const SongStyle = ({
|
|||||||
const selected = selectedProp ?? internalSelected;
|
const selected = selectedProp ?? internalSelected;
|
||||||
const setSelected = setSelectedProp ?? setInternalSelected;
|
const setSelected = setSelectedProp ?? setInternalSelected;
|
||||||
|
|
||||||
|
const handleOtherStyleChange = (value) => {
|
||||||
|
setOtherStyle?.(value);
|
||||||
|
if (value?.trim()?.length && selected != null) {
|
||||||
|
setSelected(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Selection handled by ListSelection
|
// Selection handled by ListSelection
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -42,7 +49,7 @@ const SongStyle = ({
|
|||||||
label="Tu as un autre style de chanson ?"
|
label="Tu as un autre style de chanson ?"
|
||||||
placeholder="Décrire l’objectif"
|
placeholder="Décrire l’objectif"
|
||||||
value={otherStyle}
|
value={otherStyle}
|
||||||
setValue={setOtherStyle}
|
setValue={handleOtherStyleChange}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user