From 9d1f7c7cdac1374ef7bf4252b0ab36cefd31d3e8 Mon Sep 17 00:00:00 2001 From: leon-morival Date: Tue, 2 Sep 2025 16:09:51 +0200 Subject: [PATCH] follow from playback --- src/screens/Playbacks.js | 146 +++++++++++++++++++++++++++++---------- 1 file changed, 111 insertions(+), 35 deletions(-) diff --git a/src/screens/Playbacks.js b/src/screens/Playbacks.js index 7bb90a5..7c6629c 100644 --- a/src/screens/Playbacks.js +++ b/src/screens/Playbacks.js @@ -1,7 +1,7 @@ import { useAudioPlayer } from "expo-audio"; import { BlurView } from "expo-blur"; import { VideoView, useVideoPlayer } from "expo-video"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Image, Platform, Pressable, Text, View } from "react-native"; import Carousel from "react-native-reanimated-carousel"; import { responsiveHeight } from "react-native-responsive-dimensions"; @@ -13,8 +13,8 @@ import { Palette } from "../styles"; import { FONT_FAMILY } from "../styles/Fonts"; import { size } from "../styles/Style"; -const PlaybackItem = ({ item, isActive }) => { - const { currentUID } = useUser() || {}; +const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => { + const { currentUID, followUser, unfollowUser } = useUser() || {}; const videoUrl = item?.playbackUrl || null; const audioSource = useMemo(() => { const fromSong = item?.songUrl ? { uri: item.songUrl } : null; @@ -29,6 +29,43 @@ const PlaybackItem = ({ item, isActive }) => { ); const [likesCount, setLikesCount] = useState(initialLikedBy.length); + // Fetch owner profile (cached) + const [owner, setOwner] = useState( + item?.userId && userCache?.current?.get(item.userId) + ? userCache.current.get(item.userId) + : null, + ); + useEffect(() => { + let cancelled = false; + const run = async () => { + try { + const uid = item?.userId; + if (!uid || !userCache) return; + const cached = userCache.current.get(uid); + if (cached) { + if (!cancelled) setOwner(cached); + return; + } + const user = await getUserByUid?.(uid); + if (!cancelled && user) { + userCache.current.set(uid, user); + setOwner(user); + } + } catch (e) {} + }; + run(); + return () => { + cancelled = true; + }; + }, [item?.userId, userCache, getUserByUid]); + + // Follow state derived from owner.followedBy + const [isFollowing, setIsFollowing] = useState(false); + useEffect(() => { + const list = Array.isArray(owner?.followedBy) ? owner.followedBy : []; + setIsFollowing(currentUID ? list.includes(currentUID) : false); + }, [owner?.followedBy, currentUID]); + useEffect(() => { const lb = Array.isArray(item?.likedBy) ? item.likedBy : []; setLikesCount(lb.length); @@ -135,41 +172,76 @@ const PlaybackItem = ({ item, isActive }) => { > - - - - - - Suivre - - + /> + ) : ( + + )} + {owner?.id && currentUID && owner.id !== currentUID && ( + { + try { + const next = !isFollowing; + setIsFollowing(next); + // Optimistic update of local owner.followedBy + setOwner((prev) => { + const fb = Array.isArray(prev?.followedBy) + ? prev.followedBy + : []; + const newFb = next + ? Array.from(new Set([...fb, currentUID])) + : fb.filter((x) => x !== currentUID); + return prev ? { ...prev, followedBy: newFb } : prev; + }); + if (next) await followUser?.(owner.id); + else await unfollowUser?.(owner.id); + } catch (e) { + // rollback on failure + setIsFollowing((v) => !v); + } + }} + > + + + {isFollowing ? "Suivi" : "Suivre"} + + + + )} { @@ -253,6 +325,8 @@ const PlaybackItem = ({ item, isActive }) => { const Playbacks = () => { const [activeIndex, setActiveIndex] = useState(0); + const userCache = useRef(new Map()); + const { getUserByUid } = useUser() || {}; const { data: playbacks = [], loadMore } = useDataFromRef({ ref: projectsRef.where("playbackUrl", "!=", null), simpleRef: false, @@ -285,6 +359,8 @@ const Playbacks = () => { )}