share playback
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user