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