follow from playback
This commit is contained in:
+111
-35
@@ -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 }) => {
|
||||
>
|
||||
<View style={{ gap: 6, alignItems: "center" }}>
|
||||
<Pressable>
|
||||
<Image
|
||||
source={img.profile}
|
||||
style={{
|
||||
...size({ size: 45 }),
|
||||
borderRadius: 100,
|
||||
}}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable>
|
||||
<BlurView
|
||||
tint="dark"
|
||||
intensity={20}
|
||||
style={{
|
||||
paddingVertical: 6,
|
||||
paddingHorizontal: 12,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: Palette.white,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
experimentalBlurMethod={
|
||||
Platform.OS !== "ios" ? "dimezisBlurView" : "none"
|
||||
}
|
||||
>
|
||||
<Text
|
||||
{owner?.profilePictureURL ? (
|
||||
<Image
|
||||
source={{ uri: owner.profilePictureURL }}
|
||||
style={{
|
||||
fontSize: 13,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterMedium,
|
||||
...size({ size: 45 }),
|
||||
borderRadius: 100,
|
||||
}}
|
||||
>
|
||||
Suivre
|
||||
</Text>
|
||||
</BlurView>
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
source={img.profile}
|
||||
style={{
|
||||
...size({ size: 45 }),
|
||||
borderRadius: 100,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</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
|
||||
tint="dark"
|
||||
intensity={20}
|
||||
style={{
|
||||
paddingVertical: 6,
|
||||
paddingHorizontal: 12,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: Palette.white,
|
||||
overflow: "hidden",
|
||||
backgroundColor: isFollowing ? "#FFFFFF1A" : undefined,
|
||||
}}
|
||||
experimentalBlurMethod={
|
||||
Platform.OS !== "ios" ? "dimezisBlurView" : "none"
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 13,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterMedium,
|
||||
}}
|
||||
>
|
||||
{isFollowing ? "Suivi" : "Suivre"}
|
||||
</Text>
|
||||
</BlurView>
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
<Pressable
|
||||
onPress={async () => {
|
||||
@@ -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 = () => {
|
||||
<PlaybackItem
|
||||
key={item?.id || index}
|
||||
item={item}
|
||||
userCache={userCache}
|
||||
getUserByUid={getUserByUid}
|
||||
isActive={index === activeIndex}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user