clear last tickets

This commit is contained in:
Thomas Demirdjian
2025-11-26 15:33:22 +01:00
parent ee9cc5dccd
commit d7b14bd873
25 changed files with 1692 additions and 1967 deletions
+101 -35
View File
@@ -24,8 +24,14 @@ import {
import { useGlobal } from "reactn";
import { background, icons } from "../../assets";
import PressableScale from "../../components/PressableScale";
import ProgressSlider from "../../components/player/ProgressSlider";
import { increment, projectsRef, usersRef } from "../../config/firebase";
import Slider from "../../components/Slider";
import {
arrayRemove,
arrayUnion,
increment,
projectsRef,
usersRef,
} from "../../config/firebase";
import useDataFromRef from "../../hooks/useDataFromRef";
import usePlayer from "../../hooks/usePlayer";
import useTrackController from "../../hooks/useTrackController";
@@ -39,11 +45,6 @@ import {
createMusicSharePayload,
openShareSheet,
} from "../../utils/shareSheet";
import {
getProjectLikes,
LIKE_TARGET,
toggleProjectLike,
} from "../../utils/likes";
import {
formatStructureLabel,
getPromptLabelForStructure,
@@ -61,6 +62,9 @@ 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);
@@ -82,10 +86,13 @@ const MusicDetails = ({ route }) => {
});
useEffect(() => {
const likes = getProjectLikes(project, LIKE_TARGET.SONG);
const liked = currentUID ? likes.includes(currentUID) : false;
setFav(liked);
}, [currentUID, project]);
if (project && currentUID) {
const liked = Array.isArray(project?.likedBy)
? project.likedBy.includes(currentUID)
: false;
setFav(liked);
}
}, [project?.likedBy, currentUID]);
const title = project?.title || "Sans titre";
const artist = useMemo(() => {
@@ -289,6 +296,15 @@ 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 {
@@ -316,30 +332,41 @@ 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 (targetMs) => {
async (ratio) => {
const dur = sliderDurationMs || 0;
if (!trackDescriptor || dur <= 0) return;
const bounded = Math.max(0, Math.min(dur, Math.floor(targetMs)));
const targetMs = Math.max(0, Math.floor(dur * ratio));
lastSeekTargetMsRef.current = targetMs;
try {
if (!isCurrentTrack) {
await ensureLoaded({ startPositionMs: bounded, autoPlay: false });
await ensureLoaded({ startPositionMs: targetMs, autoPlay: false });
} else {
await seekTrackTo(bounded);
await seekTrackTo(targetMs);
}
} catch (e) {
console.log("MusicDetails seek error", e?.message);
@@ -401,6 +428,37 @@ 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;
@@ -455,6 +513,10 @@ const MusicDetails = ({ route }) => {
// Build a readable text from lyrics with section labels
if (Array.isArray(project?.lyrics)) {
return project.lyrics
.filter((s) => {
const t = (s?.type || "").toLowerCase();
return ["couplet", "refrain"].includes(t);
})
.map((s) => {
const body = (s?.lyrics || "").trim();
if (!body) return null;
@@ -696,13 +758,15 @@ const MusicDetails = ({ route }) => {
pushCurrent();
let lineIdx = 0;
return grouped.map((section) => ({
...section,
lines: section.lines.map((line) => ({
...line,
globalIdx: lineIdx++,
})),
}));
return grouped
.filter((s) => ["couplet", "refrain"].includes(s.type))
.map((section) => ({
...section,
lines: section.lines.map((line) => ({
...line,
globalIdx: lineIdx++,
})),
}));
}, [alignedWords]);
const flatLines = useMemo(
@@ -729,7 +793,7 @@ const MusicDetails = ({ route }) => {
if (lyricsRef.current && typeof y === "number") {
try {
lyricsRef.current.scrollTo({ y: Math.max(0, y - 80), animated: true });
} catch (e) {}
} catch (e) { }
}
}, [currentLineIdx]);
@@ -804,11 +868,10 @@ const MusicDetails = ({ route }) => {
const next = !fav;
setFav(next);
try {
await toggleProjectLike({
projectId,
target: LIKE_TARGET.SONG,
currentUID,
next,
await projectsRef.doc(projectId).update({
likedBy: next
? arrayUnion(currentUID)
: arrayRemove(currentUID),
});
} catch (e) {
setFav(!next);
@@ -834,15 +897,18 @@ const MusicDetails = ({ route }) => {
</View>
{songUrl && (
<View style={{ paddingTop: 22 }}>
<ProgressSlider
positionMs={positionMs}
durationMs={sliderDurationMs}
isPlaying={isTrackPlaying}
<Slider
value={fmt(positionMs)}
maxValue={fmt(sliderDurationMs)}
progress={
sliderDurationMs
? Math.min(1, Math.max(0, (positionMs || 0) / sliderDurationMs))
: 0
}
seekEnabled={!!songUrl}
onSeekStart={handleSliderSeekStart}
onSeek={handleSliderSeek}
onPause={pauseTrack}
onPlay={resumeTrack}
disabled={!songUrl}
onSeekEnd={handleSliderSeekEnd}
/>
<View
style={{ ...Style.containerRow, gap: 24, alignSelf: "center" }}