clear lot of tickets

This commit is contained in:
Thomas Demirdjian
2025-11-07 17:33:06 +01:00
parent 4365f1e46d
commit d7d9211fbe
30 changed files with 970 additions and 355 deletions
+19 -2
View File
@@ -6,7 +6,7 @@ import React, {
useRef,
useState,
} from "react";
import { Image, Platform, ScrollView, Text, View } from "react-native";
import { Image, Platform, ScrollView, StyleSheet, Text, View } from "react-native";
import { responsiveHeight } from "react-native-responsive-dimensions";
import { background, icons } from "../../assets";
import SearchBar from "../../components/SearchBar";
@@ -91,6 +91,7 @@ const Library = () => {
const [dropdownVisible, setDropdownVisible] = useState(false);
const searchWrapperRef = useRef(null);
const hasSearchQuery = search.trim().length > 0;
const closeDropdown = useCallback(() => {
setDropdownVisible(false);
@@ -103,6 +104,7 @@ const Library = () => {
};
const shouldShowResults = dropdownVisible;
const shouldBlurContent = dropdownVisible && hasSearchQuery;
const handleChangeText = (value) => {
setSearch(value);
@@ -203,7 +205,21 @@ const Library = () => {
)}
</View>
</View>
<View style={{ flex: 1 }}>
<View style={{ flex: 1, position: "relative" }}>
{shouldBlurContent && (
<BlurView
intensity={35}
tint="dark"
style={[
StyleSheet.absoluteFillObject,
{
zIndex: 10,
borderRadius: 0,
backgroundColor: "rgba(0, 0, 0, 0.25)",
},
]}
/>
)}
<ScrollView
contentContainerStyle={{
flexGrow: 1,
@@ -212,6 +228,7 @@ const Library = () => {
paddingTop: Platform.OS !== "android" ? 20 : 0,
}}
showsVerticalScrollIndicator={false}
style={{ flex: 1, position: "relative", zIndex: 5 }}
>
<BlurView
intensity={40}
+37 -5
View File
@@ -221,6 +221,38 @@ const MusicDetails = ({ route }) => {
}
}, [project?.musicTimestamps, project?.songIndex]);
const estimatedDurationMs = useMemo(() => {
const toMs = (value) => {
const num = Number(value);
if (!Number.isFinite(num) || num <= 0) return 0;
return num > 1000 ? Math.round(num) : Math.round(num * 1000);
};
const idx = Number(project?.songIndex);
const normalizedIdx = Number.isFinite(idx) && idx >= 0 ? idx : 0;
const tsEntry = project?.musicTimestamps?.[normalizedIdx];
if (tsEntry && typeof tsEntry === "object") {
const fromMetadata =
toMs(tsEntry.durationMs) ||
toMs(tsEntry.duration) ||
toMs(tsEntry.durationS) ||
toMs(tsEntry.durationSeconds) ||
toMs(tsEntry.audioDuration) ||
toMs(tsEntry.audioLength);
if (fromMetadata > 0) return fromMetadata;
}
let maxEndS = 0;
for (let i = 0; i < alignedWords.length; i++) {
const word = alignedWords[i];
const end = Number(word?.endS ?? word?.startS ?? 0);
if (Number.isFinite(end) && end > maxEndS) {
maxEndS = end;
}
}
return maxEndS > 0 ? Math.round(maxEndS * 1000) : 0;
}, [alignedWords, project?.musicTimestamps, project?.songIndex]);
const sliderDurationMs = durationMs > 0 ? durationMs : estimatedDurationMs;
// Reset counters when the track changes
useEffect(() => {
listenedMsRef.current = 0;
@@ -320,7 +352,7 @@ const MusicDetails = ({ route }) => {
const handleSliderSeek = useCallback(
async (ratio) => {
const dur = durationMs || 0;
const dur = sliderDurationMs || 0;
if (!trackDescriptor || dur <= 0) return;
const targetMs = Math.max(0, Math.floor(dur * ratio));
try {
@@ -333,7 +365,7 @@ const MusicDetails = ({ route }) => {
console.log("MusicDetails seek error", e?.message);
}
},
[durationMs, trackDescriptor, isCurrentTrack, ensureLoaded, seekTrackTo]
[sliderDurationMs, trackDescriptor, isCurrentTrack, ensureLoaded, seekTrackTo]
);
const handleToggleLoop = useCallback(async () => {
@@ -839,10 +871,10 @@ const MusicDetails = ({ route }) => {
<View style={{ paddingTop: 22 }}>
<Slider
value={fmt(positionMs)}
maxValue={fmt(durationMs)}
maxValue={fmt(sliderDurationMs)}
progress={
durationMs
? Math.min(1, Math.max(0, (positionMs || 0) / durationMs))
sliderDurationMs
? Math.min(1, Math.max(0, (positionMs || 0) / sliderDurationMs))
: 0
}
seekEnabled={!!songUrl}
+91 -4
View File
@@ -11,6 +11,7 @@ import React, {
import {
Pressable,
Image as RNImage,
Platform,
ScrollView,
StyleSheet,
Text,
@@ -52,6 +53,7 @@ import {
createMusicSharePayload,
openShareSheet,
} from "../../utils/shareSheet";
import { goBack } from "../../navigation/NavigationService";
// 20 secondes
const timeBeforeIncrement = 20000;
@@ -71,6 +73,29 @@ const MusicDetails = ({ route }) => {
const timerRef = React.useRef(null);
const hasAutoPlayedRef = React.useRef(false);
const { isLooping, setLooping } = usePlayer() || {};
const isWeb = Platform.OS === "web";
const handleBackPress = useCallback(() => {
goBack();
}, []);
const renderWebBackButton = useCallback(() => {
if (!isWeb) return null;
return (
<View style={styles.webBackContainer}>
<Pressable
accessibilityRole="button"
onPress={handleBackPress}
style={styles.webBackButton}
>
<RNImage
source={icons.chevronDown}
style={styles.webBackIcon}
resizeMode="contain"
/>
<Text style={styles.webBackText}>Retour</Text>
</Pressable>
</View>
);
}, [handleBackPress, isWeb]);
const { data: project } = useDataFromRef({
ref: projectId ? projectsRef.doc(projectId) : null,
@@ -297,6 +322,38 @@ const MusicDetails = ({ route }) => {
}
}, [project?.musicTimestamps, project?.songIndex]);
const estimatedDurationMs = useMemo(() => {
const toMs = (value) => {
const num = Number(value);
if (!Number.isFinite(num) || num <= 0) return 0;
return num > 1000 ? Math.round(num) : Math.round(num * 1000);
};
const idx = Number(project?.songIndex);
const normalizedIdx = Number.isFinite(idx) && idx >= 0 ? idx : 0;
const tsEntry = project?.musicTimestamps?.[normalizedIdx];
if (tsEntry && typeof tsEntry === "object") {
const fromMetadata =
toMs(tsEntry.durationMs) ||
toMs(tsEntry.duration) ||
toMs(tsEntry.durationS) ||
toMs(tsEntry.durationSeconds) ||
toMs(tsEntry.audioDuration) ||
toMs(tsEntry.audioLength);
if (fromMetadata > 0) return fromMetadata;
}
let maxEndS = 0;
for (let i = 0; i < alignedWords.length; i++) {
const word = alignedWords[i];
const end = Number(word?.endS ?? word?.startS ?? 0);
if (Number.isFinite(end) && end > maxEndS) {
maxEndS = end;
}
}
return maxEndS > 0 ? Math.round(maxEndS * 1000) : 0;
}, [alignedWords, project?.musicTimestamps, project?.songIndex]);
const sliderDurationMs = durationMs > 0 ? durationMs : estimatedDurationMs;
// Reset counters when the track changes
useEffect(() => {
listenedMsRef.current = 0;
@@ -411,7 +468,7 @@ const MusicDetails = ({ route }) => {
const handleSliderSeek = useCallback(
async (ratio) => {
const dur = durationMs || 0;
const dur = sliderDurationMs || 0;
if (!trackDescriptor || dur <= 0) return;
const targetMs = Math.max(0, Math.floor(dur * ratio));
lastSeekTargetMs.current = targetMs;
@@ -431,7 +488,7 @@ const MusicDetails = ({ route }) => {
console.log("MusicDetails seek error", e?.message);
}
},
[trackDescriptor, durationMs, isCurrentTrack, ensureLoaded, seekTrackTo]
[trackDescriptor, sliderDurationMs, isCurrentTrack, ensureLoaded, seekTrackTo]
);
const handleToggleLoop = useCallback(async () => {
@@ -905,6 +962,8 @@ const MusicDetails = ({ route }) => {
return (
<Page
headerType="NAVIGATION"
hideBackButton={isWeb}
topStickyContent={renderWebBackButton}
title={action === "userProfile" ? "Mon profil" : "Détail musique"}
backgroundImg={
action === "userProfile" ? background.profileBG : background.libraryBG2
@@ -1078,8 +1137,10 @@ const MusicDetails = ({ route }) => {
</View>
<Slider
value={fmt(positionMs)}
maxValue={fmt(durationMs)}
progress={durationMs ? (positionMs || 0) / durationMs : 0}
maxValue={fmt(sliderDurationMs)}
progress={
sliderDurationMs ? (positionMs || 0) / sliderDurationMs : 0
}
seekEnabled={!!songUrl}
onSeekStart={handleSliderSeekStart}
onSeek={handleSliderSeek}
@@ -1191,6 +1252,32 @@ const MusicDetails = ({ route }) => {
export default MusicDetails;
const styles = StyleSheet.create({
webBackContainer: {
alignSelf: "flex-start",
marginBottom: 12,
},
webBackButton: {
flexDirection: "row",
alignItems: "center",
gap: 8,
alignSelf: "flex-start",
paddingHorizontal: 14,
paddingVertical: 7,
borderRadius: 999,
borderWidth: 1,
borderColor: Palette.ultraLightWhite,
backgroundColor: Palette.ultraLightWhite,
},
webBackIcon: {
width: 16,
height: 16,
transform: [{ rotate: "90deg" }],
},
webBackText: {
fontSize: 14,
color: Palette.white,
fontFamily: FONT_FAMILY.InterSemiBold,
},
img: {
width: 200,
height: 200,
@@ -84,11 +84,6 @@ const SearchResultsList = ({
);
}
const fallbackThumbnail = thumbnailCandidates.find(isValid);
if (fallbackThumbnail) {
coverCandidates.push(fallbackThumbnail);
}
return coverCandidates.find(isValid) || null;
},
[]