clear last tickets
This commit is contained in:
@@ -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" }}
|
||||
|
||||
Reference in New Issue
Block a user