diff --git a/src/components/ProfilePicture.js b/src/components/ProfilePicture.js index 583315a..300e1f4 100644 --- a/src/components/ProfilePicture.js +++ b/src/components/ProfilePicture.js @@ -56,7 +56,7 @@ const styles = StyleSheet.create({ backgroundColor: "transparent", }, fallback: { - backgroundColor: Palette.glass, + backgroundColor: "#7373736e", borderColor: Palette.ultraLightWhite, borderWidth: 2, }, diff --git a/src/components/modal/ProfileSettingsModal.js b/src/components/modal/ProfileSettingsModal.js index 763c81b..7416b08 100644 --- a/src/components/modal/ProfileSettingsModal.js +++ b/src/components/modal/ProfileSettingsModal.js @@ -1,12 +1,12 @@ -import { View, Text } from "react-native"; import React from "react"; -import AppActionSheet from "../AppActionSheet"; +import { Text, View } from "react-native"; +import { SheetManager } from "react-native-actions-sheet"; +import { Routes } from "../../navigation"; +import { navigate } from "../../navigation/NavigationService"; import { Palette } from "../../styles"; import { FONT_FAMILY } from "../../styles/Fonts"; +import AppActionSheet from "../AppActionSheet"; import BlurItemButton from "../BlurItemButton"; -import { SheetManager } from "react-native-actions-sheet"; -import { navigate } from "../../navigation/NavigationService"; -import { Routes } from "../../navigation"; const ProfileSettingsModal = () => { const onClose = async () => { @@ -27,13 +27,13 @@ const ProfileSettingsModal = () => { Paramètres - { onClose(); navigate(Routes.EditProfile); }} - /> + /> */} { diff --git a/src/providers/PlayerProvider.js b/src/providers/PlayerProvider.js index 401f568..b12482d 100644 --- a/src/providers/PlayerProvider.js +++ b/src/providers/PlayerProvider.js @@ -79,6 +79,9 @@ const DEFAULT_CONTEXT = { seekBy: noopAsync, stop: noopAsync, setQueue: noop, + isLooping: false, + setLooping: noop, + toggleLooping: noop, }; export const PlayerContext = createContext(DEFAULT_CONTEXT); @@ -215,6 +218,7 @@ const PlayerProvider = ({ children }) => { durationMs: 0, }); const [error, setError] = useState(null); + const [isLooping, setIsLooping] = useState(false); const autoPlayRef = useRef(false); const pendingSeekValueRef = useRef(null); @@ -295,6 +299,13 @@ const PlayerProvider = ({ children }) => { run(); }, [player, currentTrack?.id]); + useEffect(() => { + if (!player) return; + try { + player.loop = !!isLooping; + } catch (_err) {} + }, [player, isLooping]); + const play = useCallback( async (trackInput, options = {}) => { const normalized = normalizeTrack(trackInput, options); @@ -455,6 +466,14 @@ const PlayerProvider = ({ children }) => { } }, [player]); + const setLooping = useCallback((value) => { + setIsLooping(!!value); + }, []); + + const toggleLooping = useCallback(() => { + setIsLooping((prev) => !prev); + }, []); + const contextValue = useMemo( () => ({ currentTrack, @@ -474,6 +493,9 @@ const PlayerProvider = ({ children }) => { seekBy, stop, setQueue, + isLooping, + setLooping, + toggleLooping, }), [ currentTrack, @@ -491,6 +513,9 @@ const PlayerProvider = ({ children }) => { seekTo, shouldHideOnRoute, stop, + isLooping, + setLooping, + toggleLooping, ] ); diff --git a/src/screens/LandingPage.js b/src/screens/LandingPage.js index ad89378..d22abba 100644 --- a/src/screens/LandingPage.js +++ b/src/screens/LandingPage.js @@ -35,7 +35,7 @@ export default function LandingPage() { }, []); const handleLaunchAdventure = useCallback(() => { - navigation.navigate(Routes.Register); + navigation.navigate(Routes.Login); }, [navigation]); const { data: video } = useDataFromRef({ @@ -103,9 +103,7 @@ export default function LandingPage() { title="Présentation de Musicland" onPress={() => setVideoUrl( - Platform.OS === "web" - ? video?.landingWeb - : video?.landing, + Platform.OS === "web" ? video?.landingWeb : video?.landing ) } containerStyle={styles.actionButton} diff --git a/src/screens/Library/MusicDetails.js b/src/screens/Library/MusicDetails.js index 6b5bf8c..2b026a6 100644 --- a/src/screens/Library/MusicDetails.js +++ b/src/screens/Library/MusicDetails.js @@ -1,4 +1,6 @@ +import { Feather } from "@expo/vector-icons"; import useTrackController from "../../hooks/useTrackController"; +import usePlayer from "../../hooks/usePlayer"; import { Image as ExpoImage } from "expo-image"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { @@ -58,6 +60,7 @@ const MusicDetails = ({ route }) => { const incrementDoneRef = useRef(false); const timerRef = useRef(null); const hasAutoPlayedRef = useRef(false); + const { isLooping, setLooping } = usePlayer() || {}; const { data: project } = useDataFromRef({ ref: projectId ? projectsRef.doc(projectId) : null, @@ -193,6 +196,7 @@ const MusicDetails = ({ route }) => { seekTo: seekTrackTo, seekBy: seekTrackBy, } = useTrackController(trackDescriptor); + const loopEnabled = isCurrentTrack && !!isLooping; // Karaoke aligned words (from timestamps) const alignedWords = useMemo(() => { @@ -325,6 +329,31 @@ const MusicDetails = ({ route }) => { [durationMs, trackDescriptor, isCurrentTrack, ensureLoaded, seekTrackTo] ); + const handleToggleLoop = useCallback(async () => { + if (!isCurrentTrack) return; + const next = !loopEnabled; + if (typeof setLooping === "function") { + setLooping(next); + } + if (next) { + try { + await seekTrackTo(0); + if (!isTrackPlaying) { + await resumeTrack(); + } + } catch (e) { + console.log("MusicDetails loop toggle error", e?.message); + } + } + }, [ + isCurrentTrack, + loopEnabled, + setLooping, + seekTrackTo, + isTrackPlaying, + resumeTrack, + ]); + useEffect(() => { if (!autoPlayRequested || hasAutoPlayedRef.current) return; if (!trackDescriptor || !songUrl) return; @@ -746,6 +775,24 @@ const MusicDetails = ({ route }) => { {artist} + + + { if (!projectId || !currentUID) return; diff --git a/src/screens/Library/MusicDetails.web.js b/src/screens/Library/MusicDetails.web.js index 4cb8b4a..8bebb08 100644 --- a/src/screens/Library/MusicDetails.web.js +++ b/src/screens/Library/MusicDetails.web.js @@ -1,5 +1,6 @@ import { BlurView } from "expo-blur"; import { Image as ExpoImage } from "expo-image"; +import { Feather } from "@expo/vector-icons"; import React, { useCallback, useEffect, @@ -33,6 +34,7 @@ import { } from "../../config/firebase"; import useDataFromRef from "../../hooks/useDataFromRef"; import useTrackController from "../../hooks/useTrackController"; +import usePlayer from "../../hooks/usePlayer"; import Page from "../../layouts/Page"; import { Palette } from "../../styles"; import { FONT_FAMILY } from "../../styles/Fonts"; @@ -67,6 +69,7 @@ const MusicDetails = ({ route }) => { const incrementDoneRef = React.useRef(false); const timerRef = React.useRef(null); const hasAutoPlayedRef = React.useRef(false); + const { isLooping, setLooping } = usePlayer() || {}; const { data: project } = useDataFromRef({ ref: projectId ? projectsRef.doc(projectId) : null, @@ -275,6 +278,7 @@ const MusicDetails = ({ route }) => { } = useTrackController(trackDescriptor); const isPlaying = isTrackPlaying; + const loopEnabled = isCurrentTrack && !!isLooping; // Karaoke aligned words (from timestamps) const alignedWords = useMemo(() => { @@ -429,6 +433,31 @@ const MusicDetails = ({ route }) => { [trackDescriptor, durationMs, isCurrentTrack, ensureLoaded, seekTrackTo] ); + const handleToggleLoop = useCallback(async () => { + if (!isCurrentTrack) return; + const next = !loopEnabled; + if (typeof setLooping === "function") { + setLooping(next); + } + if (next) { + try { + await seekTrackTo(0); + if (!isPlaying) { + await resumeTrack(); + } + } catch (e) { + console.log("MusicDetails.web loop toggle error", e?.message); + } + } + }, [ + isCurrentTrack, + loopEnabled, + setLooping, + seekTrackTo, + isPlaying, + resumeTrack, + ]); + useEffect(() => { if (!autoPlayRequested || hasAutoPlayedRef.current) { return; @@ -926,6 +955,27 @@ const MusicDetails = ({ route }) => { {artist} + + + { if (!projectId || !currentUID) return; @@ -972,9 +1022,7 @@ const MusicDetails = ({ route }) => { handleSeekBySeconds(-10)}> {/* Play / Pause */} @@ -987,7 +1035,7 @@ const MusicDetails = ({ route }) => { onPress={togglePlay} > diff --git a/src/screens/Profile/Profile.js b/src/screens/Profile/Profile.js index d06cfc2..8a83053 100644 --- a/src/screens/Profile/Profile.js +++ b/src/screens/Profile/Profile.js @@ -34,11 +34,11 @@ import Page from "../../layouts/Page"; import { Routes } from "../../navigation"; import { push } from "../../navigation/NavigationService"; import { useUser } from "../../providers/UserDataProvider"; -import { getArtistDisplayName } from "../../utils/artistName"; -import { getProjectLikes, LIKE_TARGET } from "../../utils/likes"; import { Palette } from "../../styles"; import { FONT_FAMILY } from "../../styles/Fonts"; import Style, { gutters, size } from "../../styles/Style"; +import { getArtistDisplayName } from "../../utils/artistName"; +import { getProjectLikes, LIKE_TARGET } from "../../utils/likes"; import MusicCard from "../Library/components/MusicCard"; const Profile = () => { const { params } = useRoute(); @@ -321,9 +321,7 @@ const Profile = () => { }); const displayedProjects = isSelf ? selfProjects : otherUserProjects; - const displayedPlaybacksSource = isSelf - ? selfPlaybacks - : otherUserProjects; + const displayedPlaybacksSource = isSelf ? selfPlaybacks : otherUserProjects; const displayedPlaybacks = Array.isArray(displayedPlaybacksSource) ? displayedPlaybacksSource.filter((project) => project?.playbackUrl) : []; @@ -419,12 +417,12 @@ const Profile = () => { {webUploadMessage} ) : null} + style={{ + fontSize: 22, + color: Palette.white, + fontFamily: FONT_FAMILY.InterSemiBold, + }} + > {profileTitle}