follow from playback
This commit is contained in:
+111
-35
@@ -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,41 +172,76 @@ const PlaybackItem = ({ item, isActive }) => {
|
|||||||
>
|
>
|
||||||
<View style={{ gap: 6, alignItems: "center" }}>
|
<View style={{ gap: 6, alignItems: "center" }}>
|
||||||
<Pressable>
|
<Pressable>
|
||||||
<Image
|
{owner?.profilePictureURL ? (
|
||||||
source={img.profile}
|
<Image
|
||||||
style={{
|
source={{ uri: owner.profilePictureURL }}
|
||||||
...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
|
|
||||||
style={{
|
style={{
|
||||||
fontSize: 13,
|
...size({ size: 45 }),
|
||||||
color: Palette.white,
|
borderRadius: 100,
|
||||||
fontFamily: FONT_FAMILY.InterMedium,
|
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
Suivre
|
) : (
|
||||||
</Text>
|
<Image
|
||||||
</BlurView>
|
source={img.profile}
|
||||||
|
style={{
|
||||||
|
...size({ size: 45 }),
|
||||||
|
borderRadius: 100,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</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
|
||||||
|
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>
|
</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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user