feat: slider

This commit is contained in:
2025-12-08 15:35:16 +01:00
parent c97fb0a856
commit c68dcef76d
4 changed files with 52 additions and 81 deletions
+3 -3
View File
@@ -46,7 +46,7 @@ const ProgressSlider = ({
if (isPlaying && typeof onPause === "function") { if (isPlaying && typeof onPause === "function") {
onPause(); onPause();
} }
}, [disabled, safeDuration, isPlaying, onPause, onSeekStart]); }, [disabled, safeDuration, isPlaying, onPause, onSeekStart, progress]);
const handleSeek = useCallback( const handleSeek = useCallback(
(ratio) => { (ratio) => {
@@ -56,7 +56,7 @@ const ProgressSlider = ({
const targetMs = safeDuration * bounded; const targetMs = safeDuration * bounded;
onSeek(targetMs); onSeek(targetMs);
}, },
[disabled, onSeek, safeDuration], [disabled, onSeek, safeDuration]
); );
const handleSeekEnd = useCallback(() => { const handleSeekEnd = useCallback(() => {
@@ -77,7 +77,7 @@ const ProgressSlider = ({
value={formatTime( value={formatTime(
isSeeking && previewRatio != null isSeeking && previewRatio != null
? safeDuration * previewRatio ? safeDuration * previewRatio
: safePosition, : safePosition
)} )}
maxValue={formatTime(safeDuration)} maxValue={formatTime(safeDuration)}
progress={progress} progress={progress}
+13 -6
View File
@@ -31,7 +31,7 @@ const hasCoverAsset = (project) => {
...cover.options.flatMap((option) => [ ...cover.options.flatMap((option) => [
option?.finalUrl, option?.finalUrl,
option?.generatedUrl, option?.generatedUrl,
]), ])
); );
} }
return coverCandidates.some(isValidUri); return coverCandidates.some(isValidUri);
@@ -43,7 +43,7 @@ const hasThumbnailAsset = (project) =>
const mergeProjectAssets = ( const mergeProjectAssets = (
project, project,
firestoreData, firestoreData,
{ mergeCover = false, mergeThumbnails = false } = {}, { mergeCover = false, mergeThumbnails = false } = {}
) => { ) => {
if (!firestoreData || typeof firestoreData !== "object") { if (!firestoreData || typeof firestoreData !== "object") {
return project; return project;
@@ -51,7 +51,10 @@ const mergeProjectAssets = (
const mergedProject = { ...project }; const mergedProject = { ...project };
if (mergeCover) { if (mergeCover) {
if (!isValidUri(mergedProject.coverUrl) && isValidUri(firestoreData.coverUrl)) { if (
!isValidUri(mergedProject.coverUrl) &&
isValidUri(firestoreData.coverUrl)
) {
mergedProject.coverUrl = firestoreData.coverUrl; mergedProject.coverUrl = firestoreData.coverUrl;
} }
const firestoreCover = const firestoreCover =
@@ -67,7 +70,10 @@ const mergeProjectAssets = (
} }
if (mergeThumbnails) { if (mergeThumbnails) {
if (!isValidUri(mergedProject.thumbnailUrl) && isValidUri(firestoreData.thumbnailUrl)) { if (
!isValidUri(mergedProject.thumbnailUrl) &&
isValidUri(firestoreData.thumbnailUrl)
) {
mergedProject.thumbnailUrl = firestoreData.thumbnailUrl; mergedProject.thumbnailUrl = firestoreData.thumbnailUrl;
} }
if ( if (
@@ -83,7 +89,7 @@ const mergeProjectAssets = (
const hydrateProjects = async ( const hydrateProjects = async (
projects = [], projects = [],
{ ensureCover = false, ensureThumbnails = false } = {}, { ensureCover = false, ensureThumbnails = false } = {}
) => { ) => {
if (!Array.isArray(projects) || projects.length === 0) { if (!Array.isArray(projects) || projects.length === 0) {
return []; return [];
@@ -112,7 +118,7 @@ const hydrateProjects = async (
console.log("useSearch.hydrateProjects error", error?.message || error); console.log("useSearch.hydrateProjects error", error?.message || error);
return project; return project;
} }
}), })
); );
}; };
@@ -132,6 +138,7 @@ const useSearch = () => {
filters: `NOT objectID:${currentUID}`, filters: `NOT objectID:${currentUID}`,
}, },
}); });
console.log("users are ", JSON.stringify(users, null, 2));
const { hits: playbacks, loading: playbackLoading } = useAlgoliaSearch({ const { hits: playbacks, loading: playbackLoading } = useAlgoliaSearch({
query: search, query: search,
algoliaObject: AlgoliaProjectConfig, algoliaObject: AlgoliaProjectConfig,
+4 -10
View File
@@ -20,6 +20,7 @@ import { Routes } from "../navigation/Routes";
import { useUser } from "../providers/UserDataProvider"; import { useUser } from "../providers/UserDataProvider";
import { Palette } from "../styles"; import { Palette } from "../styles";
import { FONT_FAMILY } from "../styles/Fonts"; import { FONT_FAMILY } from "../styles/Fonts";
import { BlurView } from "expo-blur";
const LANGUAGE_STORAGE_KEY = "preferredLanguage"; const LANGUAGE_STORAGE_KEY = "preferredLanguage";
@@ -120,12 +121,7 @@ export default function LandingPage() {
{selectedLanguage && ( {selectedLanguage && (
<View style={styles.actions}> <View style={styles.actions}>
{showFreeCreditsPopup && ( {showFreeCreditsPopup && (
<LinearGradient <BlurView style={styles.freeCreditsCard}>
colors={["#F94697", "#7023F7"]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={styles.freeCreditsCard}
>
<View style={styles.freeCreditsContent}> <View style={styles.freeCreditsContent}>
<View style={styles.freeCreditsTextContainer}> <View style={styles.freeCreditsTextContainer}>
<Text style={styles.freeCreditsTitle}> <Text style={styles.freeCreditsTitle}>
@@ -147,7 +143,7 @@ export default function LandingPage() {
/> />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</LinearGradient> </BlurView>
)} )}
<GradientButton <GradientButton
title="Découvrir Musicland" title="Découvrir Musicland"
@@ -158,9 +154,7 @@ export default function LandingPage() {
title="Présentation de Musicland" title="Présentation de Musicland"
onPress={() => onPress={() =>
setVideoUrl( setVideoUrl(
Platform.OS === "web" Platform.OS === "web" ? video?.landingWeb : video?.landing
? video?.landingWeb
: video?.landing,
) )
} }
containerStyle={styles.actionButton} containerStyle={styles.actionButton}
+32 -62
View File
@@ -8,13 +8,7 @@ import React, {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import { import { BackHandler, Text, TouchableOpacity, View } from "react-native";
AppState,
BackHandler,
Text,
TouchableOpacity,
View,
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useSafeAreaInsets } from "react-native-safe-area-context";
import Svg, { Circle } from "react-native-svg"; import Svg, { Circle } from "react-native-svg";
import alert from "../../components/Alert"; import alert from "../../components/Alert";
@@ -62,7 +56,7 @@ const RecordPlayback = ({ route }) => {
const isPausedRef = useRef(false); const isPausedRef = useRef(false);
const stopRequestedRef = useRef(false); const stopRequestedRef = useRef(false);
const restartRequestedRef = useRef(false); const restartRequestedRef = useRef(false);
const interruptedRef = useRef(false); // New ref to track interruptions const recordingStartTimeRef = useRef(0); // Track recording start time for duration check
const manualRestartInFlightRef = useRef(false); const manualRestartInFlightRef = useRef(false);
const stopRequestedAtRef = useRef(0); const stopRequestedAtRef = useRef(0);
const activeRecordingPromiseRef = useRef(null); const activeRecordingPromiseRef = useRef(null);
@@ -114,42 +108,8 @@ const RecordPlayback = ({ route }) => {
isPausedRef.current = isPaused; isPausedRef.current = isPaused;
}, [isPaused]); }, [isPaused]);
// Monitor AppState to detect backgrounding/interruptions // Removed AppState listener as it was too aggressive.
useEffect(() => { // We now use duration validation at the end of recording.
const subscription = AppState.addEventListener("change", (nextAppState) => {
if (
nextAppState.match(/inactive|background/) &&
(isRecording || isPreparing)
) {
log("App backgrounded/interrupted during recording session");
interruptedRef.current = true;
// If we are merely preparing (countdown), we can just stop the timer
if (isPreparing) {
if (countdownTimerRef.current) {
clearInterval(countdownTimerRef.current);
countdownTimerRef.current = null;
}
setIsPreparing(false);
setCountdown(0);
}
// If recording, request stop immediately.
// The recording promise will resolve, and we will check interruptedRef there.
if (isRecording) {
stopRequestedRef.current = true;
stopRequestedAtRef.current = Date.now();
try {
cameraRef.current?.stopRecording?.();
} catch (_) {}
}
}
});
return () => {
subscription.remove();
};
}, [isRecording, isPreparing]);
const musicIndex = useMemo(() => { const musicIndex = useMemo(() => {
const i = Number(songIndex); const i = Number(songIndex);
@@ -495,9 +455,7 @@ const RecordPlayback = ({ route }) => {
try { try {
stopRequestedRef.current = false; stopRequestedRef.current = false;
stopRequestedAtRef.current = 0; stopRequestedAtRef.current = 0;
stopRequestedRef.current = false; recordingStartTimeRef.current = Date.now(); // Start timer
stopRequestedAtRef.current = 0;
interruptedRef.current = false; // Reset interruption flag
listenedMsRef.current = 0; listenedMsRef.current = 0;
incrementDoneRef.current = false; incrementDoneRef.current = false;
@@ -679,6 +637,7 @@ const RecordPlayback = ({ route }) => {
} }
const video = await recordPromise; const video = await recordPromise;
const recordEndTime = Date.now();
log("Recording promise resolved", { hasVideo: !!video?.uri }); log("Recording promise resolved", { hasVideo: !!video?.uri });
activeRecordingPromiseRef.current = null; activeRecordingPromiseRef.current = null;
stopRequestedRef.current = false; stopRequestedRef.current = false;
@@ -707,21 +666,32 @@ const RecordPlayback = ({ route }) => {
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 }; progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 };
const exitRequested = exitRequestedRef.current; const exitRequested = exitRequestedRef.current;
const shouldRestart = restartRequestedRef.current; const shouldRestart = restartRequestedRef.current;
const wasInterrupted = interruptedRef.current; // Duration check logic
const recordedDurationMs = recordEndTime - recordingStartTimeRef.current;
const expectedDurationMs = (player?.duration || 0) * 1000;
if (wasInterrupted) { if (!exitRequested && !shouldRestart) {
log( // If the app was backgrounded/interrupted, the camera stops early,
"Recording was interrupted (background/inactive), discarding and exiting" // resulting in a duration significantly shorter than the song.
); // We only check this if we have a valid song duration.
exitRequestedRef.current = false; // Tolerance: 1.5s
restartRequestedRef.current = false; if (
manualRestartInFlightRef.current = false; expectedDurationMs > 0 &&
interruptedRef.current = false; recordedDurationMs < expectedDurationMs - 1500
await discardRecordingFile(video?.uri, "interrupted"); ) {
try { log(
goBack(); "Recording too short compared to song duration - likely interrupted",
} catch (_) {} {
return; recordedDurationMs,
expectedDurationMs,
}
);
await discardRecordingFile(video?.uri, "too_short");
try {
goBack();
} catch (_) {}
return;
}
} }
if (exitRequested) { if (exitRequested) {
@@ -1140,7 +1110,7 @@ const RecordPlayback = ({ route }) => {
position: "absolute", position: "absolute",
left: 0, left: 0,
right: 0, right: 0,
bottom: bottom + 130, bottom: bottom + 200,
paddingHorizontal: gutters, paddingHorizontal: gutters,
}} }}
> >