Files
musicland/src/screens/Library/components/SearchResultsList.js
T
Thomas Demirdjian 72852ad92b last tickets
2025-11-24 15:35:14 +01:00

314 lines
10 KiB
JavaScript

import React, { useCallback, useMemo, useState } from "react";
import { Pressable, ScrollView, Text, View } from "react-native";
import MoreMenu from "../../../components/MoreMenu";
import ProfilePicture from "../../../components/ProfilePicture";
import useNavigateToMusicDetails from "../../../hooks/useNavigateToMusicDetails";
import { Routes } from "../../../navigation";
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";
const EmptyText = ({ text }) => (
<View style={{ flex: 1, alignItems: "center", paddingVertical: 16 }}>
<Text
style={{
fontSize: 14,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
opacity: 0.8,
}}
>
{text}
</Text>
</View>
);
const SearchResultsList = ({
selected,
musics = [],
musicsLoading = false,
playbacks = [],
playbacksLoading = false,
users = [],
usersLoading = false,
onResultSelected,
contentContainerStyle,
style,
}) => {
const [menuPosition, setMenuPosition] = useState(null);
const [showMenu, setShowMenu] = useState(false);
const [selectedProjectId, setSelectedProjectId] = useState(null);
const navigateToMusicDetails = useNavigateToMusicDetails();
const resolveCoverUri = useCallback(
(project, { preferThumbnail = false } = {}) => {
const isValid = (value) =>
typeof value === "string" && value.trim().length > 0;
if (!project) {
return null;
}
const cover = project?.cover || {};
const thumbnailCandidates = [
project?.thumbnailUrl,
project?.songThumbnailUrl,
].filter(isValid);
if (preferThumbnail) {
const preferredThumbnail = thumbnailCandidates.find(isValid);
if (preferredThumbnail) {
return preferredThumbnail;
}
}
if (isValid(project?.coverUrl)) {
return project.coverUrl;
}
const coverCandidates = [
cover?.result,
cover?.finalUrl,
cover?.generatedBackground,
];
if (Array.isArray(cover?.options)) {
coverCandidates.push(
...cover.options.flatMap((option) => [
option?.finalUrl,
option?.generatedUrl,
]),
);
}
return coverCandidates.find(isValid) || null;
},
[],
);
const handleMusicPress = (project) => {
const didNavigate = navigateToMusicDetails({
projectId: project?.id,
songUrl: project?.songUrl,
project,
});
if (didNavigate) {
onResultSelected?.();
}
};
const handlePlaybackPress = (project) => {
if (!project?.id) {
return;
}
navigate(Routes.Playbacks, { projectId: project.id });
onResultSelected?.();
};
const handleProfilePress = (userId) => {
navigate(Routes.SingerProfile, { userId });
onResultSelected?.();
};
const musicItems = useMemo(() => {
if (!Array.isArray(musics)) {
return [];
}
if (selected === "Musiques") {
return musics;
}
return musics.filter((project) => project?.hasPlayback !== true);
}, [musics, selected]);
const shouldShowMusics =
(!selected && (musicItems.length > 0 || musicsLoading)) ||
selected === "Musiques";
const shouldShowPlaybacks =
(!selected && (playbacks.length > 0 || playbacksLoading)) ||
selected === "Playback";
const shouldShowUsers =
(!selected && (users.length > 0 || usersLoading)) || selected === "Profils";
return (
<View style={[{ flex: 1 }, style]}>
<ScrollView
contentContainerStyle={{ paddingTop: 2, ...contentContainerStyle }}
showsVerticalScrollIndicator={false}
>
{shouldShowMusics && (
<View style={{ gap: 10 }}>
{musicItems.length > 0 && (
<>
{Array.isArray(musicItems) &&
musicItems.slice(0, 6).map((project) => (
<MusicCard
key={project.id}
title={project?.title || "Sans titre"}
subtitle={project?.userName}
imageUri={resolveCoverUri(project)}
projectId={project?.id}
likedBy={getProjectLikes(project, LIKE_TARGET.SONG)}
likeTarget={LIKE_TARGET.SONG}
onPress={() => handleMusicPress(project)}
onPressMore={(positionTop) => {
setSelectedProjectId(project.id);
setMenuPosition(positionTop);
setShowMenu(
(previous) =>
!previous ||
positionTop?.top !== (menuPosition?.top ?? null),
);
}}
/>
))}
{musicsLoading && (
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
alignSelf: "center",
}}
>
{"Chargement…"}
</Text>
)}
</>
)}
{musicItems.length === 0 && !musicsLoading && (
<EmptyText text={"Aucune musique trouvée"} />
)}
</View>
)}
{shouldShowPlaybacks && (
<View style={{ gap: 10, paddingTop: 10 }}>
{playbacks.length > 0 && (
<>
{Array.isArray(playbacks) &&
playbacks.slice(0, 6).map((project) => (
<MusicCard
key={project.id}
title={project?.title || "Sans titre"}
subtitle={project?.userName}
imageUri={resolveCoverUri(project, {
preferThumbnail: true,
})}
projectId={project?.id}
likedBy={getProjectLikes(project, LIKE_TARGET.PLAYBACK)}
likeTarget={LIKE_TARGET.PLAYBACK}
onPress={() => handlePlaybackPress(project)}
onPressMore={(positionTop) => {
setSelectedProjectId(project.id);
setMenuPosition(positionTop);
setShowMenu(
(previous) =>
!previous ||
positionTop?.top !== (menuPosition?.top ?? null),
);
}}
/>
))}
{playbacksLoading && (
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
alignSelf: "center",
}}
>
{"Chargement…"}
</Text>
)}
</>
)}
{playbacks.length === 0 && !playbacksLoading && (
<EmptyText text={"Aucun playback trouvé"} />
)}
</View>
)}
{shouldShowUsers && (
<View style={{ paddingHorizontal: 2, paddingTop: 10 }}>
<CreateLyricsHeader
gradientProps={{ start: { x: 0, y: 0 }, end: { x: 0, y: 1 } }}
>
{users.length > 0 && (
<View style={{ gap: 8 }}>
{Array.isArray(users) &&
users.slice(0, 5).map((user) => (
<Pressable
style={{ gap: 14, ...Style.containerRow }}
key={user.id}
onPress={() => handleProfilePress(user.id)}
>
<ProfilePicture
uri={user?.profilePictureURL || defaultAvatar}
size={60}
imageProps={{ priority: "high" }}
/>
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
}}
>
{getArtistDisplayName(user, "Utilisateur")}
</Text>
</Pressable>
))}
{usersLoading && (
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
alignSelf: "center",
}}
>
{"Chargement…"}
</Text>
)}
</View>
)}
{users.length === 0 && !usersLoading && (
<EmptyText text={"Aucun profil trouvé"} />
)}
</CreateLyricsHeader>
</View>
)}
{selected === "Clips" && (
<View style={{ paddingHorizontal: 2, paddingTop: 10 }}>
<CreateLyricsHeader
gradientProps={{ start: { x: 0, y: 0 }, end: { x: 0, y: 1 } }}
>
<EmptyText text={"Aucun clip trouvé"} />
</CreateLyricsHeader>
</View>
)}
</ScrollView>
<MoreMenu
visible={showMenu}
top={menuPosition?.top ?? 0}
position={menuPosition}
onClose={() => setShowMenu(false)}
inPlaylist={false}
projectId={selectedProjectId}
/>
</View>
);
};
export default SearchResultsList;