fix tickets add reort mail and design fixes

This commit is contained in:
Thomas Demirdjian
2025-10-29 15:36:12 +01:00
parent d1be3c19a2
commit ce285881ca
35 changed files with 1069 additions and 202 deletions
+226
View File
@@ -24,6 +24,7 @@ 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,
@@ -45,6 +46,11 @@ import {
normalizeStructureType,
segmentRequiresLyrics,
} from "../../utils/songStructure";
import {
createMusicSharePayload,
openShareSheet,
} from "../../utils/shareSheet";
import { Feather } from "@expo/vector-icons";
// 20 secondes
const timeBeforeIncrement = 20000;
@@ -52,14 +58,17 @@ const timeBeforeIncrement = 20000;
const MusicDetails = ({ route }) => {
const { params } = route || {};
const action = params?.action;
const autoPlayRequested = params?.autoPlay;
const projectId = params?.projectId || null;
const [fav, setFav] = useState(false);
const [currentUID] = useGlobal("currentUID");
const [, setTooltip] = useGlobal("_tooltip");
const wasPlayingBeforeSeek = React.useRef(false);
const lastSeekTargetMs = React.useRef(null);
const listenedMsRef = React.useRef(0);
const incrementDoneRef = React.useRef(false);
const timerRef = React.useRef(null);
const hasAutoPlayedRef = React.useRef(false);
const { data: project } = useDataFromRef({
ref: projectId ? projectsRef.doc(projectId) : null,
@@ -116,6 +125,108 @@ 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 [isDownloading, setIsDownloading] = useState(false);
const [downloadProgress, setDownloadProgress] = useState(0);
const handleDownload = useCallback(async () => {
if (!songUrl || isDownloading) return;
try {
setIsDownloading(true);
setDownloadProgress(0);
const response = await fetch(songUrl);
if (!response.ok) {
throw new Error(`download_failed_${response.status}`);
}
const contentType = response.headers.get("content-type") || "audio/mpeg";
const total = Number(response.headers.get("content-length")) || 0;
const sanitizedTitle = String(title || "musicland-track")
.replace(/[\\/:*?"<>|]+/g, "-")
.trim()
.slice(0, 80);
const fileName = `${sanitizedTitle || "musicland-track"}.mp3`;
if (response.body && typeof response.body.getReader === "function") {
const reader = response.body.getReader();
const chunks = [];
let received = 0;
let pseudoProgress = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (value) {
chunks.push(value);
received += value.length;
if (total > 0) {
setDownloadProgress(Math.min(1, received / total));
} else {
pseudoProgress = Math.min(0.95, pseudoProgress + 0.05);
setDownloadProgress(pseudoProgress);
}
}
}
const blob = new Blob(chunks, { type: contentType });
setDownloadProgress(1);
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
} else {
const blob = await response.blob();
setDownloadProgress(1);
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
} catch (error) {
console.error("MusicDetails.download", error);
setTooltip({
type: "error",
text: "Téléchargement impossible",
});
} finally {
setIsDownloading(false);
setTimeout(() => setDownloadProgress(0), 400);
}
}, [isDownloading, setTooltip, songUrl, title]);
const handleReport = useCallback(() => {
if (!projectId) return;
SheetManager.show("Report", {
payload: {
targetType: "music",
projectId,
title,
ownerId: project?.userId || owner?.id || null,
},
});
}, [owner?.id, project?.userId, projectId, title]);
const {
isCurrent: isCurrentTrack,
isPlaying: isTrackPlaying,
@@ -150,6 +261,7 @@ const MusicDetails = ({ route }) => {
useEffect(() => {
listenedMsRef.current = 0;
incrementDoneRef.current = false;
hasAutoPlayedRef.current = false;
}, [trackId]);
// Start/stop a timer to accumulate listened milliseconds while playing
@@ -282,6 +394,38 @@ const MusicDetails = ({ route }) => {
[trackDescriptor, durationMs, isCurrentTrack, ensureLoaded, seekTrackTo]
);
useEffect(() => {
if (!autoPlayRequested || hasAutoPlayedRef.current) {
return;
}
if (!trackDescriptor || !songUrl) {
return;
}
const run = async () => {
try {
if (!isCurrentTrack) {
await ensureLoaded({ startPositionMs: 0, autoPlay: true });
} else if (!isPlaying) {
await resumeTrack();
}
hasAutoPlayedRef.current = true;
} catch (e) {
console.log("MusicDetails autoplay error", e?.message);
}
};
run();
}, [
autoPlayRequested,
ensureLoaded,
isCurrentTrack,
isPlaying,
resumeTrack,
songUrl,
trackDescriptor,
]);
const handleSliderSeekEnd = useCallback(async () => {
const targetMs =
typeof lastSeekTargetMs.current === "number"
@@ -677,6 +821,14 @@ const MusicDetails = ({ route }) => {
backgroundImg={
action === "userProfile" ? background.profileBG : background.libraryBG2
}
shareBtn={
sharePayload
? {
label: "Partager le morceau",
onPress: handleShare,
}
: false
}
{...(action === "userProfile" && {
containerStyle: {
backgroundColor: "#0000004D",
@@ -761,6 +913,45 @@ 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 } })
@@ -975,3 +1166,38 @@ 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>
);
};