follow users
This commit is contained in:
@@ -49,6 +49,7 @@ try {
|
|||||||
export const usersRef = firestore.collection("users");
|
export const usersRef = firestore.collection("users");
|
||||||
export const projectsRef = firestore.collection("projects");
|
export const projectsRef = firestore.collection("projects");
|
||||||
export const playlistsRef = firestore.collection("playlists");
|
export const playlistsRef = firestore.collection("playlists");
|
||||||
|
export const followsRef = firestore.collection("follows");
|
||||||
export const notificationsRef = firestore.collection("notifications");
|
export const notificationsRef = firestore.collection("notifications");
|
||||||
export const documentsRef = firestore.collection("documents");
|
export const documentsRef = firestore.collection("documents");
|
||||||
export const chatsRef = firestore.collection("chats");
|
export const chatsRef = firestore.collection("chats");
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { createContext, useContext, useGlobal } from "reactn";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { checkIfEmailIsValid } from "../actions/signupActions";
|
import { checkIfEmailIsValid } from "../actions/signupActions";
|
||||||
import firebase, {
|
import firebase, {
|
||||||
|
followsRef,
|
||||||
playlistsRef,
|
playlistsRef,
|
||||||
projectsRef,
|
projectsRef,
|
||||||
usersRef,
|
usersRef,
|
||||||
@@ -26,7 +27,6 @@ export default ({ children }) => {
|
|||||||
const [userProjects, setUserProjects] = useState([]);
|
const [userProjects, setUserProjects] = useState([]);
|
||||||
const [userLikedProjects, setUserLikedProjects] = useState([]);
|
const [userLikedProjects, setUserLikedProjects] = useState([]);
|
||||||
const [userPlaylists, setUserPlaylists] = useState([]);
|
const [userPlaylists, setUserPlaylists] = useState([]);
|
||||||
|
|
||||||
useDataFromRef({
|
useDataFromRef({
|
||||||
ref: currentUID ? usersRef.doc(currentUID) : null,
|
ref: currentUID ? usersRef.doc(currentUID) : null,
|
||||||
simpleRef: true,
|
simpleRef: true,
|
||||||
@@ -86,6 +86,50 @@ export default ({ children }) => {
|
|||||||
...newData,
|
...newData,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
const followUser = async (userId) => {
|
||||||
|
try {
|
||||||
|
if (currentUID && userId) {
|
||||||
|
await followsRef.doc(`${currentUID}_${userId}`).set(
|
||||||
|
{
|
||||||
|
followerId: currentUID,
|
||||||
|
followeeId: userId,
|
||||||
|
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
|
||||||
|
},
|
||||||
|
{ merge: true }
|
||||||
|
);
|
||||||
|
setTooltip({ type: "success", text: "Abonnement mis à jour" });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
setTooltip({ type: "error", text: e?.message || "Action impossible" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const unfollowUser = async (userId) => {
|
||||||
|
try {
|
||||||
|
if (currentUID && userId) {
|
||||||
|
await followsRef.doc(`${currentUID}_${userId}`).delete();
|
||||||
|
setTooltip({ type: "success", text: "Vous ne suivez plus cet utilisateur" });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
setTooltip({ type: "error", text: e?.message || "Action impossible" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getUserByUid = async (uid) => {
|
||||||
|
try {
|
||||||
|
const userDoc = await usersRef.doc(uid).get();
|
||||||
|
return userDoc.exists
|
||||||
|
? {
|
||||||
|
id: userDoc.id,
|
||||||
|
...userDoc.data(),
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const updateUserData = async ({ data, shouldSetTooltip = true }) => {
|
const updateUserData = async ({ data, shouldSetTooltip = true }) => {
|
||||||
try {
|
try {
|
||||||
@@ -193,13 +237,15 @@ export default ({ children }) => {
|
|||||||
userPlaylists,
|
userPlaylists,
|
||||||
|
|
||||||
setCurrentUserData,
|
setCurrentUserData,
|
||||||
|
getUserByUid,
|
||||||
onSignOut,
|
onSignOut,
|
||||||
|
|
||||||
pendingUserData,
|
pendingUserData,
|
||||||
updatePendingUserData,
|
updatePendingUserData,
|
||||||
|
|
||||||
updateUserData,
|
updateUserData,
|
||||||
|
followUser,
|
||||||
|
unfollowUser,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,29 +1,49 @@
|
|||||||
import {
|
import React, { useMemo, useState } from "react";
|
||||||
View,
|
import { Image, Pressable, ScrollView, Text, View } from "react-native";
|
||||||
Text,
|
|
||||||
StyleSheet,
|
|
||||||
Pressable,
|
|
||||||
ScrollView,
|
|
||||||
Image,
|
|
||||||
} from "react-native";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import Page from "../../layouts/Page";
|
|
||||||
import { background, img } from "../../assets";
|
import { background, img } from "../../assets";
|
||||||
import { gutters, Palette, Style } from "../../styles";
|
|
||||||
import SearchBar from "../../components/SearchBar";
|
|
||||||
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
|
||||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
|
||||||
import BorderGradient from "../../components/BorderGradient/BorderGradient";
|
|
||||||
import { BlurView } from "expo-blur";
|
|
||||||
import ResearchHeader from "./components/ResearchHeader";
|
|
||||||
import MusicCard from "./components/MusicCard";
|
|
||||||
import MoreMenu from "../../components/MoreMenu";
|
import MoreMenu from "../../components/MoreMenu";
|
||||||
import { size } from "../../styles/Style";
|
import { usersRef } from "../../config/firebase";
|
||||||
import { navigate } from "../../navigation/NavigationService";
|
import useDataFromRef from "../../hooks/useDataFromRef";
|
||||||
|
import Page from "../../layouts/Page";
|
||||||
import { Routes } from "../../navigation";
|
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 ResearchHeader from "./components/ResearchHeader";
|
||||||
|
|
||||||
const Research = () => {
|
const Research = () => {
|
||||||
const [selected, setSelected] = useState(null);
|
const [selected, setSelected] = useState(null);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const queryText = useMemo(() => search.trim(), [search]);
|
||||||
|
|
||||||
|
const usersQueryRef = useMemo(() => {
|
||||||
|
if (!queryText) return null;
|
||||||
|
try {
|
||||||
|
return usersRef
|
||||||
|
.orderBy("userName")
|
||||||
|
.startAt(queryText)
|
||||||
|
.endAt(queryText + "\uf8ff");
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [queryText]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: userResults = [],
|
||||||
|
loading: usersLoading,
|
||||||
|
loadMore: loadMoreUsers,
|
||||||
|
} = useDataFromRef({
|
||||||
|
ref: usersQueryRef,
|
||||||
|
simpleRef: false,
|
||||||
|
listener: false,
|
||||||
|
condition: !!usersQueryRef && queryText.length > 0,
|
||||||
|
refreshArray: [queryText],
|
||||||
|
usePagination: true,
|
||||||
|
batchSize: 5,
|
||||||
|
});
|
||||||
const [menuTop, setMenuTop] = useState(0);
|
const [menuTop, setMenuTop] = useState(0);
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
const [selectedProjectId, setSelectedProjectId] = useState(null);
|
const [selectedProjectId, setSelectedProjectId] = useState(null);
|
||||||
@@ -49,6 +69,8 @@ const Research = () => {
|
|||||||
<ResearchHeader
|
<ResearchHeader
|
||||||
onPress={(item) => onPressMenu(item)}
|
onPress={(item) => onPressMenu(item)}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
|
searchValue={search}
|
||||||
|
onChangeSearch={setSearch}
|
||||||
/>
|
/>
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<ScrollView contentContainerStyle={{ paddingTop: 2 }}>
|
<ScrollView contentContainerStyle={{ paddingTop: 2 }}>
|
||||||
@@ -113,20 +135,35 @@ const Research = () => {
|
|||||||
{(!selected || selected === "Profils") && (
|
{(!selected || selected === "Profils") && (
|
||||||
<View style={{ paddingHorizontal: 2, paddingTop: 10 }}>
|
<View style={{ paddingHorizontal: 2, paddingTop: 10 }}>
|
||||||
<CreateLyricsHeader
|
<CreateLyricsHeader
|
||||||
gradientProps={{
|
gradientProps={{ start: { x: 0, y: 0 }, end: { x: 0, y: 1 } }}
|
||||||
start: { x: 0, y: 0 },
|
|
||||||
end: { x: 0, y: 1 },
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<View style={{ gap: 8 }}>
|
<View style={{ gap: 8 }}>
|
||||||
{Array.from({ length: 2 }).map((_, index) => (
|
{queryText.length === 0 && (
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: Palette.gray,
|
||||||
|
fontFamily: FONT_FAMILY.InterRegular,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Commence à taper pour rechercher des profils…
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{queryText.length > 0 &&
|
||||||
|
Array.isArray(userResults) &&
|
||||||
|
userResults.map((u) => (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={{ gap: 14, ...Style.containerRow }}
|
style={{ gap: 14, ...Style.containerRow }}
|
||||||
key={index}
|
key={u.id}
|
||||||
onPress={() => navigate(Routes.SingerProfile)}
|
onPress={() =>
|
||||||
|
navigate(Routes.SingerProfile, { userId: u.id })
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
source={img.profile}
|
source={
|
||||||
|
u?.photoURL ? { uri: u.photoURL } : img.profile
|
||||||
|
}
|
||||||
style={{ ...size({ size: 60 }), borderRadius: 100 }}
|
style={{ ...size({ size: 60 }), borderRadius: 100 }}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
@@ -136,10 +173,28 @@ const Research = () => {
|
|||||||
fontFamily: FONT_FAMILY.InterRegular,
|
fontFamily: FONT_FAMILY.InterRegular,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
ReggaetonLil
|
{u?.userName || "Utilisateur"}
|
||||||
</Text>
|
</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{queryText.length > 0 && userResults.length > 0 && (
|
||||||
|
<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>
|
</View>
|
||||||
</CreateLyricsHeader>
|
</CreateLyricsHeader>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -6,10 +6,15 @@ import BorderGradient from "../../../components/BorderGradient/BorderGradient";
|
|||||||
import { BlurView } from "expo-blur";
|
import { BlurView } from "expo-blur";
|
||||||
import { FONT_FAMILY } from "../../../styles/Fonts";
|
import { FONT_FAMILY } from "../../../styles/Fonts";
|
||||||
|
|
||||||
const ResearchHeader = ({ onPress, selected }) => {
|
const ResearchHeader = ({ onPress, selected, searchValue = "", onChangeSearch = () => {} }) => {
|
||||||
return (
|
return (
|
||||||
<View style={{ gap: 12 }}>
|
<View style={{ gap: 12 }}>
|
||||||
<SearchBar />
|
<SearchBar
|
||||||
|
textInputProps={{
|
||||||
|
value: searchValue,
|
||||||
|
onChangeText: onChangeSearch,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
...Style.containerRow,
|
...Style.containerRow,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BlurView } from "expo-blur";
|
import { BlurView } from "expo-blur";
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
FlatList,
|
FlatList,
|
||||||
Image,
|
Image,
|
||||||
@@ -14,14 +14,46 @@ import { background, icons, img } from "../../assets";
|
|||||||
import BorderGradient from "../../components/BorderGradient/BorderGradient";
|
import BorderGradient from "../../components/BorderGradient/BorderGradient";
|
||||||
import GradientButton from "../../components/GradientButton";
|
import GradientButton from "../../components/GradientButton";
|
||||||
import Page from "../../layouts/Page";
|
import Page from "../../layouts/Page";
|
||||||
|
import { useUser } from "../../providers/UserDataProvider";
|
||||||
|
import { getFollowersCount, getFollowingCount } from "../../services/follows";
|
||||||
|
import { useGlobal } from "reactn";
|
||||||
|
import { followsRef } from "../../config/firebase";
|
||||||
import { gutters, Palette } from "../../styles";
|
import { gutters, Palette } from "../../styles";
|
||||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||||
import Style, { size } from "../../styles/Style";
|
import Style, { size } from "../../styles/Style";
|
||||||
import MusicCard from "../Library/components/MusicCard";
|
import MusicCard from "../Library/components/MusicCard";
|
||||||
|
const SingerProfile = ({ route }) => {
|
||||||
const SingerProfile = () => {
|
const { userId } = route.params;
|
||||||
|
const { getUserByUid, followUser, unfollowUser } = useUser();
|
||||||
|
const [currentUID] = useGlobal("currentUID");
|
||||||
|
console.log("user id ", userId);
|
||||||
const [selected, setSelected] = useState(null);
|
const [selected, setSelected] = useState(null);
|
||||||
|
const [userData, setUserData] = useState(null);
|
||||||
|
const [following, setFollowing] = useState(0);
|
||||||
|
const [followers, setFollowers] = useState(0);
|
||||||
|
const fetchUserData = async () => {
|
||||||
|
const data = userId ? await getUserByUid(userId) : {};
|
||||||
|
setUserData(data);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
fetchUserData();
|
||||||
|
}, [userId]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!userId) return;
|
||||||
|
getFollowersCount(userId).then(setFollowers);
|
||||||
|
getFollowingCount(userId).then(setFollowing);
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
if (currentUID && userId) {
|
||||||
|
const doc = await followsRef.doc(`${currentUID}_${userId}`).get();
|
||||||
|
setIsFollowing(!!doc?.exists);
|
||||||
|
} else {
|
||||||
|
setIsFollowing(false);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
})();
|
||||||
|
}, [userId, currentUID]);
|
||||||
|
console.log("following", following);
|
||||||
const onPressMenu = (item) => {
|
const onPressMenu = (item) => {
|
||||||
if (selected === item) {
|
if (selected === item) {
|
||||||
setSelected(null);
|
setSelected(null);
|
||||||
@@ -29,7 +61,19 @@ const SingerProfile = () => {
|
|||||||
setSelected(item);
|
setSelected(item);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const [isFollowing, setIsFollowing] = useState(false);
|
||||||
|
const handleFollowUser = async () => {
|
||||||
|
if (!currentUID || !userId || currentUID === userId) return;
|
||||||
|
if (isFollowing) {
|
||||||
|
await unfollowUser(userId);
|
||||||
|
setIsFollowing(false);
|
||||||
|
setFollowers((v) => Math.max(0, (v || 0) - 1));
|
||||||
|
} else {
|
||||||
|
await followUser(userId);
|
||||||
|
setIsFollowing(true);
|
||||||
|
setFollowers((v) => (v || 0) + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
backgroundImg={background.profileBG}
|
backgroundImg={background.profileBG}
|
||||||
@@ -74,7 +118,7 @@ const SingerProfile = () => {
|
|||||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
elia.mrn
|
{userData?.userName}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={{ ...Style.containerRow, gap: 10, marginTop: 10 }}>
|
<View style={{ ...Style.containerRow, gap: 10, marginTop: 10 }}>
|
||||||
@@ -83,22 +127,25 @@ const SingerProfile = () => {
|
|||||||
<Text style={styles.label}>playbacks</Text>
|
<Text style={styles.label}>playbacks</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={{ flex: 1, alignItems: "center" }}>
|
<View style={{ flex: 1, alignItems: "center" }}>
|
||||||
<Text style={styles.value}>880</Text>
|
<Text style={styles.value}>{followers}</Text>
|
||||||
<Text style={styles.label}>abonnés</Text>
|
<Text style={styles.label}>abonnés</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={{ flex: 1, alignItems: "center" }}>
|
<View style={{ flex: 1, alignItems: "center" }}>
|
||||||
<Text style={styles.value}>6</Text>
|
<Text style={styles.value}>{following}</Text>
|
||||||
<Text style={styles.label}>abonnements</Text>
|
<Text style={styles.label}>abonnements</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
{currentUID !== userId && (
|
||||||
<GradientButton
|
<GradientButton
|
||||||
title="Suivre"
|
onPress={handleFollowUser}
|
||||||
|
title={isFollowing ? "Ne plus suivre" : "Suivre"}
|
||||||
containerStyle={{
|
containerStyle={{
|
||||||
width: "80%",
|
width: "80%",
|
||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
marginTop: 18,
|
marginTop: 18,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</BlurView>
|
</BlurView>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { followsRef } from "../config/firebase";
|
||||||
|
|
||||||
|
export const getFollowersCount = async (userId) => {
|
||||||
|
if (!userId) return 0;
|
||||||
|
const snap = await followsRef.where("followeeId", "==", userId).get();
|
||||||
|
return snap?.size || 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFollowingCount = async (userId) => {
|
||||||
|
if (!userId) return 0;
|
||||||
|
const snap = await followsRef.where("followerId", "==", userId).get();
|
||||||
|
return snap?.size || 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unfollowUser = async ({ followerId, followeeId }) => {
|
||||||
|
if (!followerId || !followeeId) throw new Error("Missing followerId/followeeId");
|
||||||
|
const id = `${followerId}_${followeeId}`;
|
||||||
|
await followsRef.doc(id).delete();
|
||||||
|
return id;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user