314 lines
10 KiB
JavaScript
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;
|