diff --git a/functions/src/notifications.js b/functions/src/notifications.js
index 9e0c2dc..5d43d57 100644
--- a/functions/src/notifications.js
+++ b/functions/src/notifications.js
@@ -339,15 +339,37 @@ exports.createProjectLikeNotification = onDocumentWritten(
return null;
}
- const beforeLikes = Array.isArray(before?.likedBy) ? before.likedBy : [];
- const afterLikes = Array.isArray(after?.likedBy) ? after.likedBy : [];
+ const beforeSongLikes = Array.isArray(before?.likes?.song)
+ ? 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) {
- return null;
- }
+ const beforeSongSet = new Set(beforeSongLikes);
+ const beforePlaybackSet = new Set(beforePlaybackLikes);
- const beforeSet = new Set(beforeLikes);
- const newLikers = afterLikes.filter((uid) => !beforeSet.has(uid));
+ const newSongLikers = afterSongLikes.filter(
+ (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) {
return null;
@@ -359,7 +381,7 @@ exports.createProjectLikeNotification = onDocumentWritten(
: "ton projet";
await Promise.all(
- newLikers.map(async (likerId) => {
+ newLikers.map(async ({ likerId, likeType }) => {
if (!likerId || likerId === ownerId) {
return null;
}
@@ -371,7 +393,9 @@ exports.createProjectLikeNotification = onDocumentWritten(
? liker.userName.trim()
: "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({
sender: likerId,
@@ -384,6 +408,7 @@ exports.createProjectLikeNotification = onDocumentWritten(
projectId,
likerId,
likerName,
+ likeType,
},
});
diff --git a/src/components/modal/PlaybackPickerModal.js b/src/components/modal/PlaybackPickerModal.js
new file mode 100644
index 0000000..8c45bd1
--- /dev/null
+++ b/src/components/modal/PlaybackPickerModal.js
@@ -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 (
+
+
+ Ajouter un playback
+
+ Choisis une musique déjà créée pour y ajouter un playback ou lance un
+ nouveau projet.
+
+
+ {hasProjects ? (
+
+ {sanitizedProjects.map((project) => {
+ const coverUri = project?.coverUrl || null;
+ return (
+ handleSelectProject(project)}
+ style={styles.projectCard}
+ >
+
+
+
+ {project?.title || "Sans titre"}
+
+
+ {project?.userName || "Moi"}
+
+
+ Playback à créer
+
+
+
+ );
+ })}
+
+ ) : (
+
+
+ {hasAnyMusic
+ ? "Aucune musique n'est prête pour l'étape playback"
+ : "Tu n'as pas encore de musique prête pour le playback"}
+
+
+ {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."}
+
+
+ )}
+
+
+
+
+ );
+};
+
+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,
+ },
+});
diff --git a/src/hooks/useUserLikedProjects.js b/src/hooks/useUserLikedProjects.js
index 0aa6814..7fe8c5c 100644
--- a/src/hooks/useUserLikedProjects.js
+++ b/src/hooks/useUserLikedProjects.js
@@ -1,5 +1,6 @@
import { useGlobal } from "reactn";
import firebase from "../config/firebase";
+import { getLikeFieldPath, LIKE_TARGET } from "../utils/likes";
import useDataFromRef from "./useDataFromRef";
export default function useUserLikedProjects() {
@@ -11,7 +12,11 @@ export default function useUserLikedProjects() {
? firebase
.firestore()
.collection("projects")
- .where("likedBy", "array-contains", currentUID)
+ .where(
+ getLikeFieldPath(LIKE_TARGET.SONG),
+ "array-contains",
+ currentUID
+ )
: null,
simpleRef: false,
listener: true,
@@ -21,4 +26,3 @@ export default function useUserLikedProjects() {
return { projects: Array.isArray(data) ? data : [], loading };
}
-
diff --git a/src/providers/UserDataProvider.js b/src/providers/UserDataProvider.js
index fdcced1..be7e7c3 100644
--- a/src/providers/UserDataProvider.js
+++ b/src/providers/UserDataProvider.js
@@ -14,6 +14,10 @@ import firebase, {
usersRef,
} from "../config/firebase";
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();
@@ -63,7 +67,21 @@ export default ({ children }) => {
} = useDataFromRef({
ref: currentUID
? 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")
: null,
simpleRef: false,
@@ -71,9 +89,6 @@ export default ({ children }) => {
condition: !!currentUID,
refreshArray: [currentUID],
});
- const userLikedPlaybacks = Array.isArray(userLikedProjects)
- ? userLikedProjects.filter((project) => project.playbackUrl)
- : [];
// Subscribe to user's playlists
const { data: userPlaylists = [] } = useDataFromRef({
ref: currentUID ? playlistsRef.where("createdBy", "==", currentUID) : null,
@@ -406,7 +421,7 @@ export default ({ children }) => {
userLikedProjects,
userLikedProjectsLoading,
userLikedPlaybacks,
- userLikedPlaybacksLoading: userLikedProjectsLoading,
+ userLikedPlaybacksLoading,
userPlaylists,
selectedProjectId,
selectedProject,
diff --git a/src/screens/Home/HomeSave.js b/src/screens/Home/HomeSave.js
index 86605d8..ed4f78a 100644
--- a/src/screens/Home/HomeSave.js
+++ b/src/screens/Home/HomeSave.js
@@ -13,6 +13,7 @@ import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider";
import { getArtistDisplayName } from "../../utils/artistName";
+import { getProjectLikes, LIKE_TARGET } from "../../utils/likes";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import MusicCard from "../Library/components/MusicCard";
@@ -86,7 +87,7 @@ const HomeSave = () => {
subtitle={item?.userName || ownerDisplayName}
imageUri={item?.coverUrl || null}
projectId={item?.id}
- likedBy={item?.likedBy || []}
+ likedBy={getProjectLikes(item, LIKE_TARGET.SONG)}
onPress={() => {
selectProject(item.id);
navigate(Routes.Home);
diff --git a/src/screens/Library/components/BackTracks.js b/src/screens/Library/components/BackTracks.js
index 56cc52c..58f1221 100644
--- a/src/screens/Library/components/BackTracks.js
+++ b/src/screens/Library/components/BackTracks.js
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useCallback, useState } from "react";
import {
FlatList,
Image,
@@ -7,6 +7,7 @@ import {
View,
useWindowDimensions,
} from "react-native";
+import { SheetManager } from "react-native-actions-sheet";
import GradientButton from "../../../components/GradientButton";
import useLayoutType from "../../../hooks/useLayoutType";
import { Routes } from "../../../navigation";
@@ -29,7 +30,10 @@ const BackTracks = () => {
0,
Math.floor((containerWidth - gap * (numColumns - 1)) / numColumns)
);
- console.log("BackTracks items:", items);
+
+ const openPlaybackPicker = useCallback(() => {
+ SheetManager.show("PlaybackPicker");
+ }, []);
return (
{
liked: false,
})
}
- onPressPlus={() => navigate(Routes.WritingLyrics)}
+ onPressPlus={openPlaybackPicker}
>
{items.length > 0 ? (
@@ -85,7 +89,7 @@ const BackTracks = () => {
navigate(Routes.WritingLyrics)}
+ onPress={openPlaybackPicker}
title=" Créer un playback"
>
diff --git a/src/screens/Library/components/MusicCard.js b/src/screens/Library/components/MusicCard.js
index 974e2a9..b1fe313 100644
--- a/src/screens/Library/components/MusicCard.js
+++ b/src/screens/Library/components/MusicCard.js
@@ -13,7 +13,7 @@ import { responsiveWidth } from "react-native-responsive-dimensions";
import { useGlobal } from "reactn";
import { icons, img } from "../../../assets";
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 { FONT_FAMILY } from "../../../styles/Fonts";
import { size } from "../../../styles/Style";
@@ -26,6 +26,7 @@ const MusicCard = ({
imageUri = null,
projectId = null,
likedBy = [],
+ likeTarget = LIKE_TARGET.SONG,
}) => {
const [currentUID] = useGlobal("currentUID");
const [selected, setSelected] = useState(false);
@@ -58,8 +59,11 @@ const MusicCard = ({
const next = !selected;
setSelected(next);
try {
- await projectsRef.doc(projectId).update({
- likedBy: next ? arrayUnion(currentUID) : arrayRemove(currentUID),
+ await toggleProjectLike({
+ projectId,
+ target: likeTarget,
+ currentUID,
+ next,
});
} catch (e) {
// rollback on failure
diff --git a/src/screens/Playbacks/Playbacks.js b/src/screens/Playbacks/Playbacks.js
index dd6522d..d094f28 100644
--- a/src/screens/Playbacks/Playbacks.js
+++ b/src/screens/Playbacks/Playbacks.js
@@ -15,12 +15,7 @@ import { icons, img } from "../../assets";
import { openComments } from "../../components/bottomsheets/CommentsBottomSheet";
import KaraokeLyrics from "../../components/KaraokeLyrics";
import ProfilePicture from "../../components/ProfilePicture";
-import {
- arrayRemove,
- arrayUnion,
- projectsRef,
- usersRef,
-} from "../../config/firebase";
+import { projectsRef, usersRef } from "../../config/firebase";
import useDataFromRef from "../../hooks/useDataFromRef";
import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService";
@@ -28,11 +23,16 @@ import { useUser } from "../../providers/UserDataProvider";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import { size } from "../../styles/Style";
+import {
+ getProjectLikes,
+ LIKE_TARGET,
+ toggleProjectLike,
+} from "../../utils/likes";
const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
const { currentUID, followUser, unfollowUser } = useUser() || {};
const videoUrl = item?.playbackUrl || null;
- const initialLikedBy = Array.isArray(item?.likedBy) ? item.likedBy : [];
+ const initialLikedBy = getProjectLikes(item, LIKE_TARGET.PLAYBACK);
const [isLiked, setIsLiked] = useState(
currentUID ? initialLikedBy.includes(currentUID) : false
);
@@ -100,10 +100,10 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
}, [owner?.followedBy, currentUID]);
useEffect(() => {
- const lb = Array.isArray(item?.likedBy) ? item.likedBy : [];
+ const lb = getProjectLikes(item, LIKE_TARGET.PLAYBACK);
setLikesCount(lb.length);
setIsLiked(currentUID ? lb.includes(currentUID) : false);
- }, [item?.likedBy, currentUID]);
+ }, [item?.likes?.playback, currentUID]);
const projectTitle = typeof item?.title === "string" ? item.title.trim() : "";
@@ -292,16 +292,12 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
setIsLiked(nextLiked);
setLikesCount((c) => Math.max(0, c + (nextLiked ? 1 : -1)));
- const ref = projectsRef.doc(item.id);
- await ref.set(
- {
- likedBy: nextLiked
- ? arrayUnion(currentUID)
- : arrayRemove(currentUID),
- // Optionally: updatedAt could be set if needed
- },
- { merge: true }
- );
+ await toggleProjectLike({
+ projectId: item.id,
+ currentUID,
+ target: LIKE_TARGET.PLAYBACK,
+ next: nextLiked,
+ });
} catch (e) {
// rollback on failure
setIsLiked((v) => !v);
diff --git a/src/screens/Playbacks/components/PlaybackItem.web.js b/src/screens/Playbacks/components/PlaybackItem.web.js
index 5450798..7e16133 100644
--- a/src/screens/Playbacks/components/PlaybackItem.web.js
+++ b/src/screens/Playbacks/components/PlaybackItem.web.js
@@ -17,12 +17,7 @@ import {
} from "react-native";
import { icons, img } from "../../../assets";
import KaraokeLyrics from "../../../components/KaraokeLyrics";
-import {
- arrayRemove,
- arrayUnion,
- projectsRef,
- usersRef,
-} from "../../../config/firebase";
+import { usersRef } from "../../../config/firebase";
import { Routes } from "../../../navigation";
import { navigate } from "../../../navigation/NavigationService";
import { useUser } from "../../../providers/UserDataProvider";
@@ -30,6 +25,11 @@ import { Palette } from "../../../styles";
import { FONT_FAMILY } from "../../../styles/Fonts";
import { size } from "../../../styles/Style";
import CommentsPanel from "./CommentsPanel.web";
+import {
+ getProjectLikes,
+ LIKE_TARGET,
+ toggleProjectLike,
+} from "../../../utils/likes";
// Debug logging toggle for web playback
const DEBUG_PLAYBACK_WEB = true;
@@ -61,7 +61,7 @@ const PlaybackItem = ({
const wasActiveRef = 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(
currentUID ? initialLikedBy.includes(currentUID) : false
);
@@ -130,10 +130,10 @@ const PlaybackItem = ({
}, [owner?.followedBy, currentUID]);
useEffect(() => {
- const lb = Array.isArray(item?.likedBy) ? item.likedBy : [];
+ const lb = getProjectLikes(item, LIKE_TARGET.PLAYBACK);
setLikesCount(lb.length);
setIsLiked(currentUID ? lb.includes(currentUID) : false);
- }, [item?.likedBy, currentUID]);
+ }, [item?.likes?.playback, currentUID]);
useEffect(() => {
startedRef.current = false;
@@ -428,15 +428,12 @@ const PlaybackItem = ({
setIsLiked(nextLiked);
setLikesCount((c) => Math.max(0, c + (nextLiked ? 1 : -1)));
- const ref = projectsRef.doc(item.id);
- await ref.set(
- {
- likedBy: nextLiked
- ? arrayUnion(currentUID)
- : arrayRemove(currentUID),
- },
- { merge: true }
- );
+ await toggleProjectLike({
+ projectId: item.id,
+ currentUID,
+ target: LIKE_TARGET.PLAYBACK,
+ next: nextLiked,
+ });
} catch (_e) {
setIsLiked((v) => !v);
setLikesCount((c) =>
diff --git a/src/screens/Profile/Profile.js b/src/screens/Profile/Profile.js
index d9ceedb..2d76515 100644
--- a/src/screens/Profile/Profile.js
+++ b/src/screens/Profile/Profile.js
@@ -35,6 +35,7 @@ import { Routes } from "../../navigation";
import { push } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider";
import { getArtistDisplayName } from "../../utils/artistName";
+import { getProjectLikes, LIKE_TARGET } from "../../utils/likes";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import Style, { gutters, size } from "../../styles/Style";
@@ -191,7 +192,11 @@ const Profile = () => {
};
// Composant factorisé pour afficher la liste de musiques
- const MusicListSection = ({ data, emptyText }) => (
+ const MusicListSection = ({
+ data,
+ emptyText,
+ likeTarget = LIKE_TARGET.SONG,
+ }) => (
{Array.isArray(data) && data.length > 0 ? (
{
subtitle={isSelf ? selfDisplayName : targetDisplayName}
imageUri={item?.coverUrl || null}
projectId={item?.id}
- likedBy={item?.likedBy || []}
+ likedBy={getProjectLikes(item, likeTarget)}
+ likeTarget={likeTarget}
onPress={() =>
navigateToMusicDetails({
projectId: item.id,
songUrl: item?.songUrl,
project: item,
+ queueProjects: data,
+ queueSource: {
+ id: isSelf ? "profile-self" : "profile-other",
+ type: "collection",
+ name: isSelf
+ ? "Mes musiques"
+ : targetDisplayName || "Profil",
+ },
})
}
onPressMore={(posTop) => {
@@ -528,6 +542,7 @@ const Profile = () => {
)}
{selected === "Chansons" && (
diff --git a/src/utils/Sheet.js b/src/utils/Sheet.js
index 0782789..4eee952 100644
--- a/src/utils/Sheet.js
+++ b/src/utils/Sheet.js
@@ -5,6 +5,7 @@ import DeleteAccountModal from "../components/modal/DeleteAccountModal";
import DeletePlaybackModal from "../components/modal/DeletePlaybackModal";
import DeleteAudioModal from "../components/modal/DeleteAudioModal";
import PlaylistModal from "../components/modal/PlaylistModal";
+import PlaybackPickerModal from "../components/modal/PlaybackPickerModal";
import ShareModal from "../components/modal/ShareModal";
registerSheet("Delete", DeleteModal);
@@ -13,6 +14,7 @@ registerSheet("DeleteAccount", DeleteAccountModal);
registerSheet("DeletePlayback", DeletePlaybackModal);
registerSheet("DeleteAudio", DeleteAudioModal);
registerSheet("Playlist", PlaylistModal);
+registerSheet("PlaybackPicker", PlaybackPickerModal);
registerSheet("Share", ShareModal);
export {};
diff --git a/src/utils/likes.js b/src/utils/likes.js
new file mode 100644
index 0000000..8990b54
--- /dev/null
+++ b/src/utils/likes.js
@@ -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 }
+ );
+};
+