From 69977aba2bed484c3ba06cd2d6cc40c3bfefabb1 Mon Sep 17 00:00:00 2001 From: Philip Cesar Garay Date: Tue, 9 Sep 2025 17:17:11 +0800 Subject: [PATCH] Fix Task: #64 --- src/components/ItemContainer/ItemContainer.js | 11 ++++- src/screens/Writing/CreateLyricsWithAi.js | 15 ++++-- src/screens/Writing/Goals.js | 19 ++++++- src/screens/Writing/Lyrics.js | 49 ++++++++++++++++--- src/screens/Writing/components/CustomInput.js | 7 ++- 5 files changed, 84 insertions(+), 17 deletions(-) diff --git a/src/components/ItemContainer/ItemContainer.js b/src/components/ItemContainer/ItemContainer.js index 367c9e7..3a7f1fc 100644 --- a/src/components/ItemContainer/ItemContainer.js +++ b/src/components/ItemContainer/ItemContainer.js @@ -3,13 +3,17 @@ import React from "react"; import BorderGradient from "../BorderGradient/BorderGradient"; import { BlurView } from "expo-blur"; import { responsiveHeight } from "react-native-responsive-dimensions"; +import { useKeyboard } from "@react-native-community/hooks"; const ItemContainer = ({ height = responsiveHeight(40), children, gradientProps, style, + disableKeyboardHeight = false, }) => { + const { keyboardShown } = useKeyboard(); + return ( diff --git a/src/screens/Writing/CreateLyricsWithAi.js b/src/screens/Writing/CreateLyricsWithAi.js index ae8d8a4..22a572c 100644 --- a/src/screens/Writing/CreateLyricsWithAi.js +++ b/src/screens/Writing/CreateLyricsWithAi.js @@ -1,4 +1,4 @@ -import { View, Dimensions } from "react-native"; +import { View, Dimensions, Platform } from "react-native"; import React, { useMemo, useRef, useState } from "react"; import Page from "../../layouts/Page"; import MusicLandHeader from "../../components/MusicLandHeader"; @@ -73,7 +73,8 @@ const CreateLyricsWithAi = () => { setStyle(sel.style); if (!otherStyle && typeof sel.otherStyle === "string") setOtherStyle(sel.otherStyle); - if (!audience && typeof sel.audience === "string") setAudience(sel.audience); + 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) @@ -216,7 +217,11 @@ const CreateLyricsWithAi = () => { }, [selectedIndex, parentLayout?.height]); return ( - + { (Array.isArray(customStructure) ? customStructure : Array.isArray(selectedProject?.config?.structure) - ? selectedProject.config.structure - : []) + ? selectedProject.config.structure + : []) } onChange={setCustomStructure} /> diff --git a/src/screens/Writing/Goals.js b/src/screens/Writing/Goals.js index 696c4a0..9935f4d 100644 --- a/src/screens/Writing/Goals.js +++ b/src/screens/Writing/Goals.js @@ -1,4 +1,12 @@ -import { View, Text, FlatList, StyleSheet, Pressable } from "react-native"; +import { + View, + Text, + FlatList, + StyleSheet, + Pressable, + KeyboardAvoidingView, + Platform, +} from "react-native"; import React, { useState } from "react"; import CreateLyricsHeader from "./components/CreateLyricsHeader"; import { Palette, Style } from "../../styles"; @@ -6,8 +14,15 @@ import { GOALS } from "../../data/data"; import { FONT_FAMILY } from "../../styles/Fonts"; import CustomInput from "./components/CustomInput"; import ItemContainer from "../../components/ItemContainer/ItemContainer"; +import { useKeyboard } from "@react-native-community/hooks"; +import { responsiveHeight } from "react-native-responsive-dimensions"; -const Goals = ({ selected: selectedProp, setSelected: setSelectedProp, otherObjective, setOtherObjective }) => { +const Goals = ({ + selected: selectedProp, + setSelected: setSelectedProp, + otherObjective, + setOtherObjective, +}) => { const [internalSelected, setInternalSelected] = useState(null); const selected = selectedProp ?? internalSelected; diff --git a/src/screens/Writing/Lyrics.js b/src/screens/Writing/Lyrics.js index dd218c3..7b77738 100644 --- a/src/screens/Writing/Lyrics.js +++ b/src/screens/Writing/Lyrics.js @@ -1,5 +1,11 @@ import { View, ScrollView, Alert } from "react-native"; -import React, { useMemo, useState, useCallback, useEffect } from "react"; +import React, { + useMemo, + useState, + useCallback, + useEffect, + useRef, +} from "react"; import Page from "../../layouts/Page"; import MusicLandHeader from "../../components/MusicLandHeader"; import { navigate } from "../../navigation/NavigationService"; @@ -14,9 +20,12 @@ import useMinuit from "react-native-minuit/src/hooks/useMinuit.js"; import { useUser } from "../../providers/UserDataProvider"; const Lyrics = ({ navigation }) => { + const scrollRef = useRef(null); const [containerLayout, setContainerLayout] = useState(null); const { setIsLoading } = useMinuit(); const { selectedProjectId, selectedProject } = useUser(); + const [isFocus, setIsFocus] = useState(null); + const [itemsContainerLayout, setItemsContainerLayout] = useState([]); // Effective sources from provider only const projectTitle = selectedProject?.title || ""; @@ -103,7 +112,7 @@ const Lyrics = ({ navigation }) => { if (invalid) { Alert.alert( "Champs incomplets", - "Chaque couplet et refrain doit contenir du texte.", + "Chaque couplet et refrain doit contenir du texte." ); return; } @@ -124,7 +133,7 @@ const Lyrics = ({ navigation }) => { normalizedOldLyrics.every( (s, i) => s.type === normalizedNewLyrics[i]?.type && - s.lyrics === normalizedNewLyrics[i]?.lyrics, + s.lyrics === normalizedNewLyrics[i]?.lyrics ); // 1) Appel de la Cloud Function de modération avant tout enregistrement @@ -148,7 +157,7 @@ const Lyrics = ({ navigation }) => { : null; Alert.alert( "Contenu interdit", - [data?.message, quotes].filter(Boolean).join("\n\n"), + [data?.message, quotes].filter(Boolean).join("\n\n") ); return; // stop here } @@ -156,7 +165,7 @@ const Lyrics = ({ navigation }) => { if (data?.errorCode === "ANALYSE_FAILED") { Alert.alert( "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." ); return; } @@ -191,11 +200,11 @@ const Lyrics = ({ navigation }) => { } catch (moderationError) { console.log( "Moderation call failed", - moderationError?.message || moderationError, + moderationError?.message || moderationError ); Alert.alert( "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." ); return; } @@ -250,8 +259,12 @@ const Lyrics = ({ navigation }) => { }} onLayout={(e) => setContainerLayout(e.nativeEvent.layout)} > - + { setValue={setTitleValue} multiline={false} maxLength={60} + onLayout={(e) => { + e.persist(); + setItemsContainerLayout((prev) => [ + ...prev, + e?.nativeEvent?.layout, + ]); + }} /> {sections.map((s, idx) => { // Calculer l'index humain par type @@ -284,6 +304,19 @@ const Lyrics = ({ navigation }) => { height={type === "refrain" ? 170 : 225} value={s?.lyrics || ""} setValue={(val) => setSectionAt(idx, val)} + onFocus={() => { + setIsFocus(idx); + scrollRef?.current?.scrollTo({ + y: itemsContainerLayout[idx + 1]?.y, + }); + }} + onLayout={(e) => { + e.persist(); + setItemsContainerLayout((prev) => [ + ...prev, + e?.nativeEvent?.layout, + ]); + }} /> ); })} diff --git a/src/screens/Writing/components/CustomInput.js b/src/screens/Writing/components/CustomInput.js index 8aa0d82..9b18d5c 100644 --- a/src/screens/Writing/components/CustomInput.js +++ b/src/screens/Writing/components/CustomInput.js @@ -11,9 +11,12 @@ const CustomInput = ({ height = 65, multiline = true, maxLength, + onFocus, + onBlur, + onLayout, }) => { return ( - + {label && (