diff --git a/functions/index.js b/functions/index.js
index b281aa8..134d496 100644
--- a/functions/index.js
+++ b/functions/index.js
@@ -18,3 +18,4 @@ exports.cover = require("./src/cover");
exports.projects = require("./src/project");
exports.thumbnail = require("./src/thumbnail");
exports.upload = require("./src/upload");
+exports.algolia = require("./src/algolia");
diff --git a/functions/src/algolia.js b/functions/src/algolia.js
new file mode 100644
index 0000000..201294e
--- /dev/null
+++ b/functions/src/algolia.js
@@ -0,0 +1,26 @@
+const { onRequest } = require("firebase-functions/v2/https");
+
+exports.algoliaTransformProjectData = onRequest(
+ { region: "europe-west1" },
+ (req, res) => {
+ const payload = req.body.data;
+ const objectID = payload.objectID;
+ try {
+ const flat = { ...payload };
+ delete flat["musicTimestamps"];
+
+ console.log(`Change in ${payload.objectID}`);
+ console.log(flat);
+ // Ton object final doit contenir "objectID"
+ const result = {
+ objectID,
+ ...flat,
+ };
+
+ res.send({ result });
+ } catch (e) {
+ console.log(`Error ${objectID}:`, e.message);
+ res.status(500).end();
+ }
+ },
+);
diff --git a/functions/src/project.js b/functions/src/project.js
index f30ec58..3f0be78 100644
--- a/functions/src/project.js
+++ b/functions/src/project.js
@@ -33,6 +33,20 @@ exports.onProjectUpdate = onDocumentWritten(
batch.delete(doc.ref);
});
await batch.commit();
+ } else {
+ if (previousData) {
+ // edit
+ if (!previousData?.songUrl && currentData?.songUrl) {
+ await refList.projects.doc(projectId).update({
+ hasSong: true,
+ });
+ }
+ if (!previousData?.playbackUrl && currentData?.playbackUrl) {
+ await refList.projects.doc(projectId).update({
+ hasPlayback: true,
+ });
+ }
+ }
}
} catch (e) {
console.log(e);
diff --git a/src/data/keys.js b/src/data/keys.js
index 5a3d833..0e36e89 100644
--- a/src/data/keys.js
+++ b/src/data/keys.js
@@ -19,3 +19,17 @@ export const GOOGLE_IOS_CLIENT_ID =
// Android client ID (client_type 1 from google-services.json)
export const GOOGLE_ANDROID_CLIENT_ID =
"305598753437-qck73beo37mi04qlautgafbpdl3ccaoh.apps.googleusercontent.com";
+
+const AlgoliaAppId = "38AHXYR8S1";
+const AlgoliaSearchKey = "2ac15d7b8b5e6490b53821067e4a0f03";
+
+export const AlgoliaUserConfig = {
+ index: "users",
+ projectID: AlgoliaAppId,
+ publicKey: AlgoliaSearchKey,
+};
+export const AlgoliaProjectConfig = {
+ index: "projects",
+ projectID: AlgoliaAppId,
+ publicKey: AlgoliaSearchKey,
+};
diff --git a/src/hooks/useSearch.js b/src/hooks/useSearch.js
index 6767356..45124a4 100644
--- a/src/hooks/useSearch.js
+++ b/src/hooks/useSearch.js
@@ -1,7 +1,7 @@
-import { useEffect, useMemo, useState } from "react";
-import { useGlobal } from "reactn";
-import { usersRef, projectsRef } from "../config/firebase";
-import useDataFromRef from "./useDataFromRef";
+import { useState } from "react";
+import useAlgoliaSearch from "react-native-minuit/src/hooks/useAlgoliaSearch";
+import { AlgoliaUserConfig, AlgoliaProjectConfig } from "../data/keys";
+import { useUserData } from "../providers/UserDataProvider";
const batchSizes = {
users: 5,
@@ -11,221 +11,42 @@ const batchSizes = {
const useSearch = () => {
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 { currentUID } = useUserData();
- const usersQueryRef = useMemo(() => {
- try {
- const lower = queryLower;
- if (lower) {
- return usersRef
- .orderBy("userNameLower")
- .startAt(lower)
- .endAt(lower + "\uf8ff");
- }
- return usersRef.orderBy("userNameLower");
- } catch (_error) {
- 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 (_error) {
- return null;
- }
- }, [queryText]);
-
- const projectsQueryRefCase = useMemo(() => {
- try {
- const raw = queryText || "";
- if (raw) {
- return projectsRef
- .orderBy("title")
- .startAt(raw)
- .endAt(raw + "\uf8ff");
- }
- return null;
- } catch (_error) {
- return null;
- }
- }, [queryText]);
-
- const {
- data: userResults = [],
- loading: usersLoading,
- loadMore: loadMoreUsers,
- } = useDataFromRef({
- ref: usersQueryRef,
- simpleRef: false,
- listener: false,
- condition: !!usersQueryRef,
- refreshArray: [queryLower],
- usePagination: true,
- batchSize: batchSizes.users,
+ const { hits: users, loading: userLoading } = useAlgoliaSearch({
+ query: search,
+ algoliaObject: AlgoliaUserConfig,
+ batch: batchSizes.users,
+ searchParams: {
+ filters: `NOT objectID:${currentUID}`,
+ },
});
-
- const filteredUsers = useMemo(() => {
- const arr = Array.isArray(userResults) ? userResults : [];
- const matchingUsers = queryLower
- ? arr.filter((user) => {
- const nameLower = (
- user?.userNameLower ||
- user?.userName ||
- ""
- ).toLowerCase();
- return nameLower.includes(queryLower);
- })
- : arr;
- const seen = new Set();
- const unique = [];
- for (const user of matchingUsers) {
- const id = user?.id;
- if (!id || seen.has(id)) continue;
- if (id === currentUID || user?.userID === currentUID) continue;
- seen.add(id);
- unique.push(user);
- }
- return unique;
- }, [userResults, queryLower, currentUID]);
-
- const {
- data: projectResultsLower = [],
- loading: projectsLoadingLower,
- loadMore: loadMoreProjectsLower,
- } = useDataFromRef({
- ref: projectsQueryRefLower,
- simpleRef: false,
- listener: false,
- condition: !!projectsQueryRefLower,
- refreshArray: [queryText],
- usePagination: true,
- batchSize: batchSizes.projects,
+ const { hits: playbacks, loading: playbackLoading } = useAlgoliaSearch({
+ query: search,
+ algoliaObject: AlgoliaProjectConfig,
+ batch: batchSizes.projects,
+ searchParams: {
+ filters: `hasPlayback:true AND hasSong:true`,
+ },
});
-
- const {
- data: projectResultsCase = [],
- loading: projectsLoadingCase,
- loadMore: loadMoreProjectsCase,
- } = useDataFromRef({
- ref: projectsQueryRefCase,
- simpleRef: false,
- listener: false,
- condition: !!projectsQueryRefCase,
- refreshArray: [queryText],
- usePagination: true,
- batchSize: batchSizes.projects,
+ const { hits: musics, loading: musicLoading } = useAlgoliaSearch({
+ query: search,
+ algoliaObject: AlgoliaProjectConfig,
+ batch: batchSizes.projects,
+ searchParams: {
+ filters: `hasPlayback:false AND hasSong:true`,
+ },
});
- 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: batchSizes.projects,
- });
-
- const projectsLoading = queryText
- ? projectsLoadingLower || projectsLoadingCase
- : projectsLoadingDefault;
-
- const loadMoreProjects = () => {
- if (queryText) {
- loadMoreProjectsLower?.();
- loadMoreProjectsCase?.();
- } else {
- loadMoreProjectsDefault?.();
- }
- };
-
- const filteredProjects = useMemo(() => {
- const lowerMatches = Array.isArray(projectResultsLower)
- ? projectResultsLower
- : [];
- const caseMatches = Array.isArray(projectResultsCase)
- ? projectResultsCase
- : [];
- const defaultProjects = Array.isArray(projectResultsDefault)
- ? projectResultsDefault
- : [];
-
- const arr = queryText ? [...lowerMatches, ...caseMatches] : defaultProjects;
- const seen = new Set();
- const unique = [];
- for (const project of arr) {
- const id = project?.id;
- if (!id || seen.has(id)) continue;
- seen.add(id);
- unique.push(project);
- }
- return unique;
- }, [
- queryText,
- projectResultsLower,
- projectResultsCase,
- projectResultsDefault,
- ]);
-
- useEffect(() => {
- setAutoLoads(0);
- }, [queryText]);
-
- useEffect(() => {
- if (
- filteredUsers.length < batchSizes.users &&
- usersQueryRef &&
- !usersLoading
- ) {
- if (autoLoads < 3) {
- setAutoLoads((previous) => previous + 1);
- loadMoreUsers?.();
- }
- }
- }, [
- filteredUsers.length,
- usersLoading,
- usersQueryRef,
- autoLoads,
- loadMoreUsers,
- ]);
-
- const toggleSelected = (item) => {
- setSelected((prev) => (prev === item ? null : item));
- };
-
return {
search,
setSearch,
- queryText,
- queryLower,
selected,
setSelected,
- toggleSelected,
- filteredUsers,
- usersLoading,
- loadMoreUsers,
- filteredProjects,
- projectsLoading,
- loadMoreProjects,
+ users,
+ musics,
+ playbacks,
+ loading: userLoading || musicLoading || playbackLoading,
};
};
diff --git a/src/screens/Library/Library.web.js b/src/screens/Library/Library.web.js
index 6ba43b3..00995c4 100644
--- a/src/screens/Library/Library.web.js
+++ b/src/screens/Library/Library.web.js
@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect, useRef, useState } from "react";
+import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Image, Platform, ScrollView, Text, View } from "react-native";
import { responsiveHeight } from "react-native-responsive-dimensions";
import { background, icons } from "../../assets";
@@ -50,15 +50,38 @@ const Library = () => {
setSearch,
selected,
setSelected,
- toggleSelected,
- filteredProjects,
- projectsLoading,
- loadMoreProjects,
- filteredUsers,
- usersLoading,
- loadMoreUsers,
+ users = [],
+ musics = [],
+ playbacks = [],
+ loading,
} = useSearch();
+ const toggleSelected = useCallback(
+ (value) => {
+ setSelected((previous) => (previous === value ? null : value));
+ },
+ [setSelected],
+ );
+
+ const musicResults = useMemo(
+ () => (Array.isArray(musics) ? musics : []),
+ [musics],
+ );
+
+ const playbackResults = useMemo(
+ () => (Array.isArray(playbacks) ? playbacks : []),
+ [playbacks],
+ );
+
+ const userResults = useMemo(
+ () => (Array.isArray(users) ? users : []),
+ [users],
+ );
+
+ const musicsLoading = loading;
+ const playbacksLoading = loading;
+ const usersLoading = loading;
+
const [dropdownVisible, setDropdownVisible] = useState(false);
const searchWrapperRef = useRef(null);
@@ -152,12 +175,12 @@ const Library = () => {
/>
diff --git a/src/screens/Library/Research.js b/src/screens/Library/Research.js
index 1c276ad..a2d9871 100644
--- a/src/screens/Library/Research.js
+++ b/src/screens/Library/Research.js
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useCallback, useMemo } from "react";
import { View } from "react-native";
import { background } from "../../assets";
import useSearch from "../../hooks/useSearch";
@@ -12,15 +12,39 @@ const Research = () => {
search,
setSearch,
selected,
- toggleSelected,
- filteredProjects,
- projectsLoading,
- loadMoreProjects,
- filteredUsers,
- usersLoading,
- loadMoreUsers,
+ setSelected,
+ users = [],
+ musics = [],
+ playbacks = [],
+ loading,
} = useSearch();
+ const toggleSelected = useCallback(
+ (value) => {
+ setSelected((previous) => (previous === value ? null : value));
+ },
+ [setSelected],
+ );
+
+ const musicResults = useMemo(
+ () => (Array.isArray(musics) ? musics : []),
+ [musics],
+ );
+
+ const playbackResults = useMemo(
+ () => (Array.isArray(playbacks) ? playbacks : []),
+ [playbacks],
+ );
+
+ const userResults = useMemo(
+ () => (Array.isArray(users) ? users : []),
+ [users],
+ );
+
+ const musicsLoading = loading;
+ const playbacksLoading = loading;
+ const usersLoading = loading;
+
return (
{
/>
diff --git a/src/screens/Library/components/SearchResultsList.js b/src/screens/Library/components/SearchResultsList.js
index 80980c4..8d4ce0b 100644
--- a/src/screens/Library/components/SearchResultsList.js
+++ b/src/screens/Library/components/SearchResultsList.js
@@ -1,6 +1,6 @@
-import { Image as ExpoImage } from "expo-image";
import React, { useState } from "react";
import { Pressable, ScrollView, Text, View } from "react-native";
+import { Image as ExpoImage } from "expo-image";
import { img } from "../../../assets";
import MoreMenu from "../../../components/MoreMenu";
import { Routes } from "../../../navigation";
@@ -28,12 +28,12 @@ const EmptyText = ({ text }) => (
const SearchResultsList = ({
selected,
- filteredProjects,
- projectsLoading,
- loadMoreProjects,
- filteredUsers,
- usersLoading,
- loadMoreUsers,
+ musics = [],
+ musicsLoading = false,
+ playbacks = [],
+ playbacksLoading = false,
+ users = [],
+ usersLoading = false,
onResultSelected,
contentContainerStyle,
style,
@@ -52,14 +52,23 @@ const SearchResultsList = ({
onResultSelected?.();
};
+ const shouldShowMusics =
+ (!selected && (musics.length > 0 || musicsLoading)) || selected === "Musiques";
+
+ const shouldShowPlaybacks =
+ (!selected && (playbacks.length > 0 || playbacksLoading)) ||
+ selected === "Playbacks";
+
+ const shouldShowUsers =
+ (!selected && (users.length > 0 || usersLoading)) || selected === "Profils";
+
return (
- {((!selected && (filteredProjects.length > 0 || projectsLoading)) ||
- selected === "Musiques") && (
+ {shouldShowMusics && (
- {filteredProjects.length > 0 && (
+ {musics.length > 0 && (
- {Array.isArray(filteredProjects) &&
- filteredProjects.slice(0, 6).map((project) => (
+ {Array.isArray(musics) &&
+ musics.slice(0, 6).map((project) => (
!previous ||
- positionTop?.top !== (menuPosition?.top ?? null)
+ positionTop?.top !== (menuPosition?.top ?? null),
);
}}
/>
))}
- {/* {(filteredProjects.length >= 6 || projectsLoading) && (
-
-
- {projectsLoading ? "Chargement…" : "Charger plus"}
-
-
- )} */}
+ {"Chargement…"}
+
+ )}
)}
- {filteredProjects.length === 0 && !projectsLoading && (
+ {musics.length === 0 && !musicsLoading && (
)}
)}
- {((!selected && (filteredUsers.length > 0 || usersLoading)) ||
- selected === "Profils") && (
+ {shouldShowPlaybacks && (
+
+
+ {playbacks.length > 0 && (
+
+ {Array.isArray(playbacks) &&
+ playbacks.slice(0, 6).map((project) => (
+ handleProjectPress(project.id)}
+ onPressMore={(positionTop) => {
+ setSelectedProjectId(project.id);
+ setMenuPosition(positionTop);
+ setShowMenu(
+ (previous) =>
+ !previous ||
+ positionTop?.top !== (menuPosition?.top ?? null),
+ );
+ }}
+ />
+ ))}
+ {playbacksLoading && (
+
+ {"Chargement…"}
+
+ )}
+
+ )}
+ {playbacks.length === 0 && !playbacksLoading && (
+
+ )}
+
+
+ )}
+ {shouldShowUsers && (
- {filteredUsers.length > 0 && (
+ {users.length > 0 && (
- {Array.isArray(filteredUsers) &&
- filteredUsers.slice(0, 5).map((user) => (
+ {Array.isArray(users) &&
+ users.slice(0, 5).map((user) => (
))}
- {/* {(filteredUsers.length >= 5 || usersLoading) && (
-
-
- {usersLoading ? "Chargement…" : "Charger plus"}
-
-
- )} */}
+ {"Chargement…"}
+
+ )}
)}
- {filteredUsers.length === 0 && !usersLoading && (
+ {users.length === 0 && !usersLoading && (
)}
)}
- {selected === "Playbacks" && (
-
-
-
-
-
- )}
{selected === "Clips" && (
{
await setIsLoading(true);
await updateProjectData({
title: result?.title || "",
- titleLower: (result?.title || "").toLowerCase(),
lyrics: Array.isArray(result?.lyrics) ? result.lyrics : [],
description: result?.lyricsDescription,
config: config || null,
@@ -198,7 +197,6 @@ const CreatingLyrics = ({ active, config, selections }) => {
await setIsLoading(true);
await updateProjectData({
title: result?.title || "",
- titleLower: (result?.title || "").toLowerCase(),
lyrics: Array.isArray(result?.lyrics)
? result.lyrics
: [],
diff --git a/src/screens/Writing/Lyrics.js b/src/screens/Writing/Lyrics.js
index 7b77738..b797f5f 100644
--- a/src/screens/Writing/Lyrics.js
+++ b/src/screens/Writing/Lyrics.js
@@ -112,7 +112,7 @@ const Lyrics = ({ navigation }) => {
if (invalid) {
Alert.alert(
"Champs incomplets",
- "Chaque couplet et refrain doit contenir du texte."
+ "Chaque couplet et refrain doit contenir du texte.",
);
return;
}
@@ -133,7 +133,7 @@ const Lyrics = ({ navigation }) => {
normalizedOldLyrics.every(
(s, i) =>
s.type === normalizedNewLyrics[i]?.type &&
- s.lyrics === normalizedNewLyrics[i]?.lyrics
+ s.lyrics === normalizedNewLyrics[i]?.lyrics,
);
// 1) Appel de la Cloud Function de modération avant tout enregistrement
@@ -157,7 +157,7 @@ const Lyrics = ({ navigation }) => {
: null;
Alert.alert(
"Contenu interdit",
- [data?.message, quotes].filter(Boolean).join("\n\n")
+ [data?.message, quotes].filter(Boolean).join("\n\n"),
);
return; // stop here
}
@@ -165,7 +165,7 @@ const Lyrics = ({ navigation }) => {
if (data?.errorCode === "ANALYSE_FAILED") {
Alert.alert(
"Analyse indisponible",
- "Impossible de vérifier la toxicité pour le moment. Réessayez plus tard."
+ "Impossible de vérifier la toxicité pour le moment. Réessayez plus tard.",
);
return;
}
@@ -200,18 +200,17 @@ const Lyrics = ({ navigation }) => {
} catch (moderationError) {
console.log(
"Moderation call failed",
- moderationError?.message || moderationError
+ moderationError?.message || moderationError,
);
Alert.alert(
"Analyse indisponible",
- "Impossible de vérifier la toxicité pour le moment. Réessayez plus tard."
+ "Impossible de vérifier la toxicité pour le moment. Réessayez plus tard.",
);
return;
}
const baseData = {
title: titleTrimmed,
- titleLower: titleTrimmed.toLowerCase(),
lyrics: normalizedNewLyrics,
config: sanitize(projectConfig),
selections: sanitize(projectSelections),