search on web

This commit is contained in:
Thomas Demirdjian
2025-10-02 17:53:17 +02:00
parent ea421d8eff
commit 8846ca05ff
14 changed files with 656 additions and 415 deletions
+27 -382
View File
@@ -1,225 +1,25 @@
import React, { useEffect, useMemo, useState } from "react";
import { Image, Pressable, ScrollView, Text, View } from "react-native";
import { Image as ExpoImage } from "expo-image";
import { background, img } from "../../assets";
import MoreMenu from "../../components/MoreMenu";
import { usersRef, projectsRef } from "../../config/firebase";
import useDataFromRef from "../../hooks/useDataFromRef";
import { useGlobal } from "reactn";
import React from "react";
import { View } from "react-native";
import { background } from "../../assets";
import useSearch from "../../hooks/useSearch";
import Page from "../../layouts/Page";
import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService";
import { gutters, Palette, Style } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import { size } from "../../styles/Style";
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
import MusicCard from "./components/MusicCard";
import { gutters } from "../../styles";
import ResearchHeader from "./components/ResearchHeader";
import SearchResultsList from "./components/SearchResultsList";
const Research = () => {
const [selected, setSelected] = useState(null);
const [search, setSearch] = useState("");
const queryText = useMemo(() => search.trim(), [search]);
const queryLower = useMemo(
() => (search || "").toLowerCase().trim(),
[search],
);
const [currentUID] = useGlobal("currentUID");
const [autoLoads, setAutoLoads] = useState(0);
const usersQueryRef = useMemo(() => {
try {
const lower = queryLower;
if (lower) {
return usersRef
.orderBy("userNameLower")
.startAt(lower)
.endAt(lower + "\uf8ff");
}
return usersRef.orderBy("userNameLower");
} catch (e) {
return null;
}
}, [queryLower]);
const projectsQueryRefLower = useMemo(() => {
try {
const lower = queryText ? queryText.toLowerCase() : "";
if (lower) {
return projectsRef
.orderBy("titleLower")
.startAt(lower)
.endAt(lower + "\uf8ff");
}
return null;
} catch (e) {
return null;
}
}, [queryText]);
const projectsQueryRefCase = useMemo(() => {
try {
const raw = queryText || "";
if (raw) {
return projectsRef
.orderBy("title")
.startAt(raw)
.endAt(raw + "\uf8ff");
}
return null;
} catch (e) {
return null;
}
}, [queryText]);
const {
data: userResults = [],
loading: usersLoading,
loadMore: loadMoreUsers,
} = useDataFromRef({
ref: usersQueryRef,
simpleRef: false,
listener: false,
condition: !!usersQueryRef,
refreshArray: [queryLower],
usePagination: true,
batchSize: 5,
});
const filteredUsers = useMemo(() => {
let arr = Array.isArray(userResults) ? userResults : [];
// Client-side safety filter to guarantee match on pseudo
if (queryLower) {
arr = arr.filter((u) => {
const nameLower = (u?.userNameLower || u?.userName || "").toLowerCase();
return nameLower.includes(queryLower);
});
}
const seen = new Set();
const unique = [];
for (const u of arr) {
const id = u?.id;
if (!id || seen.has(id)) continue;
if (id === currentUID || u?.userID === currentUID) continue;
seen.add(id);
unique.push(u);
}
return unique;
}, [JSON.stringify(userResults), queryLower, currentUID]);
const {
data: projectResultsLower = [],
loading: projectsLoadingLower,
loadMore: loadMoreProjectsLower,
} = useDataFromRef({
ref: projectsQueryRefLower,
simpleRef: false,
listener: false,
condition: !!projectsQueryRefLower,
refreshArray: [queryText],
usePagination: true,
batchSize: 6,
});
const {
data: projectResultsCase = [],
loading: projectsLoadingCase,
loadMore: loadMoreProjectsCase,
} = useDataFromRef({
ref: projectsQueryRefCase,
simpleRef: false,
listener: false,
condition: !!projectsQueryRefCase,
refreshArray: [queryText],
usePagination: true,
batchSize: 6,
});
const {
data: projectResultsDefault = [],
loading: projectsLoadingDefault,
loadMore: loadMoreProjectsDefault,
} = useDataFromRef({
ref: !queryText ? projectsRef.orderBy("updatedAt", "desc") : null,
simpleRef: false,
listener: false,
condition: !queryText,
refreshArray: [queryText],
usePagination: true,
batchSize: 6,
});
const projectsLoading = queryText
? projectsLoadingLower || projectsLoadingCase
: projectsLoadingDefault;
const loadMoreProjects = () => {
if (queryText) {
loadMoreProjectsLower?.();
loadMoreProjectsCase?.();
} else {
loadMoreProjectsDefault?.();
}
};
const filteredProjects = useMemo(() => {
const arr = queryText
? [
...(Array.isArray(projectResultsLower) ? projectResultsLower : []),
...(Array.isArray(projectResultsCase) ? projectResultsCase : []),
]
: Array.isArray(projectResultsDefault)
? projectResultsDefault
: [];
const seen = new Set();
const unique = [];
for (const p of arr) {
const id = p?.id;
if (!id || seen.has(id)) continue;
seen.add(id);
unique.push(p);
}
return unique;
}, [
queryText,
JSON.stringify(projectResultsLower),
JSON.stringify(projectResultsCase),
JSON.stringify(projectResultsDefault),
]);
useEffect(() => {
setAutoLoads(0);
}, [queryText]);
useEffect(() => {
if (filteredUsers.length < 5 && usersQueryRef && !usersLoading) {
if (autoLoads < 3) {
setAutoLoads((n) => n + 1);
loadMoreUsers();
}
}
}, [filteredUsers.length, usersLoading, usersQueryRef, autoLoads]);
const [menuPosition, setMenuPosition] = useState(null);
const [showMenu, setShowMenu] = useState(false);
const [selectedProjectId, setSelectedProjectId] = useState(null);
const onPressMenu = (item) => {
if (selected === item) {
setSelected(null);
} else {
setSelected(item);
}
};
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>
);
search,
setSearch,
selected,
toggleSelected,
filteredProjects,
projectsLoading,
loadMoreProjects,
filteredUsers,
usersLoading,
loadMoreUsers,
} = useSearch();
return (
<Page
@@ -232,175 +32,20 @@ const Research = () => {
>
<View style={{ flex: 1, gap: 10 }}>
<ResearchHeader
onPress={(item) => onPressMenu(item)}
onPress={toggleSelected}
selected={selected}
searchValue={search}
onChangeSearch={setSearch}
/>
<View style={{ flex: 1 }}>
<ScrollView contentContainerStyle={{ paddingTop: 2 }}>
{((!selected && (filteredProjects.length > 0 || projectsLoading)) ||
selected === "Musiques") && (
<View style={{ paddingHorizontal: 2 }}>
<CreateLyricsHeader
gradientProps={{
start: { x: 0, y: 0 },
end: { x: 0, y: 1 },
}}
>
{filteredProjects.length > 0 ? (
<View style={{ gap: 10 }}>
{Array.isArray(filteredProjects) &&
filteredProjects.slice(0, 6).map((p) => (
<MusicCard
key={p.id}
title={p?.title || "Sans titre"}
subtitle={"MusicLand"}
imageUri={p?.coverUrl || null}
projectId={p?.id}
likedBy={p?.likedBy || []}
onPress={() =>
navigate(Routes.MusicDetails, { projectId: p.id })
}
onPressMore={(posTop) => {
setSelectedProjectId(p.id);
setMenuPosition(posTop);
setShowMenu(
(prev) =>
!prev ||
posTop?.top !== (menuPosition?.top ?? null),
);
}}
/>
))}
{(filteredProjects.length >= 6 || projectsLoading) && (
<Pressable
onPress={loadMoreProjects}
style={{ alignSelf: "center", marginTop: 6 }}
>
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
opacity: projectsLoading ? 0.6 : 1,
}}
>
{projectsLoading ? "Chargement…" : "Charger plus"}
</Text>
</Pressable>
)}
</View>
) : (
!projectsLoading && (
<EmptyText text={"Aucune musique trouvée"} />
)
)}
</CreateLyricsHeader>
</View>
)}
{((!selected && (filteredUsers.length > 0 || usersLoading)) ||
selected === "Profils") && (
<View style={{ paddingHorizontal: 2, paddingTop: 10 }}>
<CreateLyricsHeader
gradientProps={{ start: { x: 0, y: 0 }, end: { x: 0, y: 1 } }}
>
{filteredUsers.length > 0 ? (
<View style={{ gap: 8 }}>
{Array.isArray(filteredUsers) &&
filteredUsers.slice(0, 5).map((u) => (
<Pressable
style={{ gap: 14, ...Style.containerRow }}
key={u.id}
onPress={() =>
navigate(Routes.SingerProfile, { userId: u.id })
}
>
<ExpoImage
source={
u?.pictureUrl ||
u?.profilePictureURL ||
u?.photoURL
? {
uri:
u?.pictureUrl ||
u?.profilePictureURL ||
u?.photoURL,
}
: img.profile
}
cachePolicy="memory-disk"
priority="high"
contentFit="cover"
transition={100}
style={{
...size({ size: 60 }),
borderRadius: 100,
}}
/>
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
}}
>
{u?.userName || "Utilisateur"}
</Text>
</Pressable>
))}
{(filteredUsers.length >= 5 || usersLoading) && (
<Pressable
onPress={loadMoreUsers}
style={{ alignSelf: "center", marginTop: 6 }}
>
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
opacity: usersLoading ? 0.6 : 1,
}}
>
{usersLoading ? "Chargement…" : "Charger plus"}
</Text>
</Pressable>
)}
</View>
) : (
!usersLoading && <EmptyText text={"Aucun profil trouvé"} />
)}
</CreateLyricsHeader>
</View>
)}
{selected === "Playbacks" && (
<View style={{ paddingHorizontal: 2, paddingTop: 10 }}>
<CreateLyricsHeader
gradientProps={{ start: { x: 0, y: 0 }, end: { x: 0, y: 1 } }}
>
<EmptyText text={"Aucun playback 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>
)}
<MoreMenu
visible={showMenu}
top={menuPosition?.top ?? 0}
position={menuPosition}
onClose={() => setShowMenu(false)}
inPlaylist={false}
projectId={selectedProjectId}
/>
</ScrollView>
</View>
<SearchResultsList
selected={selected}
filteredProjects={filteredProjects}
projectsLoading={projectsLoading}
loadMoreProjects={loadMoreProjects}
filteredUsers={filteredUsers}
usersLoading={usersLoading}
loadMoreUsers={loadMoreUsers}
/>
</View>
</Page>
);