Change main page and modify flows

This commit is contained in:
Thomas Demirdjian
2025-09-02 11:45:27 +02:00
parent 8b7980308a
commit 98367af0b7
47 changed files with 3695 additions and 1020 deletions
+7 -2
View File
@@ -1,7 +1,8 @@
import { BlurView } from "expo-blur";
import * as ImagePicker from "expo-image-picker";
import React, { useEffect, useState } from "react";
import { Image, Platform, Pressable, Text, View } from "react-native";
import { Platform, Pressable, Text, View, Image as RNImage } from "react-native";
import { Image as ExpoImage } from "expo-image";
import { responsiveHeight } from "react-native-responsive-dimensions";
import { useGlobal } from "reactn";
import { background, img } from "../../assets";
@@ -145,7 +146,7 @@ const EditProfile = () => {
}
>
<Pressable style={{ alignItems: "center" }} onPress={onChangePicture}>
<Image
<ExpoImage
source={
currentUserData?.profilePictureURL
? {
@@ -153,6 +154,10 @@ const EditProfile = () => {
}
: img.profile
}
cachePolicy="memory-disk"
priority="high"
contentFit="cover"
transition={100}
style={{
...size({ size: 108 }),
borderRadius: 100,
+174
View File
@@ -0,0 +1,174 @@
import React, { useMemo, useState } from "react";
import { Pressable, Text, View } from "react-native";
import { Image as ExpoImage } from "expo-image";
import Page from "../../layouts/Page";
import { background, img } from "../../assets";
import { Palette, Style } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import { size } from "../../styles/Style";
import { useUser } from "../../providers/UserDataProvider";
import { usersRef } from "../../config/firebase";
import useDataFromArrayId from "react-native-minuit/src/hooks/useDataFromArrayId";
import useDataFromRef from "../../hooks/useDataFromRef";
import { Routes } from "../../navigation";
import { useRoute } from "@react-navigation/native";
import { navigate } from "../../navigation/NavigationService";
const Follows = () => {
const { params } = useRoute();
const initial = params?.selected || "Abonnés"; // "Abonnés" | "Abonnements"
const targetUserId = params?.userId || null;
const [selected, setSelected] = useState(initial);
const { currentUID, currentUserData } = useUser();
// Abonnés (followers): docs for IDs in currentUserData.followedBy
// If viewing another user's profile, subscribe to that user's doc
const { data: targetUserDoc = null } = useDataFromRef({
ref: targetUserId ? usersRef.doc(targetUserId) : null,
simpleRef: true,
listener: true,
condition: !!targetUserId,
refreshArray: [targetUserId],
});
const followerIds = useMemo(() => {
if (targetUserId) {
return Array.isArray(targetUserDoc?.followedBy)
? targetUserDoc.followedBy
: [];
}
return Array.isArray(currentUserData?.followedBy)
? currentUserData.followedBy
: [];
}, [
targetUserId,
targetUserDoc?.followedBy?.length || 0,
currentUserData?.followedBy?.length || 0,
]);
const { data: followers = [], loading: followersLoading } =
useDataFromArrayId({
ref: usersRef,
arrayId: followerIds,
condition: followerIds.length > 0,
refreshArray: [followerIds.length],
});
// Abonnements (following): users whose followedBy includes currentUID
const { data: following = [], loading: followingLoading } = useDataFromRef({
ref:
targetUserId || currentUID
? usersRef.where(
"followedBy",
"array-contains",
targetUserId || currentUID,
)
: null,
simpleRef: false,
listener: true,
condition: !!(targetUserId || currentUID),
refreshArray: [targetUserId || currentUID],
});
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 UserRow = ({ user }) => (
<Pressable
style={{ gap: 14, ...Style.containerRow }}
onPress={() => navigate(Routes.SingerProfile, { userId: user?.id })}
>
<ExpoImage
source={
user?.profilePictureURL
? { uri: user.profilePictureURL }
: 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,
}}
>
{user?.userName || "Utilisateur"}
</Text>
</Pressable>
);
const list = selected === "Abonnés" ? followers : following;
const loading = selected === "Abonnés" ? followersLoading : followingLoading;
return (
<Page
headerType="NAVIGATION"
backgroundImg={background.profileBG}
title={"Abonnés et abonnements"}
contentContainerStyle={{ paddingBottom: 20 }}
>
<View style={{ gap: 12 }}>
<View style={{ ...Style.containerRow, gap: 6 }}>
{["Abonnés", "Abonnements"].map((item) => (
<Pressable
key={item}
onPress={() => setSelected(item)}
style={{ flex: 1 }}
>
<View
style={{
height: 30,
borderRadius: 100,
overflow: "hidden",
backgroundColor:
selected === item ? "#FFFFFF22" : Palette.glass,
alignItems: "center",
justifyContent: "center",
}}
>
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
}}
>
{item}
</Text>
</View>
</Pressable>
))}
</View>
<View style={{ gap: 8 }}>
{Array.isArray(list) && list.length > 0 ? (
list.map((u) => <UserRow key={u.id} user={u} />)
) : !loading ? (
<EmptyText
text={
selected === "Abonnés" ? "Aucun abonné" : "Aucun abonnement"
}
/>
) : null}
</View>
</View>
</Page>
);
};
export default Follows;
+80 -78
View File
@@ -1,5 +1,4 @@
import { BlurView } from "expo-blur";
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import {
FlatList,
Image,
@@ -10,6 +9,7 @@ import {
Text,
View,
} from "react-native";
import { Image as ExpoImage } from "expo-image";
import { SheetManager } from "react-native-actions-sheet";
import { responsiveHeight } from "react-native-responsive-dimensions";
import { useGlobal } from "reactn";
@@ -20,31 +20,40 @@ import MoreMenu from "../../components/MoreMenu";
import { projectsRef } from "../../config/firebase";
import Page from "../../layouts/Page";
import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService";
import { navigate, push } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider";
import { getFollowersCount, getFollowingCount } from "../../services/follows";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import Style, { gutters, size } from "../../styles/Style";
import MusicCard from "../Library/components/MusicCard";
import { BlurView } from "expo-blur";
const Profile = () => {
const [selected, setSelected] = useState("Chansons");
const [currentUserData] = useGlobal("currentUserData");
const { userProjects, currentUID } = useUser();
const { userProjects, followingCount, currentUserData } = useUser();
const [, setTooltip] = useGlobal("_tooltip");
const [menuTop, setMenuTop] = useState(0);
const [showMenu, setShowMenu] = useState(false);
const [selectedProjectId, setSelectedProjectId] = useState(null);
const [following, setFollowing] = useState(0);
const [followers, setFollowers] = useState(0);
const onPressMenu = (item) => {
setSelected(item);
};
useEffect(() => {
if (!currentUID) return;
getFollowersCount(currentUID).then(setFollowers);
getFollowingCount(currentUID).then(setFollowing);
}, [currentUID]);
const EmptyText = ({ text }) => (
<View style={{ flex: 1, ...Style.containerCenter, paddingTop: 20 }}>
<Text
style={{
fontSize: 14,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
opacity: 0.8,
}}
>
{text}
</Text>
</View>
);
return (
<Page
backgroundImg={background.profileBG}
@@ -76,7 +85,7 @@ const Profile = () => {
}
>
<View style={{ alignItems: "center" }}>
<Image
<ExpoImage
source={
currentUserData?.profilePictureURL
? {
@@ -84,6 +93,10 @@ const Profile = () => {
}
: img.profile
}
cachePolicy="memory-disk"
priority="high"
contentFit="cover"
transition={100}
style={{
...size({ size: 108 }),
borderRadius: 100,
@@ -101,19 +114,27 @@ const Profile = () => {
</View>
<View style={{ ...Style.containerRow, gap: 10, marginTop: 10 }}>
<View style={{ flex: 1, alignItems: "center" }}>
<Text style={styles.value}>{userProjects?.length || 0}</Text>
<Text style={styles.label}>projets</Text>
</View>
<Pressable
style={{ flex: 1, alignItems: "center" }}
onPress={() => push(Routes.Follows, { selected: "Abonnés" })}
>
<Text style={styles.value}>
{currentUserData?.playlist?.length || 0}
{currentUserData?.followedBy?.length || 0}
</Text>
<Text style={styles.label}>playbacks</Text>
</View>
<View style={{ flex: 1, alignItems: "center" }}>
<Text style={styles.value}>{followers}</Text>
<Text style={styles.label}>abonnés</Text>
</View>
<View style={{ flex: 1, alignItems: "center" }}>
<Text style={styles.value}>{following}</Text>
</Pressable>
<Pressable
style={{ flex: 1, alignItems: "center" }}
onPress={() =>
push(Routes.Follows, { selected: "Abonnements" })
}
>
<Text style={styles.value}>{followingCount}</Text>
<Text style={styles.label}>abonnements</Text>
</View>
</Pressable>
</View>
</BlurView>
</View>
@@ -157,64 +178,39 @@ const Profile = () => {
</View>
{selected === "Playbacks" && (
<View style={{ flex: 1 }}>
<FlatList
data={Array.from({ length: 6 })}
numColumns={3}
contentContainerStyle={{ gap: 14 }}
columnWrapperStyle={{ gap: 10 }}
renderItem={() => (
<Pressable
style={{ flex: 1, gap: 6 }}
onPress={() => navigate(Routes.Reels)}
>
<Image
source={img.placeholder3}
style={{
width: "100%",
height: 147,
borderRadius: 10,
}}
/>
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
}}
>
Pour Lili
</Text>
</Pressable>
)}
/>
<EmptyText text={"Aucun playback pour le moment"} />
</View>
)}
{selected === "Chansons" && (
<ScrollView style={{ flex: 1 }}>
<FlatList
data={Array.isArray(userProjects) ? userProjects : []}
contentContainerStyle={{
gap: 10,
}}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<MusicCard
title={item?.title || "Sans titre"}
subtitle={currentUserData?.userName || "MusicLand"}
imageUri={item?.coverUrl || null}
projectId={item?.id}
likedBy={item?.likedBy || []}
onPress={() =>
navigate(Routes.MusicDetails, { projectId: item.id })
}
onPressMore={(posTop) => {
setSelectedProjectId(item.id);
setMenuTop(posTop);
setShowMenu((prev) => !prev || posTop !== menuTop);
}}
/>
)}
/>
{Array.isArray(userProjects) && userProjects.length > 0 ? (
<FlatList
data={userProjects}
contentContainerStyle={{
gap: 10,
}}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<MusicCard
title={item?.title || "Sans titre"}
subtitle={currentUserData?.userName || "MusicLand"}
imageUri={item?.coverUrl || null}
projectId={item?.id}
likedBy={item?.likedBy || []}
onPress={() =>
navigate(Routes.MusicDetails, { projectId: item.id })
}
onPressMore={(posTop) => {
setSelectedProjectId(item.id);
setMenuTop(posTop);
setShowMenu((prev) => !prev || posTop !== menuTop);
}}
/>
)}
/>
) : (
<EmptyText text={"Aucune chanson pour le moment"} />
)}
<MoreMenu
visible={showMenu}
top={menuTop}
@@ -249,13 +245,19 @@ const Profile = () => {
},
},
],
{ cancelable: true }
{ cancelable: true },
),
},
]}
/>
</ScrollView>
)}
{selected === "Clips" && (
<View style={{ flex: 1 }}>
<EmptyText text={"Aucun clip pour le moment"} />
</View>
)}
</View>
</Page>
);
+99 -77
View File
@@ -1,4 +1,3 @@
import { BlurView } from "expo-blur";
import React, { useEffect, useMemo, useState } from "react";
import {
FlatList,
@@ -9,23 +8,26 @@ import {
Text,
View,
} from "react-native";
import { Image as ExpoImage } from "expo-image";
import { responsiveHeight } from "react-native-responsive-dimensions";
import { useGlobal } from "reactn";
import { background, icons, img } from "../../assets";
import BorderGradient from "../../components/BorderGradient/BorderGradient";
import GradientButton from "../../components/GradientButton";
import MoreMenu from "../../components/MoreMenu";
import { followsRef, projectsRef } from "../../config/firebase";
import useDataFromRef from "../../hooks/useDataFromRef";
import { projectsRef } from "../../config/firebase";
import Page from "../../layouts/Page";
import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService";
import { navigate, push } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider";
import { getFollowersCount, getFollowingCount } from "../../services/follows";
import useDataFromRef from "../../hooks/useDataFromRef";
import { usersRef } from "../../config/firebase";
import { gutters, Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import Style, { size } from "../../styles/Style";
import MusicCard from "../Library/components/MusicCard";
import { BlurView } from "expo-blur";
const SingerProfile = ({ route }) => {
const { userId } = route.params;
const { getUserByUid, followUser, unfollowUser } = useUser();
@@ -37,25 +39,32 @@ const SingerProfile = ({ route }) => {
const fetchUserData = async () => {
const data = userId ? await getUserByUid(userId) : {};
setUserData(data);
const list = Array.isArray(data?.followedBy) ? data.followedBy : [];
setFollowers(list.length);
setIsFollowing(currentUID ? list.includes(currentUID) : false);
};
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]);
// Live following count for the viewed user
const followingQueryRef = React.useMemo(() => {
try {
return userId
? usersRef.where("followedBy", "array-contains", userId)
: null;
} catch (e) {
return null;
}
}, [userId]);
useDataFromRef({
ref: followingQueryRef,
simpleRef: false,
listener: true,
condition: !!followingQueryRef,
refreshArray: [userId],
onUpdate: (list) => setFollowing(Array.isArray(list) ? list.length : 0),
});
const onPressMenu = (item) => {
if (selected === item) {
setSelected(null);
@@ -85,6 +94,21 @@ const SingerProfile = ({ route }) => {
condition: !!userProjectsQueryRef,
refreshArray: [userId],
});
const EmptyText = ({ text }) => (
<View style={{ flex: 1, ...Style.containerCenter, paddingTop: 20 }}>
<Text
style={{
fontSize: 14,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
opacity: 0.8,
}}
>
{text}
</Text>
</View>
);
const handleFollowUser = async () => {
if (!currentUID || !userId || currentUID === userId) return;
if (isFollowing) {
@@ -127,7 +151,7 @@ const SingerProfile = ({ route }) => {
}
>
<View style={{ alignItems: "center" }}>
<Image
<ExpoImage
source={
userData?.profilePictureURL
? {
@@ -135,6 +159,10 @@ const SingerProfile = ({ route }) => {
}
: img.profile
}
cachePolicy="memory-disk"
priority="high"
contentFit="cover"
transition={100}
style={{
...size({ size: 108 }),
borderRadius: 100,
@@ -155,16 +183,26 @@ const SingerProfile = ({ route }) => {
<Text style={styles.value}>
{Array.isArray(userProjects) ? userProjects.length : 0}
</Text>
<Text style={styles.label}>playbacks</Text>
<Text style={styles.label}>projets</Text>
</View>
<View style={{ flex: 1, alignItems: "center" }}>
<Pressable
style={{ flex: 1, alignItems: "center" }}
onPress={() =>
push(Routes.Follows, { selected: "Abonnés", userId })
}
>
<Text style={styles.value}>{followers}</Text>
<Text style={styles.label}>abonnés</Text>
</View>
<View style={{ flex: 1, alignItems: "center" }}>
</Pressable>
<Pressable
style={{ flex: 1, alignItems: "center" }}
onPress={() =>
push(Routes.Follows, { selected: "Abonnements", userId })
}
>
<Text style={styles.value}>{following}</Text>
<Text style={styles.label}>abonnements</Text>
</View>
</Pressable>
</View>
{currentUID !== userId && (
<GradientButton
@@ -219,61 +257,45 @@ const SingerProfile = ({ route }) => {
</View>
{selected === "Playbacks" && (
<View style={{ flex: 1 }}>
<FlatList
data={Array.from({ length: 6 })}
numColumns={3}
contentContainerStyle={{ gap: 14 }}
columnWrapperStyle={{ gap: 10 }}
renderItem={() => (
<View style={{ flex: 1, gap: 6 }}>
<Image
source={img.placeholder3}
style={{
width: "100%",
height: 147,
borderRadius: 10,
}}
/>
<Text
style={{
fontSize: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
}}
>
Pour Lili
</Text>
</View>
)}
/>
<EmptyText text={"Aucun playback pour le moment"} />
</View>
)}
{selected === "Chansons" && (
<View style={{ flex: 1 }}>
<FlatList
data={Array.isArray(userProjects) ? userProjects : []}
contentContainerStyle={{
gap: 10,
}}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<MusicCard
title={item?.title || "Sans titre"}
subtitle={userData?.userName || "MusicLand"}
imageUri={item?.coverUrl || null}
projectId={item?.id}
likedBy={item?.likedBy || []}
onPress={() =>
navigate(Routes.MusicDetails, { projectId: item.id })
}
onPressMore={(posTop) => {
setSelectedProjectId(item.id);
setMenuTop(posTop);
setShowMenu((prev) => !prev || posTop !== menuTop);
}}
/>
)}
/>
{Array.isArray(userProjects) && userProjects.length > 0 ? (
<FlatList
data={userProjects}
contentContainerStyle={{
gap: 10,
}}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<MusicCard
title={item?.title || "Sans titre"}
subtitle={userData?.userName || "MusicLand"}
imageUri={item?.coverUrl || null}
projectId={item?.id}
likedBy={item?.likedBy || []}
onPress={() =>
navigate(Routes.MusicDetails, { projectId: item.id })
}
onPressMore={(posTop) => {
setSelectedProjectId(item.id);
setMenuTop(posTop);
setShowMenu((prev) => !prev || posTop !== menuTop);
}}
/>
)}
/>
) : (
<EmptyText text={"Aucune chanson pour le moment"} />
)}
</View>
)}
{selected === "Clips" && (
<View style={{ flex: 1 }}>
<EmptyText text={"Aucun clip pour le moment"} />
</View>
)}
<MoreMenu