last tickets

This commit is contained in:
Thomas Demirdjian
2025-11-24 15:35:14 +01:00
parent 471a3177e8
commit 72852ad92b
40 changed files with 1749 additions and 667 deletions
+38 -22
View File
@@ -11,6 +11,7 @@ import { navigate } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider";
import { Palette, Style, gutters } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import { getProjectLikes, LIKE_TARGET } from "../../utils/likes";
import MusicCard from "./components/MusicCard";
export default function AllMyList() {
@@ -28,6 +29,7 @@ export default function AllMyList() {
userLikedProjectsLoading,
userPlaybacks,
userLikedPlaybacks,
userLikedPlaybacksLoading,
} = useUser();
const navigateToMusicDetails = useNavigateToMusicDetails();
@@ -55,9 +57,18 @@ export default function AllMyList() {
]);
const sanitizedItems = Array.isArray(items) ? items : [];
const itemsLoading = liked
? userLikedProjectsLoading
: userProjectsLoading;
const itemsLoading = useMemo(() => {
if (scope === "playback") {
return liked ? userLikedPlaybacksLoading : userProjectsLoading;
}
return liked ? userLikedProjectsLoading : userProjectsLoading;
}, [
liked,
scope,
userLikedPlaybacksLoading,
userLikedProjectsLoading,
userProjectsLoading,
]);
const emptyStateContent = useMemo(() => {
const variant = liked ? "liked" : "default";
@@ -172,25 +183,30 @@ export default function AllMyList() {
<Text style={placeholderDescriptionStyle}>{loadingMessage}</Text>
</View>
) : sanitizedItems.length > 0 ? (
sanitizedItems.map((item) => (
<MusicCard
key={item.id}
title={item?.title || "Sans titre"}
imageUri={item?.coverUrl || null}
subtitle={item?.userName}
projectId={item?.id}
likedBy={item?.likedBy || []}
onPress={() => onPressItem(item)}
onPressMore={(posTop) => {
setSelectedProjectId(item.id);
setMenuPosition(posTop);
setShowMenu(
(prev) =>
!prev || posTop?.top !== (menuPosition?.top ?? null)
);
}}
/>
))
sanitizedItems.map((item) => {
const likeTarget =
scope === "playback" ? LIKE_TARGET.PLAYBACK : LIKE_TARGET.SONG;
return (
<MusicCard
key={item.id}
title={item?.title || "Sans titre"}
imageUri={item?.coverUrl || null}
subtitle={item?.userName}
projectId={item?.id}
likedBy={getProjectLikes(item, likeTarget)}
likeTarget={likeTarget}
onPress={() => onPressItem(item)}
onPressMore={(posTop) => {
setSelectedProjectId(item.id);
setMenuPosition(posTop);
setShowMenu(
(prev) =>
!prev || posTop?.top !== (menuPosition?.top ?? null)
);
}}
/>
);
})
) : (
<View style={[Style.containerCenter, placeholderBaseStyle]}>
<Text style={placeholderTitleStyle}>
+3 -1
View File
@@ -15,6 +15,7 @@ import { useUser } from "../../providers/UserDataProvider";
import { gutters, Palette, Style } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import { size } from "../../styles/Style";
import { getProjectLikes, LIKE_TARGET } from "../../utils/likes";
import MusicCard from "./components/MusicCard";
const AllMyPlaylist = ({ route }) => {
@@ -277,7 +278,8 @@ const AllMyPlaylist = ({ route }) => {
subtitle={p?.userName}
imageUri={p?.coverUrl || null}
projectId={p?.id}
likedBy={p?.likedBy || []}
likedBy={getProjectLikes(p, LIKE_TARGET.SONG)}
likeTarget={LIKE_TARGET.SONG}
onPress={() => handlePlayFromPlaylist(p)}
onPressMore={(posTop) => {
setSelectedProjectId(p.id);
+1 -1
View File
@@ -86,7 +86,7 @@ const Library = () => {
experimentalBlurMethod="dimezisBlurView"
style={{ borderRadius: 12, padding: 12, overflow: "hidden" }}
>
{hasMyMusic && <MyMusic />}
<MyMusic />
</BlurView>
{hasBackTracks && <BackTracks />}
{hasLikedMusic && <LikedMusic />}
+15 -18
View File
@@ -25,13 +25,7 @@ import { useGlobal } from "reactn";
import { background, icons } from "../../assets";
import PressableScale from "../../components/PressableScale";
import Slider from "../../components/Slider";
import {
arrayRemove,
arrayUnion,
increment,
projectsRef,
usersRef,
} from "../../config/firebase";
import { increment, projectsRef, usersRef } from "../../config/firebase";
import useDataFromRef from "../../hooks/useDataFromRef";
import usePlayer from "../../hooks/usePlayer";
import useTrackController from "../../hooks/useTrackController";
@@ -45,6 +39,11 @@ import {
createMusicSharePayload,
openShareSheet,
} from "../../utils/shareSheet";
import {
getProjectLikes,
LIKE_TARGET,
toggleProjectLike,
} from "../../utils/likes";
import {
formatStructureLabel,
getPromptLabelForStructure,
@@ -86,13 +85,10 @@ const MusicDetails = ({ route }) => {
});
useEffect(() => {
if (project && currentUID) {
const liked = Array.isArray(project?.likedBy)
? project.likedBy.includes(currentUID)
: false;
setFav(liked);
}
}, [project?.likedBy, currentUID]);
const likes = getProjectLikes(project, LIKE_TARGET.SONG);
const liked = currentUID ? likes.includes(currentUID) : false;
setFav(liked);
}, [currentUID, project]);
const title = project?.title || "Sans titre";
const artist = useMemo(() => {
@@ -862,10 +858,11 @@ const MusicDetails = ({ route }) => {
const next = !fav;
setFav(next);
try {
await projectsRef.doc(projectId).update({
likedBy: next
? arrayUnion(currentUID)
: arrayRemove(currentUID),
await toggleProjectLike({
projectId,
target: LIKE_TARGET.SONG,
currentUID,
next,
});
} catch (e) {
setFav(!next);
+26 -29
View File
@@ -27,13 +27,7 @@ import { useGlobal } from "reactn";
import { background, icons } from "../../assets";
import PressableScale from "../../components/PressableScale";
import Slider from "../../components/Slider";
import {
arrayRemove,
arrayUnion,
increment,
projectsRef,
usersRef,
} from "../../config/firebase";
import { increment, projectsRef, usersRef } from "../../config/firebase";
import { ensureAuthenticated } from "../../utils/authRedirect";
import useDataFromRef from "../../hooks/useDataFromRef";
import useTrackController from "../../hooks/useTrackController";
@@ -50,6 +44,11 @@ import {
normalizeStructureType,
segmentRequiresLyrics,
} from "../../utils/songStructure";
import {
getProjectLikes,
LIKE_TARGET,
toggleProjectLike,
} from "../../utils/likes";
import {
createMusicSharePayload,
openShareSheet,
@@ -117,13 +116,10 @@ const MusicDetails = ({ route }) => {
});
useEffect(() => {
if (project && currentUID) {
const liked = Array.isArray(project?.likedBy)
? project.likedBy.includes(currentUID)
: false;
setFav(liked);
}
}, [project?.likedBy, currentUID]);
const likes = getProjectLikes(project, LIKE_TARGET.SONG);
const liked = currentUID ? likes.includes(currentUID) : false;
setFav(liked);
}, [currentUID, project]);
const title = project?.title || "Sans titre";
const artist = useMemo(() => {
@@ -1069,21 +1065,22 @@ const MusicDetails = ({ route }) => {
</PressableScale>
<Pressable
onPress={async () => {
if (!projectId) return;
if (!ensureAuthenticated(currentUID)) return;
const next = !fav;
setFav(next);
try {
await projectsRef.doc(projectId).update({
likedBy: next
? arrayUnion(currentUID)
: arrayRemove(currentUID),
});
} catch (e) {
setFav(!next);
}
}}
>
if (!projectId) return;
if (!ensureAuthenticated(currentUID)) return;
const next = !fav;
setFav(next);
try {
await toggleProjectLike({
projectId,
target: LIKE_TARGET.SONG,
currentUID,
next,
});
} catch (e) {
setFav(!next);
}
}}
>
<RNImage
source={fav ? icons.heart : icons.heartOutline}
style={{ ...size({ size: 24 }) }}
+3 -1
View File
@@ -13,6 +13,7 @@ import usePlayer from "../../hooks/usePlayer";
import Page from "../../layouts/Page";
import { goBack } from "../../navigation/NavigationService";
import { gutters } from "../../styles";
import { getProjectLikes, LIKE_TARGET } from "../../utils/likes";
import MusicCard from "./components/MusicCard";
const PlaylistDetails = () => {
@@ -156,7 +157,8 @@ const PlaylistDetails = () => {
subtitle={p?.userName}
imageUri={p?.coverUrl || null}
projectId={p?.id}
likedBy={p?.likedBy || []}
likedBy={getProjectLikes(p, LIKE_TARGET.SONG)}
likeTarget={LIKE_TARGET.SONG}
onPress={() => handlePlayFromPlaylist(p)}
onPressMore={(posTop) => {
setSelectedProjectId(p.id);
+90 -63
View File
@@ -1,13 +1,17 @@
import React, { useState } from "react";
import { View } from "react-native";
import { Text, View } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import { useGlobal } from "reactn";
import GradientButton from "../../../components/GradientButton";
import MoreMenu from "../../../components/MoreMenu";
import { projectsRef } from "../../../config/firebase";
import useNavigateToMusicDetails from "../../../hooks/useNavigateToMusicDetails";
import { Routes } from "../../../navigation";
import { navigate } from "../../../navigation/NavigationService";
import { useUser } from "../../../providers/UserDataProvider";
import { Palette } from "../../../styles";
import { FONT_FAMILY } from "../../../styles/Fonts";
import { getProjectLikes, LIKE_TARGET } from "../../../utils/likes";
import CardContainer from "./CardContainer";
import MusicCard from "./MusicCard";
const MyMusic = () => {
@@ -16,6 +20,7 @@ const MyMusic = () => {
const [showMenu, setShowMenu] = useState(false);
const [selectedProjectId, setSelectedProjectId] = useState(null);
const projects = Array.isArray(userProjects) ? userProjects.slice(0, 3) : [];
const hasProjects = projects.length > 0;
const [, setTooltip] = useGlobal("_tooltip");
const navigateToMusicDetails = useNavigateToMusicDetails();
@@ -32,69 +37,91 @@ const MyMusic = () => {
}
onPressPlus={() => navigate(Routes.WritingLyrics)}
>
<View style={{ gap: 10 }}>
{Array.isArray(projects) &&
projects.map((p) => (
<MusicCard
key={p.id}
title={p?.title}
subtitle={p?.userName}
imageUri={p?.coverUrl || null}
projectId={p?.id}
likedBy={p?.likedBy || []}
onPress={() =>
navigateToMusicDetails({
projectId: p.id,
songUrl: p?.songUrl,
project: p,
})
}
onPressMore={(posTop) => {
setSelectedProjectId(p.id);
setMenuPosition(posTop);
setShowMenu(
(prev) => !prev || posTop?.top !== (menuPosition?.top ?? null)
);
}}
/>
))}
<MoreMenu
visible={showMenu}
top={menuPosition?.top ?? 0}
position={menuPosition}
onClose={() => setShowMenu(false)}
inPlaylist={false}
projectId={selectedProjectId}
extraItems={[
{
label: "Supprimer",
onPress: () =>
SheetManager.show("Delete", {
payload: {
title: "Supprimer le projet",
message:
"Cette action supprimera définitivement ce projet.",
onConfirm: async () => {
try {
if (!selectedProjectId) return;
await projectsRef.doc(selectedProjectId).delete();
setTooltip({
type: "success",
text: "Projet supprimé",
});
} catch (e) {
setTooltip({
type: "error",
text: e?.message || "Suppression impossible",
});
}
{hasProjects ? (
<View style={{ gap: 10 }}>
{Array.isArray(projects) &&
projects.map((p) => (
<MusicCard
key={p.id}
title={p?.title}
subtitle={p?.userName}
imageUri={p?.coverUrl || null}
projectId={p?.id}
likedBy={getProjectLikes(p, LIKE_TARGET.SONG)}
likeTarget={LIKE_TARGET.SONG}
onPress={() =>
navigateToMusicDetails({
projectId: p.id,
songUrl: p?.songUrl,
project: p,
})
}
onPressMore={(posTop) => {
setSelectedProjectId(p.id);
setMenuPosition(posTop);
setShowMenu(
(prev) =>
!prev || posTop?.top !== (menuPosition?.top ?? null)
);
}}
/>
))}
<MoreMenu
visible={showMenu}
top={menuPosition?.top ?? 0}
position={menuPosition}
onClose={() => setShowMenu(false)}
inPlaylist={false}
projectId={selectedProjectId}
extraItems={[
{
label: "Supprimer",
onPress: () =>
SheetManager.show("Delete", {
payload: {
title: "Supprimer le projet",
message:
"Cette action supprimera définitivement ce projet.",
onConfirm: async () => {
try {
if (!selectedProjectId) return;
await projectsRef.doc(selectedProjectId).delete();
setTooltip({
type: "success",
text: "Projet supprimé",
});
} catch (e) {
setTooltip({
type: "error",
text: e?.message || "Suppression impossible",
});
}
},
},
},
}),
},
]}
/>
</View>
}),
},
]}
/>
</View>
) : (
<View style={{ flexDirection: "column", gap: 10 }}>
<Text
style={{
textAlign: "center",
fontFamily: FONT_FAMILY.InterRegular,
color: Palette.white,
fontSize: 16,
}}
>
Aucune musique pour l'instant
</Text>
<GradientButton
containerStyle={{ padding: 2 }}
onPress={() => navigate(Routes.WritingLyrics)}
title=" Créer une musique"
/>
</View>
)}
</CardContainer>
);
};
+69 -43
View File
@@ -3,6 +3,7 @@ import React from "react";
import { Image, Platform, Pressable, Text, View } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import { icons } from "../../../assets";
import GradientButton from "../../../components/GradientButton";
import useLayoutType from "../../../hooks/useLayoutType";
import { Routes } from "../../../navigation";
import { navigate } from "../../../navigation/NavigationService";
@@ -17,6 +18,7 @@ const MyPlaylist = () => {
? userPlaylists.slice(0, 2)
: [];
const { isWeb } = useLayoutType();
const hasPlaylists = playlists.length > 0;
return (
<CardContainer
label="Mes Playlists"
@@ -28,54 +30,78 @@ const MyPlaylist = () => {
}
>
<View style={{ gap: 8 }}>
{playlists.map((item) => (
<Pressable
key={item.id}
style={{ borderRadius: 12, overflow: "hidden" }}
onPress={() => {
console.log("isWeb", isWeb);
if (isWeb) {
navigate(Routes.AllMyPlaylist, { playListId: item.id });
} else {
navigate(Routes.PlaylistDetails, { playlistId: item.id });
}
}}
>
<BlurView
intensity={Platform.OS !== "ios" ? 10 : 20}
style={{
...Style.containerSpaceBetween,
minHeight: 59,
paddingHorizontal: 10,
paddingVertical: 12,
{hasPlaylists ? (
playlists.map((item) => (
<Pressable
key={item.id}
style={{ borderRadius: 12, overflow: "hidden" }}
onPress={() => {
console.log("isWeb", isWeb);
if (isWeb) {
navigate(Routes.AllMyPlaylist, { playListId: item.id });
} else {
navigate(Routes.PlaylistDetails, { playlistId: item.id });
}
}}
// experimentalBlurMethod={
// Platform.OS !== "ios" ? "dimezisBlurView" : "none"
// }
>
<Text
<BlurView
intensity={Platform.OS !== "ios" ? 10 : 20}
style={{
flex: 1,
fontSize: 16,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
marginRight: 12,
flexShrink: 1,
...Style.containerSpaceBetween,
minHeight: 59,
paddingHorizontal: 10,
paddingVertical: 12,
}}
// experimentalBlurMethod={
// Platform.OS !== "ios" ? "dimezisBlurView" : "none"
// }
>
{item?.name || "Sans nom"}
</Text>
<Image
source={icons.chevronDown}
style={{
...size({ size: 15 }),
transform: [{ rotate: "-90deg" }],
}}
resizeMode="contain"
/>
</BlurView>
</Pressable>
))}
<Text
style={{
flex: 1,
fontSize: 16,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
marginRight: 12,
flexShrink: 1,
}}
>
{item?.name || "Sans nom"}
</Text>
<Image
source={icons.chevronDown}
style={{
...size({ size: 15 }),
transform: [{ rotate: "-90deg" }],
}}
resizeMode="contain"
/>
</BlurView>
</Pressable>
))
) : (
<View style={{ flexDirection: "column", gap: 10 }}>
<Text
style={{
textAlign: "center",
fontFamily: FONT_FAMILY.InterRegular,
color: Palette.white,
fontSize: 16,
}}
>
Aucune playlist pour l'instant
</Text>
<GradientButton
containerStyle={{ padding: 2 }}
onPress={() =>
SheetManager.show("Playlist", {
payload: { startAtCreate: true },
})
}
title=" Créer une playlist"
/>
</View>
)}
</View>
</CardContainer>
);
@@ -8,6 +8,7 @@ import { navigate } from "../../../navigation/NavigationService";
import { Palette, Style } from "../../../styles";
import { FONT_FAMILY } from "../../../styles/Fonts";
import { getArtistDisplayName } from "../../../utils/artistName";
import { getProjectLikes, LIKE_TARGET } from "../../../utils/likes";
import CreateLyricsHeader from "../../Writing/components/CreateLyricsHeader";
import MusicCard from "./MusicCard";
import { defaultAvatar } from "../../../data/data";
@@ -157,7 +158,8 @@ const SearchResultsList = ({
subtitle={project?.userName}
imageUri={resolveCoverUri(project)}
projectId={project?.id}
likedBy={project?.likedBy || []}
likedBy={getProjectLikes(project, LIKE_TARGET.SONG)}
likeTarget={LIKE_TARGET.SONG}
onPress={() => handleMusicPress(project)}
onPressMore={(positionTop) => {
setSelectedProjectId(project.id);
@@ -203,7 +205,8 @@ const SearchResultsList = ({
preferThumbnail: true,
})}
projectId={project?.id}
likedBy={project?.likedBy || []}
likedBy={getProjectLikes(project, LIKE_TARGET.PLAYBACK)}
likeTarget={LIKE_TARGET.PLAYBACK}
onPress={() => handlePlaybackPress(project)}
onPressMore={(positionTop) => {
setSelectedProjectId(project.id);