share playback

This commit is contained in:
2025-09-03 15:29:57 +02:00
parent 64fd988d2f
commit a516f260d1
4 changed files with 191 additions and 35 deletions
@@ -87,12 +87,19 @@ const CommentsBottomSheet = () => {
} }
}, [projectId]); }, [projectId]);
const { data: comments = [] } = useDataFromRef({ const {
data: comments = [],
setData: setComments,
loadMore,
loading: loadingComments,
} = useDataFromRef({
ref: commentsRef, ref: commentsRef,
simpleRef: false, simpleRef: false,
listener: true, listener: false,
condition: !!commentsRef, condition: !!commentsRef,
refreshArray: [projectId], refreshArray: [projectId],
usePagination: true,
batchSize: 20,
}); });
const onSend = async () => { const onSend = async () => {
@@ -100,7 +107,7 @@ const CommentsBottomSheet = () => {
if (!value || !projectId || !currentUID) return; if (!value || !projectId || !currentUID) return;
try { try {
setText(""); setText("");
await projectsRef const docRef = await projectsRef
.doc(projectId) .doc(projectId)
.collection("comments") .collection("comments")
.add({ .add({
@@ -110,6 +117,19 @@ const CommentsBottomSheet = () => {
text: value, text: value,
createdAt: serverTimestamp(), createdAt: serverTimestamp(),
}); });
// Optimistic: ajouter immédiatement le commentaire en tête de liste
const optimistic = {
id: docRef?.id || Math.random().toString(36).slice(2),
userId: currentUID,
userName: currentUserData?.userName || "",
profilePicture: currentUserData?.profilePictureURL || "",
text: value,
createdAt: new Date(),
};
setComments((prev = []) => [
optimistic,
...prev.filter((x) => x?.id !== optimistic.id),
]);
} catch { } catch {
// ignore // ignore
} }
@@ -127,6 +147,23 @@ const CommentsBottomSheet = () => {
[typing] [typing]
); );
const handleScroll = useCallback(
({ nativeEvent }) => {
try {
const { layoutMeasurement, contentOffset, contentSize } =
nativeEvent || {};
const paddingToBottom = 80;
if (
layoutMeasurement?.height + contentOffset?.y >=
(contentSize?.height || 0) - paddingToBottom
) {
loadMore?.();
}
} catch {}
},
[loadMore]
);
return ( return (
<BottomSheetModal <BottomSheetModal
ref={modalRef} ref={modalRef}
@@ -144,7 +181,12 @@ const CommentsBottomSheet = () => {
> >
<BlurView <BlurView
intensity={20} intensity={20}
style={{ flex: 1, backgroundColor: Palette.glass }} style={{
flex: 1,
backgroundColor: Palette.glass,
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
}}
experimentalBlurMethod={ experimentalBlurMethod={
Platform.OS !== "ios" ? "dimezisBlurView" : "none" Platform.OS !== "ios" ? "dimezisBlurView" : "none"
} }
@@ -164,6 +206,8 @@ const CommentsBottomSheet = () => {
<BottomSheetScrollView <BottomSheetScrollView
keyboardShouldPersistTaps="handled" keyboardShouldPersistTaps="handled"
onScroll={handleScroll}
scrollEventThrottle={100}
contentContainerStyle={{ contentContainerStyle={{
paddingHorizontal: 14, paddingHorizontal: 14,
paddingBottom: (insets?.bottom || 0) + 12, paddingBottom: (insets?.bottom || 0) + 12,
+120
View File
@@ -0,0 +1,120 @@
import React, { useMemo } from "react";
import { Linking, Pressable, Text, View } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import { shareLink } from "../../helpers";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import AppActionSheet from "../AppActionSheet";
const ShareModal = ({ payload }) => {
const {
title = "Partager",
message = "Choisis une application pour partager",
text = "Découvre ce playback sur MusicLand",
url = "",
} = payload || {};
const onClose = () => {
SheetManager.hide("Share");
};
const shareText = useMemo(() => {
return [text, url].filter(Boolean).join(" ");
}, [text, url]);
const openScheme = async () => {
try {
const supported = await Linking.canOpenURL(url);
if (supported) {
await Linking.openURL(url);
} else {
// fallback vers partage natif
shareLink({ url, title: "Partager", message: shareText });
}
} catch (e) {
shareLink({ url, title: "Partager", message: shareText });
} finally {
onClose();
}
};
return (
<AppActionSheet id="Share">
<View style={{ gap: 24 }}>
<View style={{ gap: 6 }}>
<Text
style={{
fontSize: 20,
color: Palette.white,
fontFamily: FONT_FAMILY.HelveticaNeueMedium,
textAlign: "center",
}}
>
{title}
</Text>
<Text
style={{
fontSize: 14,
color: Palette.white,
opacity: 0.8,
fontFamily: FONT_FAMILY.HelveticaNeueRegular,
textAlign: "center",
}}
>
{message}
</Text>
</View>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
gap: 16,
paddingHorizontal: 12,
}}
>
<Pressable
onPress={() => {
openScheme();
}}
style={{ alignItems: "center", gap: 8, flex: 1 }}
>
<View
style={{
width: 56,
height: 56,
borderRadius: 28,
borderWidth: 1,
borderColor: "#FFFFFF60",
alignItems: "center",
justifyContent: "center",
}}
>
<Text
style={{
color: Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
fontSize: 12,
}}
>
partager
</Text>
</View>
<Text
style={{
color: Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
fontSize: 12,
}}
>
partager
</Text>
</Pressable>
</View>
</View>
</AppActionSheet>
);
};
export default ShareModal;
+2 -28
View File
@@ -1,12 +1,9 @@
import numbro from "numbro";
import { Clipboard } from "react-native-web";
import moment from "moment"; import moment from "moment";
import numbro from "numbro";
import alert from "../components/Alert.js"; import { Linking } from "react-native";
import { isTauri, isWeb } from "../hooks/useLayoutType"; import { isTauri, isWeb } from "../hooks/useLayoutType";
import Palette from "../styles/Palette.js"; import Palette from "../styles/Palette.js";
import { Linking, Share } from "react-native";
export const capitalize = (string) => { export const capitalize = (string) => {
return string.charAt(0).toUpperCase() + string.slice(1); return string.charAt(0).toUpperCase() + string.slice(1);
@@ -196,26 +193,3 @@ export const openLinkInBrowser = ({ url }) => {
console.error(error); console.error(error);
} }
}; };
export const shareLink = ({ url, title = "", message = "" }) => {
try {
if (isWeb) {
if (!Clipboard.isAvailable) {
throw new Error("Clipboard not available");
}
Clipboard.setString(url);
alert("Lien copié", "Le lien a été copié dans votre presse-papiers.");
} else {
Share.share({
message,
url,
title,
});
}
} catch (error) {
console.log(error);
alert("Partage non disponible", "Il y a eu une erreur lors du partage.");
}
};
+21 -3
View File
@@ -9,7 +9,7 @@ import React, {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import { Image, Platform, Pressable, Text, View } from "react-native"; import { Image, Platform, Pressable, Share, 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";
import { icons, img } from "../assets"; import { icons, img } from "../assets";
@@ -105,7 +105,6 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
setLikesCount(lb.length); setLikesCount(lb.length);
setIsLiked(currentUID ? lb.includes(currentUID) : false); setIsLiked(currentUID ? lb.includes(currentUID) : false);
}, [item?.likedBy, currentUID]); }, [item?.likedBy, currentUID]);
console.log("has external audio : ", hasExternalAudio);
const audioPlayer = useAudioPlayer(audioSource || undefined); const audioPlayer = useAudioPlayer(audioSource || undefined);
const videoPlayer = useVideoPlayer(videoUrl || null, (p) => { const videoPlayer = useVideoPlayer(videoUrl || null, (p) => {
@@ -193,6 +192,7 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
return () => clearInterval(id); return () => clearInterval(id);
}, [isActive, hasExternalAudio, audioPlayer, videoPlayer]); }, [isActive, hasExternalAudio, audioPlayer, videoPlayer]);
// partage: géré via l'API native Share directement dans l'UI
return ( return (
<View <View
style={{ style={{
@@ -360,7 +360,25 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
> >
<Image source={icons.chatBubble} style={size({ size: 26 })} /> <Image source={icons.chatBubble} style={size({ size: 26 })} />
</Pressable> </Pressable>
<Pressable> <Pressable
onPress={async () => {
try {
const url = item?.playbackUrl || "";
const title = item?.title || "Partager";
const base = item?.title
? `Découvre « ${item.title} » sur MusicLand`
: `Découvre ce playback sur MusicLand`;
const message = url ? `${base}\n${url}` : base;
await Share.share(
Platform.select({
ios: url ? { url, message, title } : { message, title },
default: { message, title },
})
);
} catch (e) {}
}}
>
<Image source={icons.share} style={size({ size: 26 })} /> <Image source={icons.share} style={size({ size: 26 })} />
</Pressable> </Pressable>
</View> </View>