new project modal & seperate playback and music likes
This commit is contained in:
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 (
|
||||
<CardContainer
|
||||
@@ -41,7 +45,7 @@ const BackTracks = () => {
|
||||
liked: false,
|
||||
})
|
||||
}
|
||||
onPressPlus={() => navigate(Routes.WritingLyrics)}
|
||||
onPressPlus={openPlaybackPicker}
|
||||
>
|
||||
<View>
|
||||
{items.length > 0 ? (
|
||||
@@ -85,7 +89,7 @@ const BackTracks = () => {
|
||||
</Text>
|
||||
<GradientButton
|
||||
containerStyle={{ padding: 2 }}
|
||||
onPress={() => navigate(Routes.WritingLyrics)}
|
||||
onPress={openPlaybackPicker}
|
||||
title=" Créer un playback"
|
||||
></GradientButton>
|
||||
</View>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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,
|
||||
}) => (
|
||||
<ScrollView style={{ flex: 1, marginBottom: 70 }}>
|
||||
{Array.isArray(data) && data.length > 0 ? (
|
||||
<FlatList
|
||||
@@ -207,12 +212,21 @@ const Profile = () => {
|
||||
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 = () => {
|
||||
<MusicListSection
|
||||
data={displayedPlaybacks}
|
||||
emptyText="Aucun playback pour le moment"
|
||||
likeTarget={LIKE_TARGET.PLAYBACK}
|
||||
/>
|
||||
)}
|
||||
{selected === "Chansons" && (
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
@@ -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 }
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user