diff --git a/src/assets/index.js b/src/assets/index.js index 12a95b5..1d0550e 100644 --- a/src/assets/index.js +++ b/src/assets/index.js @@ -210,10 +210,6 @@ export const tutorial = { tasks: tutorialTasks, }; -export const mockups = { - loginAppDashboard, -}; - export const background = { writingBG, studioBG, diff --git a/src/hooks/useDataFromRef.js b/src/hooks/useDataFromRef.js index a345d13..88549bb 100644 --- a/src/hooks/useDataFromRef.js +++ b/src/hooks/useDataFromRef.js @@ -55,9 +55,6 @@ export default function useDataFromRef({ if (!_.isEqual(data, initialState)) { onUpdate(initialState); } - - console.log("Reset state", initialState); - setEndReached(false); setLastVisible(null); setData(initialState); @@ -77,10 +74,10 @@ export default function useDataFromRef({ paginate && lastVisible ? ref.startAfter(lastVisible).limit(batchSize) : initialDoc - ? ref.startAt(initialDoc).limit(batchSize) - : paginate - ? ref.limit(batchSize) - : ref; + ? ref.startAt(initialDoc).limit(batchSize) + : paginate + ? ref.limit(batchSize) + : ref; const dataSnap = await dynamicRef.get(); @@ -113,7 +110,7 @@ export default function useDataFromRef({ if (e.code === "firestore/permission-denied") { console.warn( "Permission denied for ref: ", - ref?._collectionPath?.relativeName + ref?._collectionPath?.relativeName, ); } else { console.log(e); @@ -143,14 +140,14 @@ export default function useDataFromRef({ if (e.code === "firestore/permission-denied") { console.warn( "Permission denied for ref: ", - ref?._collectionPath?.relativeName + ref?._collectionPath?.relativeName, ); } else { console.log(e); } await updateData([]); setLoading(false); - } + }, ); }; diff --git a/src/screens/Library/Library.js b/src/screens/Library/Library.js index d0c26ac..5f51be1 100644 --- a/src/screens/Library/Library.js +++ b/src/screens/Library/Library.js @@ -40,10 +40,10 @@ const Library = () => { - + {/**/} - + {/**/} diff --git a/src/screens/Library/MusicDetails.js b/src/screens/Library/MusicDetails.js index ee86413..647cbaa 100644 --- a/src/screens/Library/MusicDetails.js +++ b/src/screens/Library/MusicDetails.js @@ -1,7 +1,14 @@ import { useRoute } from "@react-navigation/core"; -import { Audio } from "expo-audio"; +import { useAudioPlayer } from "expo-audio"; import React, { useEffect, useMemo, useState } from "react"; -import { Pressable, ScrollView, StyleSheet, Text, View, Image as RNImage } from "react-native"; +import { + Pressable, + ScrollView, + StyleSheet, + Text, + View, + Image as RNImage, +} from "react-native"; import { Image as ExpoImage } from "expo-image"; import { SheetManager } from "react-native-actions-sheet"; import { useGlobal } from "reactn"; @@ -19,6 +26,10 @@ import Page from "../../layouts/Page"; import { Palette } from "../../styles"; import { FONT_FAMILY } from "../../styles/Fonts"; import Style, { gutters, size } from "../../styles/Style"; +import { + responsiveHeight, + responsiveWidth, +} from "react-native-responsive-dimensions"; // 20 secondes const timeBeforeIncrement = 20000; @@ -37,9 +48,7 @@ const MusicDetails = () => { const timerRef = React.useRef(null); const { data: project } = useDataFromRef({ - ref: projectId - ? firebase.firestore().collection("projects").doc(projectId) - : null, + ref: projectId ? projectsRef.doc(projectId) : null, simpleRef: true, listener: true, condition: !!projectId, @@ -64,61 +73,27 @@ const MusicDetails = () => { const title = project?.title || "Sans titre"; const artist = owner?.userName || "MusicLand"; const coverUrl = project?.coverUrl || null; - const songUrl = useMemo(() => { - if (project?.song?.url) return project.song.url; - const arr = Array.isArray(project?.musicUrls) ? project.musicUrls : []; - return arr[0] || null; - }, [project]); + const songUrl = project?.songUrl || null; - const soundRef = React.useRef(null); + const player = useAudioPlayer(songUrl ? { uri: songUrl } : undefined); - // Load/unload audio with expo-audio for reliable status updates on iOS/Android + // Reset counters when the track changes useEffect(() => { - let isMounted = true; - const load = async () => { - try { - // Unload previous - if (soundRef.current) { - await soundRef.current.unloadAsync(); - soundRef.current.setOnPlaybackStatusUpdate(null); - soundRef.current = null; - } - // Reset listen tracking per song load - listenedMsRef.current = 0; - incrementDoneRef.current = false; - if (!songUrl) return; - const { sound } = await Audio.Sound.createAsync( - { uri: songUrl }, - { shouldPlay: false } - ); - sound.setOnPlaybackStatusUpdate((status) => { - if (!isMounted) return; - if (!status || !status.isLoaded) return; - const pos = status.positionMillis || 0; - const dur = status.durationMillis || 0; - setProgressInfo({ pos, dur }); - setIsPlaying(!!status.isPlaying); - }); - soundRef.current = sound; - } catch (e) { - console.log("Audio load error", e?.message); - } - }; - load(); - return () => { - isMounted = false; - (async () => { - try { - if (soundRef.current) { - await soundRef.current.unloadAsync(); - soundRef.current.setOnPlaybackStatusUpdate(null); - soundRef.current = null; - } - } catch (_) {} - })(); - }; + listenedMsRef.current = 0; + incrementDoneRef.current = false; }, [songUrl]); + // Poll player state to update progress and play state + useEffect(() => { + const id = setInterval(() => { + const dur = (player?.duration || 0) * 1000; + const pos = (player?.currentTime || 0) * 1000; + setProgressInfo({ pos, dur }); + setIsPlaying(!!player?.playing); + }, 300); + return () => clearInterval(id); + }, [player]); + // Start/stop a timer to accumulate listened milliseconds while playing useEffect(() => { const clearTimer = () => { @@ -163,15 +138,13 @@ const MusicDetails = () => { }; const togglePlay = async () => { - const sound = soundRef.current; - if (!sound || !songUrl) return; + if (!player || !songUrl) return; try { - const status = await sound.getStatusAsync(); - if (status?.isLoaded && status.isPlaying) { - await sound.pauseAsync(); + if (player.playing) { + await player.pause?.(); setIsPlaying(false); } else { - await sound.playAsync(); + await player.play?.(); setIsPlaying(true); } } catch (e) { @@ -183,9 +156,8 @@ const MusicDetails = () => { try { const dur = progressInfo.dur || 0; const pos = Math.floor(dur * ratio); - const sound = soundRef.current; - if (sound && dur > 0) { - await sound.setPositionAsync(pos); + if (player && dur > 0) { + await player.seekTo?.(Math.floor((pos || 0) / 1000)); } } catch (e) { console.log("MusicDetails seek error", e?.message); @@ -196,8 +168,7 @@ const MusicDetails = () => { try { const cur = Math.floor((progressInfo.pos || 0) / 1000); const next = Math.max(0, cur + deltaSeconds); - const sound = soundRef.current; - if (sound) await sound.setPositionAsync(next * 1000); + if (player) await player.seekTo?.(next); } catch (e) { console.log("MusicDetails seekBy error", e?.message); } @@ -241,10 +212,9 @@ const MusicDetails = () => { paddingTop: 20, paddingBottom: gutters * 2, }} - // stickyHeaderIndices={[1]} > - {coverUrl ? ( + {coverUrl && ( { transition={150} style={styles.img} /> - ) : ( - )} @@ -298,79 +266,81 @@ const MusicDetails = () => { - - { - try { - const sound = soundRef.current; - const status = await sound?.getStatusAsync?.(); - wasPlayingBeforeSeek.current = - !!status?.isLoaded && !!status?.isPlaying; - if (status?.isLoaded && status?.isPlaying) { - await sound.pauseAsync(); - setIsPlaying(false); - } - } catch (e) { - console.log("Pause on seek start error", e?.message); + {songUrl && ( + + { - try { - const sound = soundRef.current; - if (sound && wasPlayingBeforeSeek.current) { - await sound.playAsync(); - setIsPlaying(true); + seekEnabled={!!songUrl} + onSeekStart={async () => { + try { + wasPlayingBeforeSeek.current = !!player?.playing; + if (player?.playing) { + await player.pause?.(); + setIsPlaying(false); + } + } catch (e) { + console.log("Pause on seek start error", e?.message); } - wasPlayingBeforeSeek.current = false; - } catch (e) { - console.log("Resume after seek error", e?.message); - } - }} - /> - - {/* Previous (rewind 10s) */} - seekBy(-10)}> - - - {/* Play / Pause */} - { + try { + if (player && wasPlayingBeforeSeek.current) { + await player.play?.(); + setIsPlaying(true); + } + wasPlayingBeforeSeek.current = false; + } catch (e) { + console.log("Resume after seek error", e?.message); + } + }} + /> + - - - {/* Next (forward 10s) */} - seekBy(10)}> - seekBy(-10)}> + + + {/* Play / Pause */} + - + onPress={togglePlay} + > + + + {/* Next (forward 10s) */} + seekBy(10)}> + + + - + )} {description?.length > 0 && ( { /> - {(!selected && - !projectsLoading && - !usersLoading && - filteredProjects.length === 0 && - filteredUsers.length === 0) && ( - - )} {((!selected && (filteredProjects.length > 0 || projectsLoading)) || selected === "Musiques") && ( diff --git a/src/screens/Library/components/MusicCard.js b/src/screens/Library/components/MusicCard.js index 912afe9..9593c53 100644 --- a/src/screens/Library/components/MusicCard.js +++ b/src/screens/Library/components/MusicCard.js @@ -1,6 +1,13 @@ import { BlurView } from "expo-blur"; import React, { useEffect, useRef, useState } from "react"; -import { Platform, Pressable, StyleSheet, Text, View, Image as RNImage } from "react-native"; +import { + Platform, + Pressable, + StyleSheet, + Text, + View, + Image as RNImage, +} from "react-native"; import { Image as ExpoImage } from "expo-image"; import { useGlobal } from "reactn"; import { icons, img } from "../../../assets"; @@ -8,6 +15,7 @@ import { arrayRemove, arrayUnion, projectsRef } from "../../../config/firebase"; import { Palette, Style } from "../../../styles"; import { FONT_FAMILY } from "../../../styles/Fonts"; import { size } from "../../../styles/Style"; +import { responsiveWidth } from "react-native-responsive-dimensions"; const MusicCard = ({ onPress, @@ -90,7 +98,7 @@ const MusicCard = ({ /> ) : ( )} @@ -107,7 +115,9 @@ const MusicCard = ({ } > - {title || "Sans titre"} + + {title || "Sans titre"} + {subtitle || "MusicLand"} { if (isLocked(index)) return; switch (index) { case 0: - navigate(Routes.WritingLyrics, {}); + navigate(Routes.WritingLyrics, { + projectId: currentProjet?.id || null, + }); break; case 1: - navigate(Routes.Studio, { action: item.type }); + navigate(Routes.Compose, { projectId: currentProjet.id }); break; case 2: - navigate(Routes.Production, { action: item.type }); + navigate(Routes.PouchReady, { projectId: currentProjet.id }); break; default: + navigate(Routes.PouchReady, { projectId: currentProjet.id }); break; } }; diff --git a/src/screens/Studio/SongReady.js b/src/screens/Studio/SongReady.js index 677fbf5..b22102d 100644 --- a/src/screens/Studio/SongReady.js +++ b/src/screens/Studio/SongReady.js @@ -124,7 +124,7 @@ const SongReady = () => { .doc(projectId) .set( { - song: { index: selectedIndex, url }, + songUrl: url, updatedAt: firebase.firestore.FieldValue.serverTimestamp(), }, { merge: true },