buid and fix bottom sheet and react native compressor

This commit is contained in:
Thomas Demirdjian
2025-10-31 14:28:10 +01:00
parent ce285881ca
commit 6f330c1df8
11 changed files with 387 additions and 180 deletions
+17 -15
View File
@@ -49,7 +49,10 @@ const AppActionSheet = ({
return (
<ActionSheet
id={id}
containerStyle={{ backgroundColor: Palette.tran }}
containerStyle={[
{ backgroundColor: Palette.tran },
isWeb && { alignItems: "center" },
]}
gestureEnabled
indicatorStyle={{
top: 20,
@@ -61,18 +64,18 @@ const AppActionSheet = ({
>
<BlurView
intensity={Platform.OS === "ios" || isWeb ? 20 : 10}
style={{
paddingTop: 36,
paddingHorizontal: 14,
paddingBottom: gutters + (insets?.bottom || 0),
backgroundColor: Palette.glass,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
overflow: "hidden",
}}
// experimentalBlurMethod={
// Platform.OS !== "ios" ? "dimezisBlurView" : "none"
// }
style={[
{
paddingTop: 36,
paddingHorizontal: 14,
paddingBottom: gutters + (insets?.bottom || 0),
backgroundColor: Palette.glass,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
overflow: "hidden",
},
isWeb && { width: 700 },
]}
>
{children}
</BlurView>
@@ -99,8 +102,7 @@ const styles = StyleSheet.create({
padding: 24,
},
webModalCard: {
width: 480,
maxWidth: "90%",
maxWidth: 500,
borderRadius: 28,
overflow: "hidden",
backgroundColor: Palette.glass,
+130
View File
@@ -0,0 +1,130 @@
import React, { useCallback } from "react";
import { Pressable, Text, View } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import AppActionSheet from "../AppActionSheet";
const OptionButton = ({ label, onPress, disabled = false }) => (
<Pressable
onPress={onPress}
disabled={disabled}
style={({ pressed }) => ({
paddingVertical: 14,
paddingHorizontal: 18,
borderRadius: 16,
backgroundColor: "rgba(255,255,255,0.08)",
opacity: disabled ? 0.5 : 1,
transform: [{ scale: pressed ? 0.97 : 1 }],
})}
>
<Text
style={{
fontSize: 16,
color: Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
textAlign: "center",
}}
>
{label}
</Text>
</Pressable>
);
const MusicOptionsModal = ({ payload }) => {
const {
onReport,
onAddToPlaylist,
onDownload,
downloadDisabled = false,
projectId = null,
ownerId = null,
title = null,
} = payload || {};
const trigger = useCallback((action) => {
Promise.resolve(SheetManager.hide("MusicOptions"))
.catch(() => {})
.finally(() => {
if (typeof action === "function") {
setTimeout(action, 220);
}
});
}, []);
const handleReport = useCallback(() => {
const run = () => {
if (typeof onReport === "function") {
onReport();
return;
}
if (projectId) {
SheetManager.show("Report", {
payload: {
targetType: "music",
projectId,
title,
ownerId,
},
});
}
};
trigger(run);
}, [onReport, ownerId, projectId, title, trigger]);
const handleAddToPlaylist = useCallback(() => {
const run = () => {
if (typeof onAddToPlaylist === "function") {
onAddToPlaylist();
return;
}
if (projectId) {
SheetManager.show("Playlist", { payload: { projectId } });
} else {
SheetManager.show("Playlist");
}
};
trigger(run);
}, [onAddToPlaylist, projectId, trigger]);
const handleDownload = useCallback(() => {
if (downloadDisabled) return;
const run = () => {
if (typeof onDownload === "function") {
onDownload();
}
};
trigger(run);
}, [downloadDisabled, onDownload, trigger]);
return (
<AppActionSheet id="MusicOptions">
<View style={{ gap: 20 }}>
<Text
style={{
fontSize: 20,
color: Palette.white,
fontFamily: FONT_FAMILY.HelveticaNeueMedium,
textAlign: "center",
}}
>
Options
</Text>
<View style={{ gap: 12 }}>
<OptionButton label="Signaler" onPress={handleReport} />
<OptionButton
label="Ajouter à une playlist"
onPress={handleAddToPlaylist}
/>
<OptionButton
label="Télécharger"
onPress={handleDownload}
disabled={downloadDisabled}
/>
</View>
</View>
</AppActionSheet>
);
};
export default MusicOptionsModal;
+65 -55
View File
@@ -8,6 +8,7 @@ import {
StyleSheet,
Text,
View,
Linking,
} from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import {
@@ -17,7 +18,6 @@ import {
import { useGlobal } from "reactn";
import { background, icons } from "../../assets";
import PressableScale from "../../components/PressableScale";
import ShareBtn from "../../components/ShareBtn/ShareBtn";
import Slider from "../../components/Slider";
import {
arrayRemove,
@@ -38,11 +38,6 @@ import {
getSegmentMeta,
normalizeStructureType,
} from "../../utils/songStructure";
import {
createMusicSharePayload,
openShareSheet,
} from "../../utils/shareSheet";
import { Feather } from "@expo/vector-icons";
// 20 secondes
const timeBeforeIncrement = 20000;
@@ -113,21 +108,6 @@ const MusicDetails = ({ route }) => {
};
}, [trackId, songUrl, title, artist, coverUrl, projectId]);
const sharePayload = useMemo(
() =>
createMusicSharePayload({
projectId,
title,
artist,
}),
[projectId, title, artist]
);
const handleShare = useCallback(() => {
if (!sharePayload) return;
openShareSheet(sharePayload);
}, [sharePayload]);
const handleReport = useCallback(() => {
if (!projectId) return;
SheetManager.show("Report", {
@@ -140,6 +120,46 @@ const MusicDetails = ({ route }) => {
});
}, [owner?.id, project?.userId, projectId, title]);
const handleAddToPlaylist = useCallback(() => {
if (projectId) {
SheetManager.show("Playlist", { payload: { projectId } });
} else {
SheetManager.show("Playlist");
}
}, [projectId]);
const handleDownload = useCallback(async () => {
if (!songUrl) return;
try {
await Linking.openURL(songUrl);
} catch (error) {
console.log("MusicDetails download error", error?.message);
}
}, [songUrl]);
const handleOpenOptions = useCallback(() => {
SheetManager.show("MusicOptions", {
payload: {
projectId,
title,
ownerId: project?.userId || owner?.id || null,
onReport: handleReport,
onAddToPlaylist: handleAddToPlaylist,
onDownload: handleDownload,
downloadDisabled: !songUrl,
},
});
}, [
handleAddToPlaylist,
handleDownload,
handleReport,
owner?.id,
project?.userId,
projectId,
songUrl,
title,
]);
const {
isCurrent: isCurrentTrack,
isPlaying: isTrackPlaying,
@@ -709,40 +729,30 @@ const MusicDetails = ({ route }) => {
if (!projectId || !currentUID) return;
const next = !fav;
setFav(next);
try {
await projectsRef.doc(projectId).update({
likedBy: next
? arrayUnion(currentUID)
: arrayRemove(currentUID),
});
} catch (e) {
setFav(!next);
}
}}
>
<RNImage
source={fav ? icons.heart : icons.heartOutline}
style={{ ...size({ size: 24 }) }}
resizeMode="contain"
/>
</PressableScale>
<PressableScale onPress={handleReport}>
<Feather name="flag" size={22} color={Palette.white} style={{ marginHorizontal: 2 }} />
</PressableScale>
{!!sharePayload && (
<ShareBtn style={{ borderRadius: 18 }} onPress={handleShare} />
)}
<Pressable
onPress={() =>
SheetManager.show("Playlist", { payload: { projectId } })
}
>
<RNImage
source={icons.more}
style={{ ...size({ size: 24 }) }}
resizeMode="contain"
/>
</Pressable>
try {
await projectsRef.doc(projectId).update({
likedBy: next
? arrayUnion(currentUID)
: arrayRemove(currentUID),
});
} catch (e) {
setFav(!next);
}
}}
>
<RNImage
source={fav ? icons.heart : icons.heartOutline}
style={{ ...size({ size: 24 }) }}
resizeMode="contain"
/>
</PressableScale>
<PressableScale onPress={handleOpenOptions}>
<RNImage
source={icons.more}
style={{ ...size({ size: 24 }) }}
resizeMode="contain"
/>
</PressableScale>
</View>
</View>
</View>
+34 -82
View File
@@ -24,7 +24,6 @@ import { useGlobal } from "reactn";
import { background, icons } from "../../assets";
import PressableScale from "../../components/PressableScale";
import Slider from "../../components/Slider";
import Svg, { Circle } from "react-native-svg";
import {
arrayRemove,
arrayUnion,
@@ -50,7 +49,6 @@ import {
createMusicSharePayload,
openShareSheet,
} from "../../utils/shareSheet";
import { Feather } from "@expo/vector-icons";
// 20 secondes
const timeBeforeIncrement = 20000;
@@ -227,6 +225,38 @@ const MusicDetails = ({ route }) => {
});
}, [owner?.id, project?.userId, projectId, title]);
const handleAddToPlaylist = useCallback(() => {
if (projectId) {
SheetManager.show("Playlist", { payload: { projectId } });
} else {
SheetManager.show("Playlist");
}
}, [projectId]);
const handleOpenOptions = useCallback(() => {
SheetManager.show("MusicOptions", {
payload: {
projectId,
title,
ownerId: project?.userId || owner?.id || null,
onReport: handleReport,
onAddToPlaylist: handleAddToPlaylist,
onDownload: handleDownload,
downloadDisabled: isDownloading || !songUrl,
},
});
}, [
handleAddToPlaylist,
handleDownload,
handleReport,
isDownloading,
owner?.id,
project?.userId,
projectId,
songUrl,
title,
]);
const {
isCurrent: isCurrentTrack,
isPlaying: isTrackPlaying,
@@ -913,56 +943,13 @@ const MusicDetails = ({ route }) => {
resizeMode="contain"
/>
</Pressable>
<PressableScale onPress={handleReport}>
<Feather
name="flag"
size={22}
color={Palette.white}
style={{ marginHorizontal: 2 }}
/>
</PressableScale>
<View
style={{
width: 36,
height: 36,
alignItems: "center",
justifyContent: "center",
}}
>
{isDownloading ? (
<DownloadProgressRing
progress={downloadProgress}
size={36}
strokeWidth={3}
/>
) : null}
<Pressable
onPress={handleDownload}
disabled={isDownloading || !songUrl}
style={{
width: 36,
height: 36,
borderRadius: 18,
alignItems: "center",
justifyContent: "center",
backgroundColor: "rgba(255,255,255,0.08)",
opacity: isDownloading || !songUrl ? 0.6 : 1,
}}
>
<Feather name="download" size={18} color={Palette.white} />
</Pressable>
</View>
<Pressable
onPress={() =>
SheetManager.show("Playlist", { payload: { projectId } })
}
>
<PressableScale onPress={handleOpenOptions}>
<RNImage
source={icons.more}
style={{ ...size({ size: 24 }) }}
resizeMode="contain"
/>
</Pressable>
</PressableScale>
</View>
</View>
</View>
@@ -1166,38 +1153,3 @@ const styles = StyleSheet.create({
marginBottom: 6,
},
});
const DownloadProgressRing = ({ progress = 0, size = 36, strokeWidth = 3 }) => {
const radius = size / 2 - strokeWidth / 2;
const circumference = 2 * Math.PI * radius;
const clamped = Math.max(0, Math.min(1, progress || 0));
const offset = circumference * (1 - clamped);
return (
<Svg
width={size}
height={size}
style={{ position: "absolute", top: 0, left: 0 }}
>
<Circle
cx={size / 2}
cy={size / 2}
r={radius}
stroke="rgba(255, 255, 255, 0.15)"
strokeWidth={strokeWidth}
fill="transparent"
/>
<Circle
cx={size / 2}
cy={size / 2}
r={radius}
stroke={Palette.primary}
strokeWidth={strokeWidth}
strokeDasharray={`${circumference} ${circumference}`}
strokeDashoffset={offset}
strokeLinecap="round"
fill="transparent"
/>
</Svg>
);
};
+2
View File
@@ -8,6 +8,7 @@ import PlaylistModal from "../components/modal/PlaylistModal";
import PlaybackPickerModal from "../components/modal/PlaybackPickerModal";
import ShareModal from "../components/modal/ShareModal";
import ReportModal from "../components/modal/ReportModal";
import MusicOptionsModal from "../components/modal/MusicOptionsModal";
registerSheet("Delete", DeleteModal);
registerSheet("ProfileSettings", ProfileSettingsModal);
@@ -18,5 +19,6 @@ registerSheet("Playlist", PlaylistModal);
registerSheet("PlaybackPicker", PlaybackPickerModal);
registerSheet("Share", ShareModal);
registerSheet("Report", ReportModal);
registerSheet("MusicOptions", MusicOptionsModal);
export {};