playlists

This commit is contained in:
2025-08-29 11:38:17 +02:00
parent d857be1424
commit e0146321b8
9 changed files with 248 additions and 108 deletions
+48 -27
View File
@@ -1,31 +1,34 @@
import {
View,
Text,
FlatList,
Pressable,
Image,
Dimensions,
TextInput,
} from "react-native";
import React, { useRef, useState } from "react"; import React, { useRef, useState } from "react";
import AppActionSheet from "../AppActionSheet"; import {
import AppCheckbox from "../AppCheckbox"; Dimensions,
FlatList,
Image,
Pressable,
Text,
TextInput,
View,
} from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import SwiperFlatList from "react-native-swiper-flatlist";
import { icons } from "../../assets";
import { useUserData } from "../../providers/UserDataProvider";
import { createPlaylist } from "../../screens/Library/Playlists/playlist";
import { Palette } from "../../styles"; import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts"; import { FONT_FAMILY } from "../../styles/Fonts";
import { icons } from "../../assets";
import Style, { size } from "../../styles/Style"; import Style, { size } from "../../styles/Style";
import SwiperFlatList from "react-native-swiper-flatlist"; import AppActionSheet from "../AppActionSheet";
import AppCheckbox from "../AppCheckbox";
import GradientButton from "../GradientButton"; import GradientButton from "../GradientButton";
import { SheetManager } from "react-native-actions-sheet";
const PLAYLIST = ["Ménage", "Voiture", "Roadtrip"];
const { width } = Dimensions.get("window"); const { width } = Dimensions.get("window");
const PlaylistModal = () => { const PlaylistModal = (props) => {
const scrollRef = useRef(null); const scrollRef = useRef(null);
const [selected, setSelected] = useState(""); const [selectedId, setSelectedId] = useState("");
const [playlist, setPlaylist] = useState(""); const [playlist, setPlaylist] = useState("");
const [isCreating, setIsCreating] = useState(false);
const { currentUID, userPlaylists = [] } = useUserData();
const startAtCreate = props?.payload?.startAtCreate || false;
return ( return (
<AppActionSheet id="Playlist"> <AppActionSheet id="Playlist">
@@ -33,6 +36,7 @@ const PlaylistModal = () => {
style={{ width, left: -14 }} style={{ width, left: -14 }}
ref={scrollRef} ref={scrollRef}
disableGesture disableGesture
index={startAtCreate ? 1 : 0}
> >
<View style={{ width, paddingHorizontal: 14 }}> <View style={{ width, paddingHorizontal: 14 }}>
<View style={{ gap: 20 }}> <View style={{ gap: 20 }}>
@@ -47,7 +51,7 @@ const PlaylistModal = () => {
Ajouter ce titre à une playlist Ajouter ce titre à une playlist
</Text> </Text>
<FlatList <FlatList
data={PLAYLIST} data={Array.isArray(userPlaylists) ? userPlaylists : []}
contentContainerStyle={{ gap: 20 }} contentContainerStyle={{ gap: 20 }}
ListHeaderComponent={ ListHeaderComponent={
<Pressable <Pressable
@@ -76,9 +80,9 @@ const PlaylistModal = () => {
} }
renderItem={({ item }) => ( renderItem={({ item }) => (
<AppCheckbox <AppCheckbox
label={item} label={item?.name || "Sans nom"}
onPress={() => setSelected(item)} onPress={() => setSelectedId(item?.id)}
selected={selected === item} selected={selectedId === item?.id}
/> />
)} )}
/> />
@@ -94,6 +98,7 @@ const PlaylistModal = () => {
</View> </View>
<View style={{ width, paddingHorizontal: 14 }}> <View style={{ width, paddingHorizontal: 14 }}>
<View style={{ gap: 20 }}> <View style={{ gap: 20 }}>
{!startAtCreate && (
<Pressable <Pressable
onPress={() => { onPress={() => {
scrollRef.current.scrollToIndex({ scrollRef.current.scrollToIndex({
@@ -113,6 +118,7 @@ const PlaylistModal = () => {
Annuler Annuler
</Text> </Text>
</Pressable> </Pressable>
)}
<View style={{ ...Style.containerCenter, gap: 14 }}> <View style={{ ...Style.containerCenter, gap: 14 }}>
<Text <Text
style={{ style={{
@@ -140,17 +146,32 @@ const PlaylistModal = () => {
/> />
</View> </View>
<GradientButton <GradientButton
title="Créer la playlist" title={isCreating ? "Création..." : "Créer la playlist"}
containerStyle={{ containerStyle={{
width: "80%", width: "80%",
alignSelf: "center", alignSelf: "center",
opacity: playlist?.trim()?.length ? 1 : 0.5,
}} }}
onPress={() => { disabled={!playlist?.trim()?.length || isCreating}
scrollRef.current.scrollToIndex({ onPress={async () => {
index: 0, if (!playlist?.trim()?.length || !currentUID) return;
animated: true, try {
setIsCreating(true);
await createPlaylist({
createdBy: currentUID,
name: playlist.trim(),
musics: [],
createdAt: new Date(),
updatedAt: new Date(),
}); });
setPlaylist(""); setPlaylist("");
SheetManager.hide("Playlist");
scrollRef.current.scrollToIndex({ index: 0, animated: true });
} catch (e) {
console.log(e);
} finally {
setIsCreating(false);
}
}} }}
/> />
</View> </View>
+8 -7
View File
@@ -1,14 +1,14 @@
import firebase from "firebase/compat/app";
import {
initializeAuth,
getReactNativePersistence,
} from "firebase/auth/react-native";
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import {
getReactNativePersistence,
initializeAuth,
} from "firebase/auth/react-native";
import firebase from "firebase/compat/app";
import "firebase/compat/auth"; import "firebase/compat/auth";
import "firebase/compat/storage";
import "firebase/compat/functions";
import "firebase/compat/firestore"; import "firebase/compat/firestore";
import "firebase/compat/functions";
import "firebase/compat/storage";
export const firebaseConfig = { export const firebaseConfig = {
apiKey: "AIzaSyCuHJHdwVN_F-VmUG4Hd7bGiMRqj6rPlLo", apiKey: "AIzaSyCuHJHdwVN_F-VmUG4Hd7bGiMRqj6rPlLo",
@@ -48,6 +48,7 @@ try {
export const usersRef = firestore.collection("users"); export const usersRef = firestore.collection("users");
export const projectsRef = firestore.collection("projects"); export const projectsRef = firestore.collection("projects");
export const playlistsRef = firestore.collection("playlists");
export const notificationsRef = firestore.collection("notifications"); export const notificationsRef = firestore.collection("notifications");
export const documentsRef = firestore.collection("documents"); export const documentsRef = firestore.collection("documents");
export const chatsRef = firestore.collection("chats"); export const chatsRef = firestore.collection("chats");
+77
View File
@@ -0,0 +1,77 @@
import { useEffect, useState } from "react";
export default function useDataFromArrayDocId({
ref,
format = null,
arrayId = [],
refreshArray = [],
condition = true,
pagination = false,
batchSize = 20,
}) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(0);
async function getAllDocs(arrayToGet) {
try {
setLoading(true);
const promises = arrayToGet.map(async (id) => {
return new Promise(async (resolve) => {
const doc = await ref.doc(id).get();
if (doc.exists) {
resolve({ ...doc.data(), id: doc.id });
} else {
resolve(null);
}
});
});
await Promise.all(promises).then(async (values) => {
const validData = values.filter((value) => value !== null);
const newData = format ? await format(validData) : validData;
if (currentPage > 0) {
setData((prev) => [...prev, ...newData]);
} else {
setData(newData);
}
if (pagination && newData.length > 0) {
setCurrentPage((prev) => prev + 1);
}
});
} catch (e) {
console.log(e);
} finally {
setLoading(false);
}
}
function getNewArrayToGet(currPage = 0) {
const start = currPage * batchSize;
const end = (currPage + 1) * batchSize;
const newArray = arrayId.slice(start, end);
return newArray.filter(Boolean);
}
useEffect(() => {
if (ref && arrayId?.length > 0 && !!condition && !loading) {
if (pagination) {
setCurrentPage(0);
getAllDocs(getNewArrayToGet(0));
} else {
getAllDocs(arrayId);
}
} else {
if (data.length > 0) {
setData([]);
}
}
}, [ref, arrayId?.length, condition, ...refreshArray]);
async function loadMore() {
if (ref && arrayId?.length > 0 && !!condition && !loading) {
getAllDocs(getNewArrayToGet(currentPage));
}
}
return { data, setData, loading, loadMore };
}
+13 -19
View File
@@ -5,7 +5,11 @@ import { createContext, useContext, useGlobal } from "reactn";
import { useState } from "react"; import { useState } from "react";
import { checkIfEmailIsValid } from "../actions/signupActions"; import { checkIfEmailIsValid } from "../actions/signupActions";
import firebase, { usersRef } from "../config/firebase"; import firebase, {
playlistsRef,
projectsRef,
usersRef,
} from "../config/firebase";
export const UserDataContext = createContext(); export const UserDataContext = createContext();
@@ -21,7 +25,8 @@ export default ({ children }) => {
const [currentUserRoles] = useGlobal("currentUserRoles"); const [currentUserRoles] = useGlobal("currentUserRoles");
const [userProjects, setUserProjects] = useState([]); const [userProjects, setUserProjects] = useState([]);
const [userLikedProjects, setUserLikedProjects] = useState([]); const [userLikedProjects, setUserLikedProjects] = useState([]);
const [userLikedMusic, setUserLikedMusic] = useState([]); const [userPlaylists, setUserPlaylists] = useState([]);
useDataFromRef({ useDataFromRef({
ref: currentUID ? usersRef.doc(currentUID) : null, ref: currentUID ? usersRef.doc(currentUID) : null,
simpleRef: true, simpleRef: true,
@@ -40,9 +45,7 @@ export default ({ children }) => {
// Subscribe to user's projects (musics) // Subscribe to user's projects (musics)
useDataFromRef({ useDataFromRef({
ref: currentUID ref: currentUID
? firebase ? projectsRef
.firestore()
.collection("projects")
.where("userId", "==", currentUID) .where("userId", "==", currentUID)
.orderBy("updatedAt", "desc") .orderBy("updatedAt", "desc")
: null, : null,
@@ -56,9 +59,7 @@ export default ({ children }) => {
// Subscribe to user's liked projects // Subscribe to user's liked projects
useDataFromRef({ useDataFromRef({
ref: currentUID ref: currentUID
? firebase ? projectsRef
.firestore()
.collection("projects")
.where("likedBy", "array-contains", currentUID) .where("likedBy", "array-contains", currentUID)
.orderBy("updatedAt", "desc") .orderBy("updatedAt", "desc")
: null, : null,
@@ -69,20 +70,14 @@ export default ({ children }) => {
onUpdate: (list) => setUserLikedProjects(Array.isArray(list) ? list : []), onUpdate: (list) => setUserLikedProjects(Array.isArray(list) ? list : []),
}); });
// userLiked music // Subscribe to user's playlists
useDataFromRef({ useDataFromRef({
ref: currentUID ref: currentUID ? playlistsRef.where("createdBy", "==", currentUID) : null,
? firebase
.firestore()
.collection("projects")
.where("likedBy", "array-contains", currentUID)
.orderBy("updatedAt", "desc")
: null,
simpleRef: false, simpleRef: false,
listener: true, listener: true,
condition: !!currentUID, condition: !!currentUID,
refreshArray: [currentUID], refreshArray: [currentUID],
onUpdate: (list) => setUserLikedMusic(Array.isArray(list) ? list : []), onUpdate: (list) => setUserPlaylists(Array.isArray(list) ? list : []),
}); });
const updatePendingUserData = (newData) => { const updatePendingUserData = (newData) => {
@@ -149,8 +144,6 @@ export default ({ children }) => {
cleanData.creationTimestamp = new Date(); cleanData.creationTimestamp = new Date();
} }
console.log("cleanData", cleanData);
await usersRef.doc(dynamicUID).set(cleanData, { merge: true }); await usersRef.doc(dynamicUID).set(cleanData, { merge: true });
if (shouldSetTooltip) { if (shouldSetTooltip) {
@@ -197,6 +190,7 @@ export default ({ children }) => {
currentUserData, currentUserData,
userProjects, userProjects,
userLikedProjects, userLikedProjects,
userPlaylists,
setCurrentUserData, setCurrentUserData,
+27 -14
View File
@@ -1,31 +1,44 @@
import { View, Text, Image, Pressable, Platform } from "react-native";
import React from "react";
import Page from "../../layouts/Page";
import { background, icons } from "../../assets";
import { gutters, Palette, Style } from "../../styles";
import { BlurView } from "expo-blur"; import { BlurView } from "expo-blur";
import React from "react";
import { Image, Platform, Pressable, Text, View } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import { background, icons } from "../../assets";
import Page from "../../layouts/Page";
import { gutters, Palette, Style } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts"; import { FONT_FAMILY } from "../../styles/Fonts";
import { size } from "../../styles/Style"; import { size } from "../../styles/Style";
import { useUser } from "../../providers/UserDataProvider";
const AllMyPlaylist = () => { const AllMyPlaylist = () => {
const { userPlaylists = [] } = useUser();
return ( return (
<Page <Page
headerType="NAVIGATION" headerType="NAVIGATION"
backgroundImg={background.libraryBG} backgroundImg={background.libraryBG}
title="Mes Playlists" title="Mes Playlists"
rightComponent={() => (
<Pressable
onPress={() =>
SheetManager.show("Playlist", { payload: { startAtCreate: true } })
}
>
<Image
source={icons.add}
style={size({ size: 18 })}
resizeMode="contain"
/>
</Pressable>
)}
contentContainerStyle={{ contentContainerStyle={{
paddingBottom: gutters * 2, paddingBottom: gutters * 2,
}} }}
> >
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
<View style={{ gap: 8 }}> <View style={{ gap: 8 }}>
{[ {(Array.isArray(userPlaylists) ? userPlaylists : []).map((item) => (
"Musiques de lover", <View
"Musiques triste", style={{ borderRadius: 12, overflow: "hidden" }}
"Happy new year", key={item?.id}
"Happy songs", >
].map((item, index) => (
<View style={{ borderRadius: 12, overflow: "hidden" }} key={index}>
<BlurView <BlurView
intensity={20} intensity={20}
style={{ style={{
@@ -44,7 +57,7 @@ const AllMyPlaylist = () => {
fontFamily: FONT_FAMILY.InterRegular, fontFamily: FONT_FAMILY.InterRegular,
}} }}
> >
{item} {item?.name || "Sans nom"}
</Text> </Text>
<Pressable> <Pressable>
<Image <Image
+23
View File
@@ -0,0 +1,23 @@
import { playlistsRef } from "../../../config/firebase";
export const createPlaylist = async (payload = {}) => {
try {
const createdBy = payload?.createdBy;
const name = payload?.name?.trim?.();
const musics = Array.isArray(payload?.musics) ? payload.musics : [];
const doc = {
createdBy,
name,
musics,
createdAt: new Date(),
updatedAt: new Date(),
};
const docRef = await playlistsRef.add(doc);
return docRef.id;
} catch (error) {
console.error("Error creating playlist:", error);
throw error;
}
};
+19 -11
View File
@@ -1,21 +1,29 @@
import { BlurView } from "expo-blur";
import React, { useEffect, useState } from "react";
import { import {
View,
Text,
Image, Image,
Platform,
Pressable, Pressable,
StyleSheet, StyleSheet,
Platform, Text,
View,
} from "react-native"; } from "react-native";
import React, { useEffect, useState } from "react";
import { Palette, Style } from "../../../styles";
import { icons, img } from "../../../assets";
import { size } from "../../../styles/Style";
import { BlurView } from "expo-blur";
import { FONT_FAMILY } from "../../../styles/Fonts";
import { useGlobal } from "reactn"; import { useGlobal } from "reactn";
import { projectsRef, arrayUnion, arrayRemove } from "../../../config/firebase"; import { icons, img } from "../../../assets";
import { arrayRemove, arrayUnion, projectsRef } from "../../../config/firebase";
import { Palette, Style } from "../../../styles";
import { FONT_FAMILY } from "../../../styles/Fonts";
import { size } from "../../../styles/Style";
const MusicCard = ({ onPress, onPressMore, title = "Sans titre", subtitle = "MusicLand", imageUri = null, projectId = null, likedBy = [] }) => { const MusicCard = ({
onPress,
onPressMore,
title = "Sans titre",
subtitle = "MusicLand",
imageUri = null,
projectId = null,
likedBy = [],
}) => {
const [currentUID] = useGlobal("currentUID"); const [currentUID] = useGlobal("currentUID");
const [selected, setSelected] = useState(false); const [selected, setSelected] = useState(false);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
+6 -8
View File
@@ -1,17 +1,14 @@
import { View } from "react-native";
import React from "react"; import React from "react";
import { View } from "react-native";
import { Routes } from "../../../navigation";
import { navigate } from "../../../navigation/NavigationService";
import { useUser } from "../../../providers/UserDataProvider";
import CardContainer from "./CardContainer"; import CardContainer from "./CardContainer";
import MusicCard from "./MusicCard"; import MusicCard from "./MusicCard";
import { navigate } from "../../../navigation/NavigationService";
import { Routes } from "../../../navigation";
import { useUser } from "../../../providers/UserDataProvider";
const MyMusic = () => { const MyMusic = () => {
const { userProjects = [] } = useUser(); const { userProjects = [] } = useUser();
const projects = Array.isArray(userProjects) const projects = Array.isArray(userProjects) ? userProjects.slice(0, 3) : [];
? userProjects.slice(0, 3)
: [];
return ( return (
<CardContainer <CardContainer
label="Mes musiques" label="Mes musiques"
@@ -28,6 +25,7 @@ const MyMusic = () => {
projectId={p?.id} projectId={p?.id}
likedBy={p?.likedBy || []} likedBy={p?.likedBy || []}
onPress={() => navigate(Routes.MusicDetails, { projectId: p.id })} onPress={() => navigate(Routes.MusicDetails, { projectId: p.id })}
onPressMore={() => console.log("More options for", p.id)}
/> />
))} ))}
</View> </View>
+8 -3
View File
@@ -8,16 +8,21 @@ import { Palette } from "../../../styles";
import { FONT_FAMILY } from "../../../styles/Fonts"; import { FONT_FAMILY } from "../../../styles/Fonts";
import { navigate } from "../../../navigation/NavigationService"; import { navigate } from "../../../navigation/NavigationService";
import { Routes } from "../../../navigation"; import { Routes } from "../../../navigation";
import { useUser } from "../../../providers/UserDataProvider";
const MyPlaylist = () => { const MyPlaylist = () => {
const { userPlaylists = [] } = useUser();
const playlists = Array.isArray(userPlaylists)
? userPlaylists.slice(0, 2)
: [];
return ( return (
<CardContainer <CardContainer
label="Mes Playlists" label="Mes Playlists"
onPress={() => navigate(Routes.AllMyPlaylist)} onPress={() => navigate(Routes.AllMyPlaylist)}
> >
<View style={{ gap: 8 }}> <View style={{ gap: 8 }}>
{["Musiques de lover", "Musiques triste"].map((item, index) => ( {playlists.map((item) => (
<View style={{ borderRadius: 12, overflow: "hidden" }} key={index}> <View style={{ borderRadius: 12, overflow: "hidden" }} key={item.id}>
<BlurView <BlurView
intensity={20} intensity={20}
style={{ style={{
@@ -36,7 +41,7 @@ const MyPlaylist = () => {
fontFamily: FONT_FAMILY.InterRegular, fontFamily: FONT_FAMILY.InterRegular,
}} }}
> >
{item} {item?.name || "Sans nom"}
</Text> </Text>
<Pressable> <Pressable>
<Image <Image