diff --git a/src/screens/cover/PouchReady.js b/src/screens/cover/PouchReady.js index 87e7c4d..94b0fc3 100644 --- a/src/screens/cover/PouchReady.js +++ b/src/screens/cover/PouchReady.js @@ -1,9 +1,19 @@ +import { BlurView } from "expo-blur"; import { Image as ExpoImage } from "expo-image"; -import React, { useCallback, useEffect, useState } from "react"; -import { ActivityIndicator, Text, View } from "react-native"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { + ActivityIndicator, + Image, + Pressable, + StyleSheet, + Text, + TextInput, + 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 MusicLandHeader from "../../components/MusicLandHeader"; @@ -15,27 +25,69 @@ import { Routes } from "../../navigation"; import { goBack, navigate } from "../../navigation/NavigationService"; import { useUser } from "../../providers/UserDataProvider"; import { gutters, Palette, Style } from "../../styles"; +import { FONT_FAMILY } from "../../styles/Fonts"; +import { getStageAction } from "../../utils/projectStages"; import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader"; -import CustomInput from "../Writing/components/CustomInput"; + +const COVER_STYLE_PRESETS = [ + "Néo-Néon / Cyberpunk", + "Photographie Minimaliste & Éditoriale", + "Illustration & Collage Surréaliste", + "Anti-Design / Maximalisme (Tendance Actuelle)", +]; const PouchReady = () => { const { selectedProjectId, selectedProject, updateProjectData } = useUser(); const { setIsLoading } = useMinuit(); const isGenerating = selectedProject?.coverStatus === "GENERATING"; - const coverOptions = Array.isArray(selectedProject?.cover?.options) - ? selectedProject.cover.options - : []; + const coverOptions = useMemo(() => { + if (!Array.isArray(selectedProject?.cover?.options)) { + return []; + } + return selectedProject.cover.options.filter(Boolean); + }, [selectedProject?.cover?.options]); const hasGeneratedOptions = coverOptions.length > 0; + const selectedOptionId = selectedProject?.cover?.selectedOptionId || null; + const selectedOption = useMemo(() => { + if (!coverOptions.length) { + return null; + } + const found = coverOptions.find((option) => option?.id === selectedOptionId); + return found || coverOptions[0] || null; + }, [coverOptions, selectedOptionId]); const coverBackgroundMessage = isWeb ? loaderMessages.pouchReadyGenerationWeb : ""; - const [coverStyle, setCoverStyle] = useState( - selectedProject?.coverStyle || "" + const [styleMode, setStyleMode] = useState("preset"); + const [selectedPresetStyle, setSelectedPresetStyle] = useState( + COVER_STYLE_PRESETS[0] ); + const [customStyle, setCustomStyle] = useState(""); + const [isPresetDropdownOpen, setIsPresetDropdownOpen] = useState(false); + const [isSelecting, setIsSelecting] = useState(false); useEffect(() => { - setCoverStyle(selectedProject?.coverStyle || ""); + const projectStyle = (selectedProject?.coverStyle || "").trim(); + const isPresetStyle = COVER_STYLE_PRESETS.includes(projectStyle); + + if (!projectStyle) { + setStyleMode("preset"); + setSelectedPresetStyle(COVER_STYLE_PRESETS[0]); + setCustomStyle(""); + setIsPresetDropdownOpen(false); + return; + } + + if (isPresetStyle) { + setStyleMode("preset"); + setSelectedPresetStyle(projectStyle); + setCustomStyle(""); + } else { + setStyleMode("custom"); + setCustomStyle(projectStyle); + } + setIsPresetDropdownOpen(false); }, [selectedProject?.coverStyle]); const generateCover = useCallback( @@ -75,7 +127,9 @@ const PouchReady = () => { ); const requestCoverGeneration = useCallback(() => { - const trimmedStyle = (coverStyle || "").trim(); + const selectedStyle = + styleMode === "preset" ? selectedPresetStyle : customStyle; + const trimmedStyle = (selectedStyle || "").trim(); if (!trimmedStyle) { alert("Attention", "Merci de renseigner un style pour la pochette.", [ { text: "OK" }, @@ -92,17 +146,16 @@ const PouchReady = () => { onPress: () => generateCover(trimmedStyle), }, ]); - }, [coverStyle, generateCover]); - - const validateCover = () => { - navigate(Routes.ValidateCover); - }; + }, [customStyle, generateCover, selectedPresetStyle, styleMode]); const coverPreviewUrl = selectedProject?.cover?.result || selectedProject?.cover?.generatedBackground || null; + const shouldShowStylePanel = + !isGenerating && !hasGeneratedOptions && !coverPreviewUrl; + const displayOptions = hasGeneratedOptions ? coverOptions : coverPreviewUrl @@ -115,10 +168,120 @@ const PouchReady = () => { ] : []; + const handleSelectOption = useCallback( + async (option) => { + if ( + !option || + option?.id === selectedOptionId || + isSelecting || + !option?.id + ) { + return; + } + const existingCover = selectedProject?.cover || {}; + const finalUrl = option.finalUrl || option.generatedUrl || null; + setIsSelecting(true); + try { + await updateProjectData({ + cover: { + ...existingCover, + options: coverOptions, + selectedOptionId: option.id, + result: finalUrl, + generatedBackground: option.generatedUrl || option.finalUrl || null, + }, + }); + } catch (e) { + console.log("PouchReady: unable to select cover", e?.message); + } finally { + setIsSelecting(false); + } + }, + [ + coverOptions, + isSelecting, + selectedOptionId, + selectedProject?.cover, + updateProjectData, + ] + ); + + const onValidatePicture = useCallback(async () => { + if (!selectedOption) { + return; + } + try { + await setIsLoading(true); + const existingCover = selectedProject?.cover || {}; + const finalUrl = + selectedOption.finalUrl || selectedOption.generatedUrl || null; + const nextCoverData = { + ...existingCover, + options: coverOptions, + selectedOptionId: selectedOption.id, + result: finalUrl, + generatedBackground: + selectedOption.generatedUrl || selectedOption.finalUrl || null, + }; + await updateProjectData({ + cover: nextCoverData, + coverUrl: finalUrl, + }); + const projectForStage = { + ...selectedProject, + cover: nextCoverData, + coverUrl: finalUrl, + }; + const playbackStage = getStageAction("director", projectForStage); + await setIsLoading(false); + alert( + "Malik", + "Super ! Ta pochette est validée. Tu peux retourner à l'accueil ou continuer avec John pour produire ton playback.", + [ + { + text: "Retour à l'accueil", + style: "cancel", + onPress: () => navigate(Routes.Home), + }, + { + text: "Continuer avec John", + onPress: () => { + const targetRoute = playbackStage?.route || Routes.Playback; + const params = + playbackStage?.params || { project: projectForStage }; + navigate(targetRoute, params); + }, + }, + ] + ); + } catch (e) { + console.log("PouchReady: unable to validate cover", e?.message); + } finally { + await setIsLoading(false); + } + }, [ + coverOptions, + navigate, + selectedOption, + selectedProject, + setIsLoading, + updateProjectData, + ]); + + const primaryActionTitle = hasGeneratedOptions + ? "Valider la pochette" + : isGenerating + ? "Veuillez patienter..." + : "En attente de la génération"; + + const isPrimaryActionDisabled = + isGenerating || + (hasGeneratedOptions ? !selectedOption || isSelecting : true); + return ( - + { const optionUri = option?.finalUrl || option?.generatedUrl || ""; if (!optionUri) return null; + if (hasGeneratedOptions) { + const isSelected = option?.id === selectedOption?.id; + const cardWidthStyle = isWeb + ? styles.coverOptionCardWeb + : styles.coverOptionCardMobile; + return ( + handleSelectOption(option)} + disabled={isSelecting} + style={[ + styles.coverOptionCard, + cardWidthStyle, + isSelected ? styles.coverOptionCardSelected : null, + isSelecting ? styles.coverOptionCardDisabled : null, + ]} + > + + + + {`Option ${index + 1}`} + + + {isSelected && ( + + + Sélectionnée + + + )} + + ); + } return ( { priority="high" contentFit="cover" transition={120} - style={{ - width: "100%", - height: 260, - borderRadius: 20, - }} + style={styles.coverPreviewImage} /> - {hasGeneratedOptions && ( - - Pochette {index + 1} - - )} ); }) ) : ( - + // + <> {isGenerating && ( - + {isWeb ? ( coverBackgroundMessage ? ( @@ -218,18 +420,138 @@ const PouchReady = () => { )} )} - + + // )} - + {shouldShowStylePanel && ( + + + Style de la pochette + + Choisis une ambiance ou écris ton propre brief créatif. + + + + + { + setStyleMode("preset"); + setIsPresetDropdownOpen(false); + if (!selectedPresetStyle) { + setSelectedPresetStyle(COVER_STYLE_PRESETS[0]); + } + }} + /> + + {styleMode === "preset" && ( + + setIsPresetDropdownOpen((prev) => !prev)} + > + + {selectedPresetStyle} + + + + {isPresetDropdownOpen && ( + + {COVER_STYLE_PRESETS.map((styleOption, index) => { + const isActive = selectedPresetStyle === styleOption; + const isLast = + index === COVER_STYLE_PRESETS.length - 1; + return ( + { + setSelectedPresetStyle(styleOption); + setIsPresetDropdownOpen(false); + }} + > + + {styleOption} + + {isActive && ( + + )} + + ); + })} + + )} + + )} + + + { + setStyleMode("custom"); + setIsPresetDropdownOpen(false); + }} + /> + + {styleMode === "custom" && ( + + + + )} + + + + )} @@ -252,9 +574,9 @@ const PouchReady = () => { /> )} validateCover()} + title={primaryActionTitle} + disabled={isPrimaryActionDisabled} + onPress={onValidatePicture} /> @@ -262,3 +584,210 @@ const PouchReady = () => { }; export default PouchReady; + +const styles = StyleSheet.create({ + coverOptionCard: { + position: "relative", + borderRadius: 20, + borderWidth: 1, + borderColor: "rgba(255,255,255,0.12)", + overflow: "hidden", + backgroundColor: "rgba(15,12,20,0.4)", + }, + coverOptionCardWeb: { + width: 280, + maxWidth: 320, + }, + coverOptionCardMobile: { + width: "100%", + }, + coverOptionCardSelected: { + borderColor: Palette.primary, + }, + coverOptionCardDisabled: { + opacity: 0.85, + }, + coverOptionImage: { + width: "100%", + aspectRatio: 1, + }, + coverOptionBadge: { + position: "absolute", + top: 12, + right: 12, + backgroundColor: Palette.transparentBlack, + paddingHorizontal: 10, + paddingVertical: 6, + borderRadius: 12, + }, + coverOptionBadgeLabel: { + color: Palette.white, + fontSize: 12, + fontFamily: FONT_FAMILY.InterSemiBold, + }, + coverOptionSelectedBadge: { + position: "absolute", + bottom: 12, + left: 12, + backgroundColor: Palette.primary, + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 12, + }, + coverOptionSelectedLabel: { + color: Palette.white, + fontSize: 12, + fontFamily: FONT_FAMILY.InterSemiBold, + }, + coverPreviewCard: { + alignItems: "center", + }, + coverPreviewCardWeb: { + width: 300, + }, + coverPreviewCardMobile: { + width: "100%", + }, + coverPreviewImage: { + width: "100%", + height: 260, + borderRadius: 20, + }, + stylePanel: { + gap: 20, + paddingHorizontal: 18, + paddingVertical: 20, + borderRadius: 24, + borderWidth: 1, + borderColor: "rgba(255,255,255,0.08)", + backgroundColor: "rgba(15, 12, 20, 0.78)", + overflow: "hidden", + }, + stylePanelWeb: { + paddingHorizontal: 26, + }, + stylePanelHeader: { + gap: 6, + }, + panelTitle: { + fontSize: 18, + color: Palette.white, + fontFamily: FONT_FAMILY.InterSemiBold, + }, + panelSubtitle: { + fontSize: 13, + color: Palette.gray, + fontFamily: FONT_FAMILY.InterRegular, + lineHeight: 18, + }, + modeContainer: { + gap: 18, + }, + modeCard: { + alignSelf: "stretch", + paddingHorizontal: 16, + paddingVertical: 12, + borderRadius: 14, + borderWidth: 1, + borderColor: "rgba(255,255,255,0.1)", + backgroundColor: "rgba(15,12,20,0.6)", + }, + modeCardActive: { + borderColor: Palette.primary, + backgroundColor: "rgba(251,104,168,0.12)", + }, + dropdownArea: { + alignSelf: "stretch", + gap: 12, + }, + dropdownTrigger: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + paddingHorizontal: 16, + paddingVertical: 14, + borderRadius: 16, + borderWidth: 1, + borderColor: "rgba(255,255,255,0.1)", + backgroundColor: "rgba(15,12,20,0.65)", + }, + dropdownTriggerLabel: { + flex: 1, + fontSize: 15, + color: Palette.white, + fontFamily: FONT_FAMILY.InterMedium, + }, + dropdownArrow: { + width: 16, + height: 16, + tintColor: Palette.gray, + marginLeft: 10, + }, + dropdownArrowOpen: { + transform: [ + { + rotate: "180deg", + }, + ], + }, + dropdownList: { + borderRadius: 16, + borderWidth: 1, + borderColor: "rgba(255,255,255,0.08)", + backgroundColor: "rgba(12,10,18,0.95)", + overflow: "hidden", + }, + dropdownOption: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + paddingHorizontal: 16, + paddingVertical: 14, + }, + dropdownOptionDivider: { + borderBottomWidth: 1, + borderBottomColor: "rgba(255,255,255,0.06)", + }, + dropdownOptionActive: { + backgroundColor: "rgba(251,104,168,0.12)", + }, + dropdownOptionLabel: { + flex: 1, + fontSize: 15, + color: Palette.white, + fontFamily: FONT_FAMILY.InterRegular, + }, + dropdownOptionLabelActive: { + color: Palette.primary, + fontFamily: FONT_FAMILY.InterSemiBold, + }, + dropdownOptionIcon: { + width: 18, + height: 18, + tintColor: Palette.primary, + marginLeft: 12, + }, + modeColumn: { + flex: 1, + gap: 12, + minWidth: 240, + }, + customInputWrapper: { + alignSelf: "stretch", + borderRadius: 18, + padding: 1, + borderWidth: 1, + borderColor: "rgba(255,255,255,0.1)", + backgroundColor: "rgba(15,12,20,0.4)", + }, + customInput: { + minHeight: 55, + borderRadius: 16, + paddingHorizontal: 16, + paddingVertical: 12, + fontSize: 15, + color: Palette.white, + backgroundColor: "rgba(15,12,20,0.85)", + fontFamily: FONT_FAMILY.InterRegular, + }, +});