import { useRoute } from "@react-navigation/native"; import { BlurView } from "expo-blur"; import * as ImagePicker from "expo-image-picker"; import React, { useEffect, useMemo, useState } from "react"; import { ActivityIndicator, FlatList, Image, Pressable, ScrollView, StyleSheet, Text, View, } from "react-native"; import { SheetManager } from "react-native-actions-sheet"; import { Feather } from "@expo/vector-icons"; import { responsiveHeight } from "react-native-responsive-dimensions"; import { useGlobal } from "reactn"; import { background, icons } from "../../assets"; import BorderGradient from "../../components/BorderGradient/BorderGradient"; import GradientButton from "../../components/GradientButton"; import MoreMenu from "../../components/MoreMenu"; import PressableScale from "../../components/PressableScale"; import ProfilePicture from "../../components/ProfilePicture"; import firebase, { projectsRef, serverTimestamp, usersRef, } from "../../config/firebase"; import loaderMessages from "../../config/loaderMessages"; import { uploadFileToFirebase } from "../../helpers/uploadToFirebase"; import useDataFromRef from "../../hooks/useDataFromRef"; import useLayoutType from "../../hooks/useLayoutType"; import useNavigateToMusicDetails from "../../hooks/useNavigateToMusicDetails"; import Page from "../../layouts/Page"; import { Routes } from "../../navigation"; import { navigate, push } from "../../navigation/NavigationService"; import { useUser } from "../../providers/UserDataProvider"; import { Palette } from "../../styles"; import { FONT_FAMILY } from "../../styles/Fonts"; import Style, { gutters, size } from "../../styles/Style"; import { getArtistDisplayName } from "../../utils/artistName"; import { getProjectLikes, LIKE_TARGET } from "../../utils/likes"; import MusicCard from "../Library/components/MusicCard"; const Profile = () => { const { params } = useRoute(); const [selected, setSelected] = useState("Chansons"); const { isWeb } = useLayoutType(); const { userProjects: selfProjects, userPlaybacks: selfPlaybacks, followingCount, currentUserData, getUserByUid, followUser, unfollowUser, } = useUser(); const [currentUID] = useGlobal("currentUID"); const targetUserId = params?.userId || currentUID || null; const isSelf = !!currentUID && targetUserId === currentUID; const [, setTooltip] = useGlobal("_tooltip"); const [userData, setUserData] = useState(null); const [isFollowing, setIsFollowing] = useState(false); const [followers, setFollowers] = useState(0); const [following, setFollowing] = useState(0); const [menuPosition, setMenuPosition] = useState(null); const [showMenu, setShowMenu] = useState(false); const [selectedProjectId, setSelectedProjectId] = useState(null); const [updatingPhoto, setUpdatingPhoto] = useState(false); const [webUploadMessage, setWebUploadMessage] = useState(""); const selfDisplayName = useMemo( () => getArtistDisplayName(currentUserData, "MusicLand"), [currentUserData] ); const targetDisplayName = useMemo( () => getArtistDisplayName(userData, "MusicLand"), [userData] ); const profileTitle = useMemo( () => getArtistDisplayName(isSelf ? currentUserData : userData, "Profil"), [currentUserData, isSelf, userData] ); const navigateToMusicDetails = useNavigateToMusicDetails(); const onPressMenu = (item) => { setSelected(item); }; const handleOpenSettings = () => { navigate(Routes.Settings); }; // Chargement des données utilisateur si on consulte un autre profil useEffect(() => { const fetch = async () => { if (!targetUserId) return; if (isSelf) { setUserData(currentUserData || null); setFollowers( Array.isArray(currentUserData?.followedBy) ? currentUserData.followedBy.length : 0 ); return; } const data = await getUserByUid(targetUserId); setUserData(data || null); const list = Array.isArray(data?.followedBy) ? data.followedBy : []; setFollowers(list.length); setIsFollowing(currentUID ? list.includes(currentUID) : false); }; fetch(); }, [targetUserId, isSelf, currentUserData?.followedBy?.length || 0]); // Live following count pour l'utilisateur consulté (autre que soi) const followingQueryRef = useMemo(() => { try { return targetUserId ? usersRef.where("followedBy", "array-contains", targetUserId) : null; } catch (e) { return null; } }, [targetUserId]); useDataFromRef({ ref: followingQueryRef, simpleRef: false, listener: true, condition: !!followingQueryRef && !isSelf, refreshArray: [targetUserId], onUpdate: (list) => setFollowing(Array.isArray(list) ? list.length : 0), }); const handleFollowUser = async () => { if (!currentUID || !targetUserId || isSelf) return; if (isFollowing) { await unfollowUser(targetUserId); setIsFollowing(false); setFollowers((v) => Math.max(0, (v || 0) - 1)); } else { await followUser(targetUserId); setIsFollowing(true); setFollowers((v) => (v || 0) + 1); } }; const onChangeProfilePicture = async (message = "") => { if (!isSelf || updatingPhoto) return; try { setUpdatingPhoto(true); setWebUploadMessage(isWeb && message ? message : ""); const uid = currentUID; if (!uid) return; const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: true, aspect: [4, 4], quality: 1, }); const pickedUri = result?.assets?.[0]?.uri || null; if (!pickedUri) return; const { resultURI = null } = await uploadFileToFirebase({ uri: pickedUri, path: `users/${uid}/profilePicture.png`, }); if (!resultURI) { throw new Error("Téléversement de l'image impossible"); } await usersRef.doc(uid).set( { profilePictureURL: resultURI, updatedAt: serverTimestamp(), }, { merge: true } ); const authUser = firebase.auth().currentUser; if (authUser) { await authUser.updateProfile({ photoURL: resultURI }); } setTooltip({ text: "Photo de profil mise à jour", type: "success" }); } catch (e) { setTooltip({ text: e?.message || "Erreur changement photo de profil", type: "error", }); } finally { setUpdatingPhoto(false); setWebUploadMessage(""); } }; // Composant factorisé pour afficher la liste de musiques const MusicListSection = ({ data, emptyText, likeTarget = LIKE_TARGET.SONG, }) => ( {Array.isArray(data) && data.length > 0 ? ( item.id} renderItem={({ item }) => ( navigateToMusicDetails({ projectId: item.id, songUrl: item?.songUrl, project: item, queueProjects: data, queueSource: { id: isSelf ? "profile-self" : "profile-other", type: "collection", name: isSelf ? "Mes musiques" : targetDisplayName || "Profil", }, }) } onPressMore={(posTop) => { setSelectedProjectId(item.id); setMenuPosition(posTop); setShowMenu( (prev) => !prev || posTop?.top !== (menuPosition?.top ?? null) ); }} /> )} /> ) : ( )} setShowMenu(false)} inPlaylist={false} projectId={selectedProjectId} extraItems={ isSelf ? [ { label: "Supprimer", onPress: () => SheetManager.show("Delete", { payload: { title: "Supprimer le projet", message: "Cette action supprimera définitivement ce projet.", onConfirm: async () => { try { if (!selectedProjectId) return; await projectsRef.doc(selectedProjectId).delete(); setTooltip({ type: "success", text: "Projet supprimé", }); } catch (e) { setTooltip({ type: "error", text: e?.message || "Suppression impossible", }); } }, }, }), }, ] : [] } /> ); const EmptyText = ({ text }) => ( {text} ); // Données projets/profils selon si c'est soi ou un autre utilisateur const projectsQueryRef = useMemo(() => { try { if (!targetUserId || isSelf) return null; return projectsRef .where("userId", "==", targetUserId) .orderBy("updatedAt", "desc"); } catch (e) { return null; } }, [targetUserId, isSelf]); const { data: otherUserProjects = [] } = useDataFromRef({ ref: projectsQueryRef, simpleRef: false, listener: true, condition: !!projectsQueryRef, refreshArray: [targetUserId], }); const displayedProjects = isSelf ? selfProjects : otherUserProjects; const displayedPlaybacksSource = isSelf ? selfPlaybacks : otherUserProjects; const displayedPlaybacks = Array.isArray(displayedPlaybacksSource) ? displayedPlaybacksSource.filter((project) => project?.playbackUrl) : []; return ( !isWeb && isSelf ? ( ) : null } > {isWeb && isSelf && ( )} {isSelf && ( onChangeProfilePicture( loaderMessages.profilePhotoUploadWeb ) } disabled={updatingPhoto} hitSlop={10} > {updatingPhoto ? ( ) : ( )} )} {isWeb && updatingPhoto && webUploadMessage ? ( {webUploadMessage} ) : null} {profileTitle} {Array.isArray(displayedProjects) ? displayedProjects.length : 0} projets push(Routes.Follows, { selected: "Abonnés", ...(isSelf ? {} : { userId: targetUserId }), }) } > {isSelf ? currentUserData?.followedBy?.length || 0 : followers} abonnés push(Routes.Follows, { selected: "Abonnements", ...(isSelf ? {} : { userId: targetUserId }), }) } > {isSelf ? followingCount : following} abonnements {isSelf ? ( push(Routes.Payments)} containerStyle={{ marginTop: 18, width: "80%", alignSelf: "center", }} gradientStyle={{ width: "100%" }} /> ) : null} {!isSelf && ( {isFollowing ? "Suivi(e)" : "Suivre"} )} {/*{["Chansons", "Playbacks", "Clips"].map((item, index) => (*/} {["Chansons", "Playbacks"].map((item, index) => ( onPressMenu(item)} style={{ flex: 1 }} > {item} ))} {selected === "Playbacks" && ( )} {selected === "Chansons" && ( )} {selected === "Clips" && ( )} ); }; export default Profile; const styles = StyleSheet.create({ value: { fontSize: 16, color: Palette.white, fontFamily: FONT_FAMILY.InterRegular, }, label: { fontSize: 12, color: Palette.white, fontFamily: FONT_FAMILY.InterRegular, }, menu: { fontSize: 12, color: Palette.white, fontFamily: FONT_FAMILY.InterRegular, }, borderGradient: { borderWidth: 1, borderRadius: 10, height: 33, position: "absolute", zIndex: 1, width: "100%", }, blurContainer: { height: 33, zIndex: -1, borderRadius: 10, overflow: "hidden", backgroundColor: Palette.glass, }, blurView: { width: "100%", height: "100%", paddingHorizontal: 10, ...Style.containerCenter, }, avatarWrapper: { position: "relative", ...size({ size: 108 }), alignItems: "center", justifyContent: "center", }, editAvatarButton: { position: "absolute", bottom: 4, right: 4, }, editAvatarBackground: { width: 36, height: 36, borderRadius: 18, backgroundColor: Palette.transparentBlack, borderWidth: 1, borderColor: Palette.white, ...Style.containerCenter, }, editAvatarIcon: { ...size({ size: 16 }), tintColor: Palette.white, }, webLoaderText: { marginTop: 8, color: Palette.white, fontSize: 12, fontFamily: FONT_FAMILY.InterRegular, textAlign: "center", opacity: 0.85, }, });