new project modal & seperate playback and music likes

This commit is contained in:
2025-10-24 16:12:07 +02:00
parent 47d5b92a57
commit 2599a25d77
12 changed files with 415 additions and 63 deletions
+34 -9
View File
@@ -339,15 +339,37 @@ exports.createProjectLikeNotification = onDocumentWritten(
return null; return null;
} }
const beforeLikes = Array.isArray(before?.likedBy) ? before.likedBy : []; const beforeSongLikes = Array.isArray(before?.likes?.song)
const afterLikes = Array.isArray(after?.likedBy) ? after.likedBy : []; ? before.likes.song
: [];
const afterSongLikes = Array.isArray(after?.likes?.song)
? after.likes.song
: [];
const beforePlaybackLikes = Array.isArray(before?.likes?.playback)
? before.likes.playback
: [];
const afterPlaybackLikes = Array.isArray(after?.likes?.playback)
? after.likes.playback
: [];
if (afterLikes.length <= beforeLikes.length) { const beforeSongSet = new Set(beforeSongLikes);
return null; const beforePlaybackSet = new Set(beforePlaybackLikes);
}
const beforeSet = new Set(beforeLikes); const newSongLikers = afterSongLikes.filter(
const newLikers = afterLikes.filter((uid) => !beforeSet.has(uid)); (uid) => uid && !beforeSongSet.has(uid)
);
const newPlaybackLikers = afterPlaybackLikes.filter(
(uid) => uid && !beforePlaybackSet.has(uid)
);
const newLikers = [];
newSongLikers.forEach((uid) => {
newLikers.push({ likerId: uid, likeType: "song" });
});
newPlaybackLikers.forEach((uid) => {
newLikers.push({ likerId: uid, likeType: "playback" });
});
if (!newLikers.length) { if (!newLikers.length) {
return null; return null;
@@ -359,7 +381,7 @@ exports.createProjectLikeNotification = onDocumentWritten(
: "ton projet"; : "ton projet";
await Promise.all( await Promise.all(
newLikers.map(async (likerId) => { newLikers.map(async ({ likerId, likeType }) => {
if (!likerId || likerId === ownerId) { if (!likerId || likerId === ownerId) {
return null; return null;
} }
@@ -371,7 +393,9 @@ exports.createProjectLikeNotification = onDocumentWritten(
? liker.userName.trim() ? liker.userName.trim()
: "Un utilisateur"; : "Un utilisateur";
const message = `${likerName} a aimé ton projet "${projectTitle}"`; const isPlaybackLike = likeType === "playback";
const assetLabel = isPlaybackLike ? "ton playback" : "ta musique";
const message = `${likerName} a aimé ${assetLabel} "${projectTitle}"`;
await sendNotification({ await sendNotification({
sender: likerId, sender: likerId,
@@ -384,6 +408,7 @@ exports.createProjectLikeNotification = onDocumentWritten(
projectId, projectId,
likerId, likerId,
likerName, likerName,
likeType,
}, },
}); });
+244
View File
@@ -0,0 +1,244 @@
import React, { useCallback, useMemo, useState } from "react";
import {
Image,
Pressable,
ScrollView,
StyleSheet,
Text,
View,
} from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import AppActionSheet from "../AppActionSheet";
import GradientButton from "../GradientButton";
import { img } from "../../assets";
import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService";
import { useUserData } from "../../providers/UserDataProvider";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import Style, { size } from "../../styles/Style";
import { isWeb } from "../../hooks/useLayoutType";
const SHEET_ID = "PlaybackPicker";
const PlaybackPickerModal = () => {
const { userProjects = [], createNewProject } = useUserData();
const [isCreating, setIsCreating] = useState(false);
const [webVisible, setWebVisible] = useState(true);
const sanitizedProjects = useMemo(() => {
if (!Array.isArray(userProjects)) {
return [];
}
return userProjects.filter((project) => {
if (!project || project.playbackUrl) {
return false;
}
return !!project?.songUrl;
});
}, [userProjects]);
const hasAnyMusic = Array.isArray(userProjects) && userProjects.length > 0;
const hideSheet = useCallback(() => {
if (isWeb) {
setWebVisible(false);
}
try {
const maybePromise = SheetManager.hide(SHEET_ID);
return Promise.resolve(maybePromise);
} catch (error) {
console.error("[PlaybackPicker] hide error", error?.message || error);
return Promise.resolve();
}
}, [setWebVisible]);
const handleSelectProject = useCallback(
async (project) => {
if (!project?.id) return;
const hidePromise = hideSheet();
await Promise.race([
Promise.resolve(hidePromise),
new Promise((resolve) => setTimeout(resolve, 200)),
]).catch(() => {});
navigate(Routes.Playback, { project });
},
[hideSheet]
);
const handleCreateNew = useCallback(async () => {
if (isCreating) {
return;
}
setIsCreating(true);
try {
let projectId = null;
if (typeof createNewProject === "function") {
projectId = await createNewProject({ hasLyrics: false });
}
const hidePromise = hideSheet();
await Promise.race([
Promise.resolve(hidePromise),
new Promise((resolve) => setTimeout(resolve, 200)),
]);
if (projectId) {
navigate(Routes.WritingLyrics, { projectId });
} else {
navigate(Routes.WritingLyrics);
}
} catch (error) {
console.error("[PlaybackPicker] create project failed", error);
} finally {
setIsCreating(false);
}
}, [createNewProject, hideSheet, isCreating]);
const hasProjects = sanitizedProjects.length > 0;
if (isWeb && !webVisible) {
return null;
}
return (
<AppActionSheet id={SHEET_ID} webModal onClose={hideSheet}>
<View style={{ gap: 18 }}>
<Text style={styles.title}>Ajouter un playback</Text>
<Text style={styles.description}>
Choisis une musique déjà créée pour y ajouter un playback ou lance un
nouveau projet.
</Text>
{hasProjects ? (
<ScrollView
style={{ maxHeight: 320 }}
contentContainerStyle={{ gap: 12, paddingVertical: 4 }}
showsVerticalScrollIndicator={false}
>
{sanitizedProjects.map((project) => {
const coverUri = project?.coverUrl || null;
return (
<Pressable
key={project.id}
onPress={() => handleSelectProject(project)}
style={styles.projectCard}
>
<Image
source={coverUri ? { uri: coverUri } : img.placeholder}
style={styles.cover}
resizeMode="cover"
/>
<View style={{ flex: 1, gap: 4 }}>
<Text numberOfLines={1} style={styles.projectTitle}>
{project?.title || "Sans titre"}
</Text>
<Text numberOfLines={1} style={styles.projectSubtitle}>
{project?.userName || "Moi"}
</Text>
<Text style={styles.projectHint}>
Playback à créer
</Text>
</View>
</Pressable>
);
})}
</ScrollView>
) : (
<View style={styles.emptyState}>
<Text style={styles.emptyTitle}>
{hasAnyMusic
? "Aucune musique n'est prête pour l'étape playback"
: "Tu n'as pas encore de musique prête pour le playback"}
</Text>
<Text style={styles.emptyDescription}>
{hasAnyMusic
? "Complète d'abord la création de ta chanson, puis reviens ici pour enregistrer le playback."
: "Crée un nouveau projet pour composer ta musique et lancer ton playback."}
</Text>
</View>
)}
<GradientButton
title="Créer un nouveau projet"
onPress={handleCreateNew}
containerStyle={{ alignSelf: "center", minWidth: 220 }}
disabled={isCreating}
/>
</View>
</AppActionSheet>
);
};
export default PlaybackPickerModal;
const styles = StyleSheet.create({
title: {
fontSize: 22,
color: Palette.white,
fontFamily: FONT_FAMILY.InterSemiBold,
textAlign: "center",
},
description: {
fontSize: 14,
color: Palette.white,
opacity: 0.85,
fontFamily: FONT_FAMILY.InterRegular,
textAlign: "center",
lineHeight: 20,
},
projectCard: {
...Style.containerRow,
gap: 12,
padding: 12,
borderRadius: 16,
backgroundColor: Palette.glass,
},
cover: {
...size({ size: 60 }),
borderRadius: 12,
backgroundColor: Palette.black,
},
projectTitle: {
fontSize: 16,
color: Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
},
projectSubtitle: {
fontSize: 12,
color: Palette.gray,
fontFamily: FONT_FAMILY.InterRegular,
},
projectHint: {
fontSize: 12,
color: Palette.white,
opacity: 0.7,
fontFamily: FONT_FAMILY.InterRegular,
},
emptyState: {
alignItems: "center",
gap: 8,
paddingVertical: 40,
paddingHorizontal: 20,
borderRadius: 20,
backgroundColor: Palette.glass,
},
emptyTitle: {
fontSize: 18,
color: Palette.white,
fontFamily: FONT_FAMILY.InterSemiBold,
textAlign: "center",
},
emptyDescription: {
fontSize: 14,
color: Palette.white,
opacity: 0.8,
fontFamily: FONT_FAMILY.InterRegular,
textAlign: "center",
lineHeight: 20,
},
});
+6 -2
View File
@@ -1,5 +1,6 @@
import { useGlobal } from "reactn"; import { useGlobal } from "reactn";
import firebase from "../config/firebase"; import firebase from "../config/firebase";
import { getLikeFieldPath, LIKE_TARGET } from "../utils/likes";
import useDataFromRef from "./useDataFromRef"; import useDataFromRef from "./useDataFromRef";
export default function useUserLikedProjects() { export default function useUserLikedProjects() {
@@ -11,7 +12,11 @@ export default function useUserLikedProjects() {
? firebase ? firebase
.firestore() .firestore()
.collection("projects") .collection("projects")
.where("likedBy", "array-contains", currentUID) .where(
getLikeFieldPath(LIKE_TARGET.SONG),
"array-contains",
currentUID
)
: null, : null,
simpleRef: false, simpleRef: false,
listener: true, listener: true,
@@ -21,4 +26,3 @@ export default function useUserLikedProjects() {
return { projects: Array.isArray(data) ? data : [], loading }; return { projects: Array.isArray(data) ? data : [], loading };
} }
+20 -5
View File
@@ -14,6 +14,10 @@ import firebase, {
usersRef, usersRef,
} from "../config/firebase"; } from "../config/firebase";
import { getUserPreferredArtistName } from "../utils/artistName"; import { getUserPreferredArtistName } from "../utils/artistName";
import { getLikeFieldPath, LIKE_TARGET } from "../utils/likes";
const SONG_LIKES_FIELD = getLikeFieldPath(LIKE_TARGET.SONG);
const PLAYBACK_LIKES_FIELD = getLikeFieldPath(LIKE_TARGET.PLAYBACK);
export const UserDataContext = createContext(); export const UserDataContext = createContext();
@@ -63,7 +67,21 @@ export default ({ children }) => {
} = useDataFromRef({ } = useDataFromRef({
ref: currentUID ref: currentUID
? projectsRef ? projectsRef
.where("likedBy", "array-contains", currentUID) .where(SONG_LIKES_FIELD, "array-contains", currentUID)
.orderBy("updatedAt", "desc")
: null,
simpleRef: false,
listener: true,
condition: !!currentUID,
refreshArray: [currentUID],
});
const {
data: userLikedPlaybacks = [],
loading: userLikedPlaybacksLoading = true,
} = useDataFromRef({
ref: currentUID
? projectsRef
.where(PLAYBACK_LIKES_FIELD, "array-contains", currentUID)
.orderBy("updatedAt", "desc") .orderBy("updatedAt", "desc")
: null, : null,
simpleRef: false, simpleRef: false,
@@ -71,9 +89,6 @@ export default ({ children }) => {
condition: !!currentUID, condition: !!currentUID,
refreshArray: [currentUID], refreshArray: [currentUID],
}); });
const userLikedPlaybacks = Array.isArray(userLikedProjects)
? userLikedProjects.filter((project) => project.playbackUrl)
: [];
// Subscribe to user's playlists // Subscribe to user's playlists
const { data: userPlaylists = [] } = useDataFromRef({ const { data: userPlaylists = [] } = useDataFromRef({
ref: currentUID ? playlistsRef.where("createdBy", "==", currentUID) : null, ref: currentUID ? playlistsRef.where("createdBy", "==", currentUID) : null,
@@ -406,7 +421,7 @@ export default ({ children }) => {
userLikedProjects, userLikedProjects,
userLikedProjectsLoading, userLikedProjectsLoading,
userLikedPlaybacks, userLikedPlaybacks,
userLikedPlaybacksLoading: userLikedProjectsLoading, userLikedPlaybacksLoading,
userPlaylists, userPlaylists,
selectedProjectId, selectedProjectId,
selectedProject, selectedProject,
+2 -1
View File
@@ -13,6 +13,7 @@ import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService"; import { navigate } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider"; import { useUser } from "../../providers/UserDataProvider";
import { getArtistDisplayName } from "../../utils/artistName"; import { getArtistDisplayName } from "../../utils/artistName";
import { getProjectLikes, LIKE_TARGET } from "../../utils/likes";
import { Palette } from "../../styles"; import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts"; import { FONT_FAMILY } from "../../styles/Fonts";
import MusicCard from "../Library/components/MusicCard"; import MusicCard from "../Library/components/MusicCard";
@@ -86,7 +87,7 @@ const HomeSave = () => {
subtitle={item?.userName || ownerDisplayName} subtitle={item?.userName || ownerDisplayName}
imageUri={item?.coverUrl || null} imageUri={item?.coverUrl || null}
projectId={item?.id} projectId={item?.id}
likedBy={item?.likedBy || []} likedBy={getProjectLikes(item, LIKE_TARGET.SONG)}
onPress={() => { onPress={() => {
selectProject(item.id); selectProject(item.id);
navigate(Routes.Home); navigate(Routes.Home);
+8 -4
View File
@@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useCallback, useState } from "react";
import { import {
FlatList, FlatList,
Image, Image,
@@ -7,6 +7,7 @@ import {
View, View,
useWindowDimensions, useWindowDimensions,
} from "react-native"; } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import GradientButton from "../../../components/GradientButton"; import GradientButton from "../../../components/GradientButton";
import useLayoutType from "../../../hooks/useLayoutType"; import useLayoutType from "../../../hooks/useLayoutType";
import { Routes } from "../../../navigation"; import { Routes } from "../../../navigation";
@@ -29,7 +30,10 @@ const BackTracks = () => {
0, 0,
Math.floor((containerWidth - gap * (numColumns - 1)) / numColumns) Math.floor((containerWidth - gap * (numColumns - 1)) / numColumns)
); );
console.log("BackTracks items:", items);
const openPlaybackPicker = useCallback(() => {
SheetManager.show("PlaybackPicker");
}, []);
return ( return (
<CardContainer <CardContainer
@@ -41,7 +45,7 @@ const BackTracks = () => {
liked: false, liked: false,
}) })
} }
onPressPlus={() => navigate(Routes.WritingLyrics)} onPressPlus={openPlaybackPicker}
> >
<View> <View>
{items.length > 0 ? ( {items.length > 0 ? (
@@ -85,7 +89,7 @@ const BackTracks = () => {
</Text> </Text>
<GradientButton <GradientButton
containerStyle={{ padding: 2 }} containerStyle={{ padding: 2 }}
onPress={() => navigate(Routes.WritingLyrics)} onPress={openPlaybackPicker}
title=" Créer un playback" title=" Créer un playback"
></GradientButton> ></GradientButton>
</View> </View>
+7 -3
View File
@@ -13,7 +13,7 @@ import { responsiveWidth } from "react-native-responsive-dimensions";
import { useGlobal } from "reactn"; import { useGlobal } from "reactn";
import { icons, img } from "../../../assets"; import { icons, img } from "../../../assets";
import PressableScale from "../../../components/PressableScale"; import PressableScale from "../../../components/PressableScale";
import { arrayRemove, arrayUnion, projectsRef } from "../../../config/firebase"; import { LIKE_TARGET, toggleProjectLike } from "../../../utils/likes";
import { Palette, Style } from "../../../styles"; import { 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";
@@ -26,6 +26,7 @@ const MusicCard = ({
imageUri = null, imageUri = null,
projectId = null, projectId = null,
likedBy = [], likedBy = [],
likeTarget = LIKE_TARGET.SONG,
}) => { }) => {
const [currentUID] = useGlobal("currentUID"); const [currentUID] = useGlobal("currentUID");
const [selected, setSelected] = useState(false); const [selected, setSelected] = useState(false);
@@ -58,8 +59,11 @@ const MusicCard = ({
const next = !selected; const next = !selected;
setSelected(next); setSelected(next);
try { try {
await projectsRef.doc(projectId).update({ await toggleProjectLike({
likedBy: next ? arrayUnion(currentUID) : arrayRemove(currentUID), projectId,
target: likeTarget,
currentUID,
next,
}); });
} catch (e) { } catch (e) {
// rollback on failure // rollback on failure
+15 -19
View File
@@ -15,12 +15,7 @@ import { icons, img } from "../../assets";
import { openComments } from "../../components/bottomsheets/CommentsBottomSheet"; import { openComments } from "../../components/bottomsheets/CommentsBottomSheet";
import KaraokeLyrics from "../../components/KaraokeLyrics"; import KaraokeLyrics from "../../components/KaraokeLyrics";
import ProfilePicture from "../../components/ProfilePicture"; import ProfilePicture from "../../components/ProfilePicture";
import { import { projectsRef, usersRef } from "../../config/firebase";
arrayRemove,
arrayUnion,
projectsRef,
usersRef,
} from "../../config/firebase";
import useDataFromRef from "../../hooks/useDataFromRef"; import useDataFromRef from "../../hooks/useDataFromRef";
import { Routes } from "../../navigation"; import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService"; import { navigate } from "../../navigation/NavigationService";
@@ -28,11 +23,16 @@ import { useUser } from "../../providers/UserDataProvider";
import { Palette } from "../../styles"; import { Palette } 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 {
getProjectLikes,
LIKE_TARGET,
toggleProjectLike,
} from "../../utils/likes";
const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => { const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
const { currentUID, followUser, unfollowUser } = useUser() || {}; const { currentUID, followUser, unfollowUser } = useUser() || {};
const videoUrl = item?.playbackUrl || null; const videoUrl = item?.playbackUrl || null;
const initialLikedBy = Array.isArray(item?.likedBy) ? item.likedBy : []; const initialLikedBy = getProjectLikes(item, LIKE_TARGET.PLAYBACK);
const [isLiked, setIsLiked] = useState( const [isLiked, setIsLiked] = useState(
currentUID ? initialLikedBy.includes(currentUID) : false currentUID ? initialLikedBy.includes(currentUID) : false
); );
@@ -100,10 +100,10 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
}, [owner?.followedBy, currentUID]); }, [owner?.followedBy, currentUID]);
useEffect(() => { useEffect(() => {
const lb = Array.isArray(item?.likedBy) ? item.likedBy : []; const lb = getProjectLikes(item, LIKE_TARGET.PLAYBACK);
setLikesCount(lb.length); setLikesCount(lb.length);
setIsLiked(currentUID ? lb.includes(currentUID) : false); setIsLiked(currentUID ? lb.includes(currentUID) : false);
}, [item?.likedBy, currentUID]); }, [item?.likes?.playback, currentUID]);
const projectTitle = typeof item?.title === "string" ? item.title.trim() : ""; const projectTitle = typeof item?.title === "string" ? item.title.trim() : "";
@@ -292,16 +292,12 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
setIsLiked(nextLiked); setIsLiked(nextLiked);
setLikesCount((c) => Math.max(0, c + (nextLiked ? 1 : -1))); setLikesCount((c) => Math.max(0, c + (nextLiked ? 1 : -1)));
const ref = projectsRef.doc(item.id); await toggleProjectLike({
await ref.set( projectId: item.id,
{ currentUID,
likedBy: nextLiked target: LIKE_TARGET.PLAYBACK,
? arrayUnion(currentUID) next: nextLiked,
: arrayRemove(currentUID), });
// Optionally: updatedAt could be set if needed
},
{ merge: true }
);
} catch (e) { } catch (e) {
// rollback on failure // rollback on failure
setIsLiked((v) => !v); setIsLiked((v) => !v);
@@ -17,12 +17,7 @@ import {
} from "react-native"; } from "react-native";
import { icons, img } from "../../../assets"; import { icons, img } from "../../../assets";
import KaraokeLyrics from "../../../components/KaraokeLyrics"; import KaraokeLyrics from "../../../components/KaraokeLyrics";
import { import { usersRef } from "../../../config/firebase";
arrayRemove,
arrayUnion,
projectsRef,
usersRef,
} from "../../../config/firebase";
import { Routes } from "../../../navigation"; import { Routes } from "../../../navigation";
import { navigate } from "../../../navigation/NavigationService"; import { navigate } from "../../../navigation/NavigationService";
import { useUser } from "../../../providers/UserDataProvider"; import { useUser } from "../../../providers/UserDataProvider";
@@ -30,6 +25,11 @@ import { Palette } 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 CommentsPanel from "./CommentsPanel.web"; import CommentsPanel from "./CommentsPanel.web";
import {
getProjectLikes,
LIKE_TARGET,
toggleProjectLike,
} from "../../../utils/likes";
// Debug logging toggle for web playback // Debug logging toggle for web playback
const DEBUG_PLAYBACK_WEB = true; const DEBUG_PLAYBACK_WEB = true;
@@ -61,7 +61,7 @@ const PlaybackItem = ({
const wasActiveRef = useRef(false); const wasActiveRef = useRef(false);
const startedRef = useRef(false); const startedRef = useRef(false);
const initialLikedBy = Array.isArray(item?.likedBy) ? item.likedBy : []; const initialLikedBy = getProjectLikes(item, LIKE_TARGET.PLAYBACK);
const [isLiked, setIsLiked] = useState( const [isLiked, setIsLiked] = useState(
currentUID ? initialLikedBy.includes(currentUID) : false currentUID ? initialLikedBy.includes(currentUID) : false
); );
@@ -130,10 +130,10 @@ const PlaybackItem = ({
}, [owner?.followedBy, currentUID]); }, [owner?.followedBy, currentUID]);
useEffect(() => { useEffect(() => {
const lb = Array.isArray(item?.likedBy) ? item.likedBy : []; const lb = getProjectLikes(item, LIKE_TARGET.PLAYBACK);
setLikesCount(lb.length); setLikesCount(lb.length);
setIsLiked(currentUID ? lb.includes(currentUID) : false); setIsLiked(currentUID ? lb.includes(currentUID) : false);
}, [item?.likedBy, currentUID]); }, [item?.likes?.playback, currentUID]);
useEffect(() => { useEffect(() => {
startedRef.current = false; startedRef.current = false;
@@ -428,15 +428,12 @@ const PlaybackItem = ({
setIsLiked(nextLiked); setIsLiked(nextLiked);
setLikesCount((c) => Math.max(0, c + (nextLiked ? 1 : -1))); setLikesCount((c) => Math.max(0, c + (nextLiked ? 1 : -1)));
const ref = projectsRef.doc(item.id); await toggleProjectLike({
await ref.set( projectId: item.id,
{ currentUID,
likedBy: nextLiked target: LIKE_TARGET.PLAYBACK,
? arrayUnion(currentUID) next: nextLiked,
: arrayRemove(currentUID), });
},
{ merge: true }
);
} catch (_e) { } catch (_e) {
setIsLiked((v) => !v); setIsLiked((v) => !v);
setLikesCount((c) => setLikesCount((c) =>
+17 -2
View File
@@ -35,6 +35,7 @@ import { Routes } from "../../navigation";
import { push } from "../../navigation/NavigationService"; import { push } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider"; import { useUser } from "../../providers/UserDataProvider";
import { getArtistDisplayName } from "../../utils/artistName"; import { getArtistDisplayName } from "../../utils/artistName";
import { getProjectLikes, LIKE_TARGET } from "../../utils/likes";
import { Palette } from "../../styles"; import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts"; import { FONT_FAMILY } from "../../styles/Fonts";
import Style, { gutters, size } from "../../styles/Style"; import Style, { gutters, size } from "../../styles/Style";
@@ -191,7 +192,11 @@ const Profile = () => {
}; };
// Composant factorisé pour afficher la liste de musiques // Composant factorisé pour afficher la liste de musiques
const MusicListSection = ({ data, emptyText }) => ( const MusicListSection = ({
data,
emptyText,
likeTarget = LIKE_TARGET.SONG,
}) => (
<ScrollView style={{ flex: 1, marginBottom: 70 }}> <ScrollView style={{ flex: 1, marginBottom: 70 }}>
{Array.isArray(data) && data.length > 0 ? ( {Array.isArray(data) && data.length > 0 ? (
<FlatList <FlatList
@@ -207,12 +212,21 @@ const Profile = () => {
subtitle={isSelf ? selfDisplayName : targetDisplayName} subtitle={isSelf ? selfDisplayName : targetDisplayName}
imageUri={item?.coverUrl || null} imageUri={item?.coverUrl || null}
projectId={item?.id} projectId={item?.id}
likedBy={item?.likedBy || []} likedBy={getProjectLikes(item, likeTarget)}
likeTarget={likeTarget}
onPress={() => onPress={() =>
navigateToMusicDetails({ navigateToMusicDetails({
projectId: item.id, projectId: item.id,
songUrl: item?.songUrl, songUrl: item?.songUrl,
project: item, project: item,
queueProjects: data,
queueSource: {
id: isSelf ? "profile-self" : "profile-other",
type: "collection",
name: isSelf
? "Mes musiques"
: targetDisplayName || "Profil",
},
}) })
} }
onPressMore={(posTop) => { onPressMore={(posTop) => {
@@ -528,6 +542,7 @@ const Profile = () => {
<MusicListSection <MusicListSection
data={displayedPlaybacks} data={displayedPlaybacks}
emptyText="Aucun playback pour le moment" emptyText="Aucun playback pour le moment"
likeTarget={LIKE_TARGET.PLAYBACK}
/> />
)} )}
{selected === "Chansons" && ( {selected === "Chansons" && (
+2
View File
@@ -5,6 +5,7 @@ import DeleteAccountModal from "../components/modal/DeleteAccountModal";
import DeletePlaybackModal from "../components/modal/DeletePlaybackModal"; import DeletePlaybackModal from "../components/modal/DeletePlaybackModal";
import DeleteAudioModal from "../components/modal/DeleteAudioModal"; import DeleteAudioModal from "../components/modal/DeleteAudioModal";
import PlaylistModal from "../components/modal/PlaylistModal"; import PlaylistModal from "../components/modal/PlaylistModal";
import PlaybackPickerModal from "../components/modal/PlaybackPickerModal";
import ShareModal from "../components/modal/ShareModal"; import ShareModal from "../components/modal/ShareModal";
registerSheet("Delete", DeleteModal); registerSheet("Delete", DeleteModal);
@@ -13,6 +14,7 @@ registerSheet("DeleteAccount", DeleteAccountModal);
registerSheet("DeletePlayback", DeletePlaybackModal); registerSheet("DeletePlayback", DeletePlaybackModal);
registerSheet("DeleteAudio", DeleteAudioModal); registerSheet("DeleteAudio", DeleteAudioModal);
registerSheet("Playlist", PlaylistModal); registerSheet("Playlist", PlaylistModal);
registerSheet("PlaybackPicker", PlaybackPickerModal);
registerSheet("Share", ShareModal); registerSheet("Share", ShareModal);
export {}; export {};
+45
View File
@@ -0,0 +1,45 @@
import { arrayRemove, arrayUnion, projectsRef } from "../config/firebase";
export const LIKE_TARGET = {
SONG: "song",
PLAYBACK: "playback",
};
export const getLikeFieldPath = (target = LIKE_TARGET.SONG) => {
return target === LIKE_TARGET.PLAYBACK ? "likes.playback" : "likes.song";
};
export const getProjectLikes = (project, target = LIKE_TARGET.SONG) => {
const path = target === LIKE_TARGET.PLAYBACK ? "playback" : "song";
const likes = project?.likes;
const list = likes ? likes[path] : null;
return Array.isArray(list) ? list : [];
};
export const isProjectLikedByUser = (
project,
target,
currentUID
) => {
if (!currentUID) return false;
return getProjectLikes(project, target).includes(currentUID);
};
export const toggleProjectLike = async ({
projectId,
target = LIKE_TARGET.SONG,
currentUID,
next,
}) => {
if (!projectId || !currentUID) return;
const fieldPath = getLikeFieldPath(target);
await projectsRef.doc(projectId).set(
{
[fieldPath]: next
? arrayUnion(currentUID)
: arrayRemove(currentUID),
},
{ merge: true }
);
};