clear last tickets

This commit is contained in:
Thomas Demirdjian
2025-11-25 14:58:06 +01:00
parent 72852ad92b
commit ee9cc5dccd
31 changed files with 1009 additions and 531 deletions
+17
View File
@@ -11,6 +11,8 @@ import {
import { responsiveHeight } from "react-native-responsive-dimensions";
import { background, icons } from "../../assets";
import SearchBar from "../../components/SearchBar";
import MobileCoinBadge from "../../components/MobileCoinBadge";
import ShareBtn from "../../components/ShareBtn/ShareBtn";
import Page from "../../layouts/Page";
import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService";
@@ -57,6 +59,21 @@ const Library = () => {
return (
<Page backgroundImg={background.libraryBG} headerType="NONE">
{Platform.OS !== "web" ? (
<View
style={{
width: "100%",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
gap: 12,
marginBottom: 6,
}}
>
<MobileCoinBadge style={{ flexShrink: 1 }} />
<ShareBtn label="Partager" style={{ flexShrink: 0 }} />
</View>
) : null}
<View style={{ gap: 17, paddingBottom: 20 }}>
<Image
source={icons.musicLandLogo}
+12 -69
View File
@@ -24,7 +24,7 @@ import {
import { useGlobal } from "reactn";
import { background, icons } from "../../assets";
import PressableScale from "../../components/PressableScale";
import Slider from "../../components/Slider";
import ProgressSlider from "../../components/player/ProgressSlider";
import { increment, projectsRef, usersRef } from "../../config/firebase";
import useDataFromRef from "../../hooks/useDataFromRef";
import usePlayer from "../../hooks/usePlayer";
@@ -61,9 +61,6 @@ const MusicDetails = ({ route }) => {
const projectId = params?.projectId || null;
const [fav, setFav] = useState(false);
const [currentUID] = useGlobal("currentUID");
const wasPlayingBeforeSeek = useRef(false);
const hasCapturedSeekStateRef = useRef(false);
const lastSeekTargetMsRef = useRef(null);
const listenedMsRef = useRef(0);
const incrementDoneRef = useRef(false);
const timerRef = useRef(null);
@@ -292,15 +289,6 @@ const MusicDetails = ({ route }) => {
return clearTimer;
}, [isTrackPlaying, projectId]);
const fmt = (ms) => {
const total = Math.max(0, Math.floor((ms || 0) / 1000));
const m = Math.floor(total / 60)
.toString()
.padStart(1, "0");
const s = (total % 60).toString().padStart(2, "0");
return `${m}:${s}`;
};
const togglePlay = useCallback(async () => {
if (!trackDescriptor) return;
try {
@@ -328,41 +316,30 @@ const MusicDetails = ({ route }) => {
const handleSliderSeekStart = useCallback(async () => {
if (!trackDescriptor) return;
lastSeekTargetMsRef.current = null;
if (!hasCapturedSeekStateRef.current) {
hasCapturedSeekStateRef.current = true;
wasPlayingBeforeSeek.current = isTrackPlaying;
}
try {
if (!isCurrentTrack) {
await ensureLoaded({ startPositionMs: positionMs, autoPlay: false });
}
if (isTrackPlaying) {
await pauseTrack();
}
} catch (e) {
console.log("MusicDetails seek start error", e?.message);
}
}, [
trackDescriptor,
isTrackPlaying,
isCurrentTrack,
ensureLoaded,
positionMs,
pauseTrack,
]);
const handleSliderSeek = useCallback(
async (ratio) => {
async (targetMs) => {
const dur = sliderDurationMs || 0;
if (!trackDescriptor || dur <= 0) return;
const targetMs = Math.max(0, Math.floor(dur * ratio));
lastSeekTargetMsRef.current = targetMs;
const bounded = Math.max(0, Math.min(dur, Math.floor(targetMs)));
try {
if (!isCurrentTrack) {
await ensureLoaded({ startPositionMs: targetMs, autoPlay: false });
await ensureLoaded({ startPositionMs: bounded, autoPlay: false });
} else {
await seekTrackTo(targetMs);
await seekTrackTo(bounded);
}
} catch (e) {
console.log("MusicDetails seek error", e?.message);
@@ -424,37 +401,6 @@ const MusicDetails = ({ route }) => {
resumeTrack,
]);
const handleSliderSeekEnd = useCallback(async () => {
const targetMs =
typeof lastSeekTargetMsRef.current === "number"
? Math.max(0, lastSeekTargetMsRef.current)
: null;
try {
if (wasPlayingBeforeSeek.current) {
if (!isCurrentTrack) {
await ensureLoaded({
startPositionMs:
targetMs !== null && Number.isFinite(targetMs)
? targetMs
: positionMs,
autoPlay: true,
});
} else {
if (targetMs !== null && Number.isFinite(targetMs)) {
await seekTrackTo(targetMs);
}
await resumeTrack();
}
}
} catch (e) {
console.log("MusicDetails seek end error", e?.message);
} finally {
wasPlayingBeforeSeek.current = false;
hasCapturedSeekStateRef.current = false;
lastSeekTargetMsRef.current = null;
}
}, [ensureLoaded, isCurrentTrack, positionMs, resumeTrack, seekTrackTo]);
const handleSeekBySeconds = useCallback(
async (deltaSeconds) => {
if (!trackDescriptor) return;
@@ -888,18 +834,15 @@ const MusicDetails = ({ route }) => {
</View>
{songUrl && (
<View style={{ paddingTop: 22 }}>
<Slider
value={fmt(positionMs)}
maxValue={fmt(sliderDurationMs)}
progress={
sliderDurationMs
? Math.min(1, Math.max(0, (positionMs || 0) / sliderDurationMs))
: 0
}
seekEnabled={!!songUrl}
<ProgressSlider
positionMs={positionMs}
durationMs={sliderDurationMs}
isPlaying={isTrackPlaying}
onSeekStart={handleSliderSeekStart}
onSeek={handleSliderSeek}
onSeekEnd={handleSliderSeekEnd}
onPause={pauseTrack}
onPlay={resumeTrack}
disabled={!songUrl}
/>
<View
style={{ ...Style.containerRow, gap: 24, alignSelf: "center" }}
+12 -82
View File
@@ -26,7 +26,7 @@ import {
import { useGlobal } from "reactn";
import { background, icons } from "../../assets";
import PressableScale from "../../components/PressableScale";
import Slider from "../../components/Slider";
import ProgressSlider from "../../components/player/ProgressSlider";
import { increment, projectsRef, usersRef } from "../../config/firebase";
import { ensureAuthenticated } from "../../utils/authRedirect";
import useDataFromRef from "../../hooks/useDataFromRef";
@@ -66,8 +66,6 @@ const MusicDetails = ({ route }) => {
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);
@@ -394,15 +392,6 @@ const MusicDetails = ({ route }) => {
return clearTimer;
}, [isPlaying, projectId]);
const fmt = (ms) => {
const total = Math.max(0, Math.floor((ms || 0) / 1000));
const m = Math.floor(total / 60)
.toString()
.padStart(1, "0");
const s = (total % 60).toString().padStart(2, "0");
return `${m}:${s}`;
};
const togglePlay = useCallback(async () => {
if (!trackDescriptor) return;
try {
@@ -434,54 +423,32 @@ const MusicDetails = ({ route }) => {
const handleSliderSeekStart = useCallback(async () => {
if (!trackDescriptor) return;
try {
console.log("[MusicDetails.web] handleSliderSeekStart", {
isCurrentTrack,
isTrackPlaying,
positionMs,
durationMs,
});
lastSeekTargetMs.current = null;
wasPlayingBeforeSeek.current = isTrackPlaying;
if (!isCurrentTrack) {
await ensureLoaded({
startPositionMs: positionMs,
autoPlay: false,
});
}
if (isTrackPlaying) {
await pauseTrack();
}
} catch (e) {
console.log("MusicDetails seek start error", e?.message);
}
}, [
trackDescriptor,
isTrackPlaying,
isCurrentTrack,
ensureLoaded,
positionMs,
pauseTrack,
lastSeekTargetMs,
durationMs,
]);
const handleSliderSeek = useCallback(
async (ratio) => {
async (targetMs) => {
const dur = sliderDurationMs || 0;
if (!trackDescriptor || dur <= 0) return;
const targetMs = Math.max(0, Math.floor(dur * ratio));
lastSeekTargetMs.current = targetMs;
console.log("[MusicDetails.web] handleSliderSeek", {
ratio,
durationMs: dur,
targetMs,
isCurrentTrack,
});
const bounded = Math.max(0, Math.min(dur, Math.floor(targetMs)));
try {
if (!isCurrentTrack) {
await ensureLoaded({ startPositionMs: targetMs, autoPlay: false });
await ensureLoaded({ startPositionMs: bounded, autoPlay: false });
} else {
await seekTrackTo(targetMs);
await seekTrackTo(bounded);
}
} catch (e) {
console.log("MusicDetails seek error", e?.message);
@@ -553,42 +520,6 @@ const MusicDetails = ({ route }) => {
trackDescriptor,
]);
const handleSliderSeekEnd = useCallback(async () => {
const targetMs =
typeof lastSeekTargetMs.current === "number"
? Math.max(0, lastSeekTargetMs.current)
: null;
console.log("[MusicDetails.web] handleSliderSeekEnd", {
targetMs,
wasPlayingBeforeSeek: wasPlayingBeforeSeek.current,
isCurrentTrack,
positionMs,
});
try {
if (wasPlayingBeforeSeek.current) {
if (!isCurrentTrack) {
await ensureLoaded({
startPositionMs:
targetMs !== null && Number.isFinite(targetMs)
? targetMs
: positionMs,
autoPlay: true,
});
} else {
if (targetMs !== null && Number.isFinite(targetMs)) {
await seekTrackTo(targetMs);
}
await resumeTrack();
}
}
} catch (e) {
console.log("MusicDetails seek end error", e?.message);
} finally {
wasPlayingBeforeSeek.current = false;
lastSeekTargetMs.current = null;
}
}, [ensureLoaded, isCurrentTrack, positionMs, resumeTrack, seekTrackTo]);
const handleSeekBySeconds = useCallback(
async (deltaSeconds) => {
if (!trackDescriptor) return;
@@ -1140,16 +1071,15 @@ const MusicDetails = ({ route }) => {
/>
</PressableScale>
</View>
<Slider
value={fmt(positionMs)}
maxValue={fmt(sliderDurationMs)}
progress={
sliderDurationMs ? (positionMs || 0) / sliderDurationMs : 0
}
seekEnabled={!!songUrl}
<ProgressSlider
positionMs={positionMs}
durationMs={sliderDurationMs}
isPlaying={isPlaying}
onSeekStart={handleSliderSeekStart}
onSeek={handleSliderSeek}
onSeekEnd={handleSliderSeekEnd}
onPause={pauseTrack}
onPlay={resumeTrack}
disabled={!songUrl}
/>
</View>
)}
+27 -4
View File
@@ -1,5 +1,5 @@
import { Image as ExpoImage } from "expo-image";
import React, { useState } from "react";
import React, { useCallback, useState } from "react";
import {
FlatList,
Pressable,
@@ -33,6 +33,29 @@ const LikedMusic = () => {
Math.floor((containerWidth - gap * (numColumns - 1)) / numColumns)
);
const navigateToMusicDetails = useNavigateToMusicDetails();
const resolveCoverUri = useCallback((project) => {
if (!project) return null;
const isValid = (value) =>
typeof value === "string" && value.trim().length > 0;
if (isValid(project?.coverUrl)) {
return project.coverUrl;
}
const cover = project?.cover || {};
const coverCandidates = [
cover?.result,
cover?.finalUrl,
cover?.generatedBackground,
];
if (Array.isArray(cover?.options)) {
coverCandidates.push(
...cover.options.flatMap((option) => [
option?.finalUrl,
option?.generatedUrl,
])
);
}
return coverCandidates.find(isValid) || null;
}, []);
return (
<CardContainer
label="Musiques likées"
@@ -43,7 +66,7 @@ const LikedMusic = () => {
liked: true,
})
}
onPressPlus={() => navigate(Routes.WritingLyrics)}
onPressPlus={() => navigate(Routes.HitParade)}
>
{items.length > 0 ? (
<FlatList
@@ -66,9 +89,9 @@ const LikedMusic = () => {
})
}
>
{item?.coverUrl ? (
{resolveCoverUri(item) ? (
<ExpoImage
source={{ uri: item.coverUrl }}
source={{ uri: resolveCoverUri(item) }}
cachePolicy="memory-disk"
priority="high"
contentFit="cover"
@@ -39,7 +39,7 @@ const LikedPlayback = () => {
liked: true,
})
}
onPressPlus={() => navigate(Routes.WritingLyrics)}
onPressPlus={() => navigate(Routes.Playbacks)}
>
<View>
{items.length > 0 ? (