follow from playback

This commit is contained in:
2025-09-02 16:09:51 +02:00
parent 6383b84ad9
commit 9d1f7c7cda
+81 -5
View File
@@ -1,7 +1,7 @@
import { useAudioPlayer } from "expo-audio"; import { useAudioPlayer } from "expo-audio";
import { BlurView } from "expo-blur"; import { BlurView } from "expo-blur";
import { VideoView, useVideoPlayer } from "expo-video"; 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 { Image, Platform, Pressable, Text, View } from "react-native";
import Carousel from "react-native-reanimated-carousel"; import Carousel from "react-native-reanimated-carousel";
import { responsiveHeight } from "react-native-responsive-dimensions"; import { responsiveHeight } from "react-native-responsive-dimensions";
@@ -13,8 +13,8 @@ import { Palette } from "../styles";
import { FONT_FAMILY } from "../styles/Fonts"; import { FONT_FAMILY } from "../styles/Fonts";
import { size } from "../styles/Style"; import { size } from "../styles/Style";
const PlaybackItem = ({ item, isActive }) => { const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
const { currentUID } = useUser() || {}; const { currentUID, followUser, unfollowUser } = useUser() || {};
const videoUrl = item?.playbackUrl || null; const videoUrl = item?.playbackUrl || null;
const audioSource = useMemo(() => { const audioSource = useMemo(() => {
const fromSong = item?.songUrl ? { uri: item.songUrl } : null; const fromSong = item?.songUrl ? { uri: item.songUrl } : null;
@@ -29,6 +29,43 @@ const PlaybackItem = ({ item, isActive }) => {
); );
const [likesCount, setLikesCount] = useState(initialLikedBy.length); 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(() => { useEffect(() => {
const lb = Array.isArray(item?.likedBy) ? item.likedBy : []; const lb = Array.isArray(item?.likedBy) ? item.likedBy : [];
setLikesCount(lb.length); setLikesCount(lb.length);
@@ -135,6 +172,15 @@ const PlaybackItem = ({ item, isActive }) => {
> >
<View style={{ gap: 6, alignItems: "center" }}> <View style={{ gap: 6, alignItems: "center" }}>
<Pressable> <Pressable>
{owner?.profilePictureURL ? (
<Image
source={{ uri: owner.profilePictureURL }}
style={{
...size({ size: 45 }),
borderRadius: 100,
}}
/>
) : (
<Image <Image
source={img.profile} source={img.profile}
style={{ style={{
@@ -142,8 +188,32 @@ const PlaybackItem = ({ item, isActive }) => {
borderRadius: 100, borderRadius: 100,
}} }}
/> />
)}
</Pressable> </Pressable>
<Pressable> {owner?.id && currentUID && owner.id !== currentUID && (
<Pressable
onPress={async () => {
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);
}
}}
>
<BlurView <BlurView
tint="dark" tint="dark"
intensity={20} intensity={20}
@@ -154,6 +224,7 @@ const PlaybackItem = ({ item, isActive }) => {
borderWidth: 1, borderWidth: 1,
borderColor: Palette.white, borderColor: Palette.white,
overflow: "hidden", overflow: "hidden",
backgroundColor: isFollowing ? "#FFFFFF1A" : undefined,
}} }}
experimentalBlurMethod={ experimentalBlurMethod={
Platform.OS !== "ios" ? "dimezisBlurView" : "none" Platform.OS !== "ios" ? "dimezisBlurView" : "none"
@@ -166,10 +237,11 @@ const PlaybackItem = ({ item, isActive }) => {
fontFamily: FONT_FAMILY.InterMedium, fontFamily: FONT_FAMILY.InterMedium,
}} }}
> >
Suivre {isFollowing ? "Suivi" : "Suivre"}
</Text> </Text>
</BlurView> </BlurView>
</Pressable> </Pressable>
)}
</View> </View>
<Pressable <Pressable
onPress={async () => { onPress={async () => {
@@ -253,6 +325,8 @@ const PlaybackItem = ({ item, isActive }) => {
const Playbacks = () => { const Playbacks = () => {
const [activeIndex, setActiveIndex] = useState(0); const [activeIndex, setActiveIndex] = useState(0);
const userCache = useRef(new Map());
const { getUserByUid } = useUser() || {};
const { data: playbacks = [], loadMore } = useDataFromRef({ const { data: playbacks = [], loadMore } = useDataFromRef({
ref: projectsRef.where("playbackUrl", "!=", null), ref: projectsRef.where("playbackUrl", "!=", null),
simpleRef: false, simpleRef: false,
@@ -285,6 +359,8 @@ const Playbacks = () => {
<PlaybackItem <PlaybackItem
key={item?.id || index} key={item?.id || index}
item={item} item={item}
userCache={userCache}
getUserByUid={getUserByUid}
isActive={index === activeIndex} isActive={index === activeIndex}
/> />
)} )}