diff --git a/src/components/modal/PlaylistModal.js b/src/components/modal/PlaylistModal.js index 3641b6c..02964ab 100644 --- a/src/components/modal/PlaylistModal.js +++ b/src/components/modal/PlaylistModal.js @@ -9,10 +9,10 @@ import { View, } from "react-native"; import { SheetManager } from "react-native-actions-sheet"; +import useMinuit from "react-native-minuit/src/hooks/useMinuit"; import SwiperFlatList from "react-native-swiper-flatlist"; import { icons } from "../../assets"; import { arrayUnion, playlistsRef } from "../../config/firebase"; -import useMinuit from "react-native-minuit/src/hooks/useMinuit"; import { useUserData } from "../../providers/UserDataProvider"; import { createPlaylist } from "../../screens/Library/Playlists/playlist"; import { Palette } from "../../styles"; @@ -33,12 +33,22 @@ const PlaylistModal = (props) => { const startAtCreate = props?.payload?.startAtCreate || false; const { setTooltip } = useMinuit(); + const sanitizedPlaylists = useMemo( + () => (Array.isArray(userPlaylists) ? userPlaylists : []), + [userPlaylists], + ); + const isDuplicateName = useMemo(() => { const lower = (playlist || "").trim().toLowerCase(); if (!lower) return false; - const list = Array.isArray(userPlaylists) ? userPlaylists : []; - return list.some((p) => (p?.name || "").trim().toLowerCase() === lower); - }, [playlist, JSON.stringify(userPlaylists)]); + return sanitizedPlaylists.some( + (p) => (p?.name || "").trim().toLowerCase() === lower, + ); + }, [playlist, sanitizedPlaylists]); + + const hideSheet = () => { + SheetManager.hide("Playlist"); + }; return ( @@ -60,8 +70,10 @@ const PlaylistModal = (props) => { > Ajouter ce titre à une playlist + item?.id || `playlist-${index}`} contentContainerStyle={{ gap: 20 }} ListHeaderComponent={ { gap: 10, }} onPress={() => - scrollRef.current.scrollToIndex({ + scrollRef.current?.scrollToIndex({ index: 1, animated: true, }) @@ -96,6 +108,7 @@ const PlaylistModal = (props) => { /> )} /> + { musics: arrayUnion(projectId), updatedAt: new Date(), }); - const addedPlaylist = ( - Array.isArray(userPlaylists) ? userPlaylists : [] - ).find((p) => p?.id === selectedId); + const addedPlaylist = sanitizedPlaylists.find( + (p) => p?.id === selectedId, + ); const name = addedPlaylist?.name || "la playlist"; setTooltip({ type: "success", @@ -129,18 +142,19 @@ const PlaylistModal = (props) => { text: e?.message || "Ajout impossible", }); } finally { - SheetManager.hide("Playlist"); + hideSheet(); } }} /> + {!startAtCreate && ( { - scrollRef.current.scrollToIndex({ + scrollRef.current?.scrollToIndex({ index: 0, animated: true, }); @@ -158,12 +172,14 @@ const PlaylistModal = (props) => { )} + Nommer la playlist @@ -197,6 +213,7 @@ const PlaylistModal = (props) => { )} + { setIsCreating(true); const name = playlist.trim(); const lower = name.toLowerCase(); - // Unicité du nom pour l'utilisateur courant (insensible à la casse) - const exists = ( - Array.isArray(userPlaylists) ? userPlaylists : [] - ).some((p) => (p?.name || "").trim().toLowerCase() === lower); + const exists = sanitizedPlaylists.some( + (p) => (p?.name || "").trim().toLowerCase() === lower, + ); if (exists) { setTooltip({ type: "error", @@ -222,6 +238,7 @@ const PlaylistModal = (props) => { }); return; } + await createPlaylist({ createdBy: currentUID, name, @@ -231,15 +248,20 @@ const PlaylistModal = (props) => { createdAt: new Date(), updatedAt: new Date(), }); + setTooltip({ type: "success", text: props?.payload?.projectId ? `Playlist “${name}” créée et musique ajoutée` : `Playlist “${name}” créée`, }); + setPlaylist(""); - SheetManager.hide("Playlist"); - scrollRef.current.scrollToIndex({ index: 0, animated: true }); + scrollRef.current?.scrollToIndex({ + index: 0, + animated: true, + }); + hideSheet(); } catch (e) { console.log(e); setTooltip({ diff --git a/src/components/modal/PlaylistModal.web.js b/src/components/modal/PlaylistModal.web.js new file mode 100644 index 0000000..681f085 --- /dev/null +++ b/src/components/modal/PlaylistModal.web.js @@ -0,0 +1,347 @@ +import { Portal } from "@gorhom/portal"; +import { BlurView } from "expo-blur"; +import React, { useEffect, useMemo, useState } from "react"; +import { + FlatList, + Image, + Pressable, + StyleSheet, + Text, + TextInput, + View, +} from "react-native"; +import { SheetManager, useProviderContext } from "react-native-actions-sheet"; +import { actionSheetEventManager } from "react-native-actions-sheet/dist/src/eventmanager"; +import useMinuit from "react-native-minuit/src/hooks/useMinuit"; +import { icons } from "../../assets"; +import { arrayUnion, playlistsRef } from "../../config/firebase"; +import { useUserData } from "../../providers/UserDataProvider"; +import { createPlaylist } from "../../screens/Library/Playlists/playlist"; +import { Palette } from "../../styles"; +import { FONT_FAMILY } from "../../styles/Fonts"; +import Style, { size } from "../../styles/Style"; +import AppCheckbox from "../AppCheckbox"; +import GradientButton from "../GradientButton"; + +const PlaylistModal = (props) => { + const [selectedId, setSelectedId] = useState(""); + const [playlist, setPlaylist] = useState(""); + const [isCreating, setIsCreating] = useState(false); + const { currentUID, userPlaylists = [] } = useUserData(); + const { setTooltip } = useMinuit(); + const startAtCreate = props?.payload?.startAtCreate || false; + const [mode, setMode] = useState(startAtCreate ? "create" : "select"); + const [visible, setVisible] = useState(true); + const providerContext = useProviderContext(); + + useEffect(() => { + setMode(startAtCreate ? "create" : "select"); + }, [startAtCreate]); + + const sanitizedPlaylists = useMemo( + () => (Array.isArray(userPlaylists) ? userPlaylists : []), + [userPlaylists] + ); + + const isDuplicateName = useMemo(() => { + const lower = (playlist || "").trim().toLowerCase(); + if (!lower) return false; + return sanitizedPlaylists.some( + (p) => (p?.name || "").trim().toLowerCase() === lower + ); + }, [playlist, sanitizedPlaylists]); + + useEffect(() => { + setVisible(true); + }, [props?.sheetId, props?.payload]); + + const hideModal = () => { + setVisible(false); + props?.payload?.onClose?.(); + + const sheetId = props?.sheetId || "Playlist"; + + if (props?.sheetId) { + SheetManager.hide(sheetId, { context: providerContext }); + // Web modal is rendered outside an actual ActionSheet, so we broadcast the + // close event manually to let the SheetProvider unmount it. + actionSheetEventManager.publish(`onclose_${sheetId}`, undefined, providerContext); + } + }; + + const handleValidateSelection = async () => { + if (!selectedId) return; + try { + const projectId = props?.payload?.projectId; + if (projectId) { + await playlistsRef.doc(selectedId).update({ + musics: arrayUnion(projectId), + updatedAt: new Date(), + }); + const addedPlaylist = sanitizedPlaylists.find( + (p) => p?.id === selectedId + ); + const name = addedPlaylist?.name || "la playlist"; + setTooltip({ type: "success", text: `Ajouté à “${name}”` }); + } + } catch (e) { + console.log("Add to playlist error", e?.message); + setTooltip({ + type: "error", + text: e?.message || "Ajout impossible", + }); + } finally { + hideModal(); + } + }; + + const handleCreatePlaylist = async () => { + if (!playlist?.trim()?.length || !currentUID) return; + try { + setIsCreating(true); + const name = playlist.trim(); + const lower = name.toLowerCase(); + const exists = sanitizedPlaylists.some( + (p) => (p?.name || "").trim().toLowerCase() === lower + ); + if (exists) { + setTooltip({ + type: "error", + text: "Ce nom de playlist existe déjà", + }); + return; + } + + await createPlaylist({ + createdBy: currentUID, + name, + musics: props?.payload?.projectId ? [props.payload.projectId] : [], + createdAt: new Date(), + updatedAt: new Date(), + }); + + setTooltip({ + type: "success", + text: props?.payload?.projectId + ? `Playlist “${name}” créée et musique ajoutée` + : `Playlist “${name}” créée`, + }); + + setPlaylist(""); + setSelectedId(""); + setMode("select"); + hideModal(); + } catch (e) { + console.log(e); + setTooltip({ + type: "error", + text: e?.message || "Création impossible", + }); + } finally { + setIsCreating(false); + } + }; + + const renderSelectStep = () => ( + + + Ajouter ce titre à une playlist + + item?.id || `playlist-${index}`} + contentContainerStyle={{ gap: 20 }} + style={{ maxHeight: 320 }} + ListHeaderComponent={ + { + setPlaylist(""); + setMode("create"); + }} + > + + + Créer une nouvelle playlist + + + } + renderItem={({ item }) => ( + setSelectedId(item?.id)} + selected={selectedId === item?.id} + /> + )} + ListEmptyComponent={ + + Aucune playlist pour le moment + + } + /> + + + ); + + const renderCreateStep = () => ( + + {!startAtCreate && ( + { + setMode("select"); + setPlaylist(""); + }} + > + + Retour + + + )} + + + Nommer la playlist + + + {isDuplicateName && ( + + Ce nom de playlist existe déjà + + )} + + + + ); + + if (!visible) return null; + + return ( + + + + + + + {mode === "select" ? renderSelectStep() : renderCreateStep()} + + + + + ); +}; + +const styles = StyleSheet.create({ + overlayContainer: { + ...StyleSheet.absoluteFillObject, + justifyContent: "center", + alignItems: "center", + zIndex: 1000, + }, + blurOverlay: { + ...StyleSheet.absoluteFillObject, + zIndex: 0, + }, + backdrop: { + ...StyleSheet.absoluteFillObject, + backgroundColor: "rgba(10, 5, 20, 0.55)", + zIndex: 1, + cursor: "pointer", + }, + modalWrapper: { + width: "90%", + maxWidth: 520, + borderRadius: 24, + overflow: "hidden", + zIndex: 2, + }, + modalContent: { + paddingVertical: 32, + paddingHorizontal: 28, + backgroundColor: "rgba(30, 23, 38, 0.92)", + gap: 24, + }, +}); + +export default PlaylistModal;