replay musics
This commit is contained in:
@@ -56,7 +56,7 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
},
|
},
|
||||||
fallback: {
|
fallback: {
|
||||||
backgroundColor: Palette.glass,
|
backgroundColor: "#7373736e",
|
||||||
borderColor: Palette.ultraLightWhite,
|
borderColor: Palette.ultraLightWhite,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { View, Text } from "react-native";
|
|
||||||
import React from "react";
|
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 { Palette } from "../../styles";
|
||||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||||
|
import AppActionSheet from "../AppActionSheet";
|
||||||
import BlurItemButton from "../BlurItemButton";
|
import BlurItemButton from "../BlurItemButton";
|
||||||
import { SheetManager } from "react-native-actions-sheet";
|
|
||||||
import { navigate } from "../../navigation/NavigationService";
|
|
||||||
import { Routes } from "../../navigation";
|
|
||||||
|
|
||||||
const ProfileSettingsModal = () => {
|
const ProfileSettingsModal = () => {
|
||||||
const onClose = async () => {
|
const onClose = async () => {
|
||||||
@@ -27,13 +27,13 @@ const ProfileSettingsModal = () => {
|
|||||||
Paramètres
|
Paramètres
|
||||||
</Text>
|
</Text>
|
||||||
<View style={{ gap: 10 }}>
|
<View style={{ gap: 10 }}>
|
||||||
<BlurItemButton
|
{/* <BlurItemButton
|
||||||
title="Modifier mon profil"
|
title="Modifier mon profil"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
onClose();
|
onClose();
|
||||||
navigate(Routes.EditProfile);
|
navigate(Routes.EditProfile);
|
||||||
}}
|
}}
|
||||||
/>
|
/> */}
|
||||||
<BlurItemButton
|
<BlurItemButton
|
||||||
title="Paramètres"
|
title="Paramètres"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ const DEFAULT_CONTEXT = {
|
|||||||
seekBy: noopAsync,
|
seekBy: noopAsync,
|
||||||
stop: noopAsync,
|
stop: noopAsync,
|
||||||
setQueue: noop,
|
setQueue: noop,
|
||||||
|
isLooping: false,
|
||||||
|
setLooping: noop,
|
||||||
|
toggleLooping: noop,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PlayerContext = createContext(DEFAULT_CONTEXT);
|
export const PlayerContext = createContext(DEFAULT_CONTEXT);
|
||||||
@@ -215,6 +218,7 @@ const PlayerProvider = ({ children }) => {
|
|||||||
durationMs: 0,
|
durationMs: 0,
|
||||||
});
|
});
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
const [isLooping, setIsLooping] = useState(false);
|
||||||
|
|
||||||
const autoPlayRef = useRef(false);
|
const autoPlayRef = useRef(false);
|
||||||
const pendingSeekValueRef = useRef(null);
|
const pendingSeekValueRef = useRef(null);
|
||||||
@@ -295,6 +299,13 @@ const PlayerProvider = ({ children }) => {
|
|||||||
run();
|
run();
|
||||||
}, [player, currentTrack?.id]);
|
}, [player, currentTrack?.id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!player) return;
|
||||||
|
try {
|
||||||
|
player.loop = !!isLooping;
|
||||||
|
} catch (_err) {}
|
||||||
|
}, [player, isLooping]);
|
||||||
|
|
||||||
const play = useCallback(
|
const play = useCallback(
|
||||||
async (trackInput, options = {}) => {
|
async (trackInput, options = {}) => {
|
||||||
const normalized = normalizeTrack(trackInput, options);
|
const normalized = normalizeTrack(trackInput, options);
|
||||||
@@ -455,6 +466,14 @@ const PlayerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}, [player]);
|
}, [player]);
|
||||||
|
|
||||||
|
const setLooping = useCallback((value) => {
|
||||||
|
setIsLooping(!!value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleLooping = useCallback(() => {
|
||||||
|
setIsLooping((prev) => !prev);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const contextValue = useMemo(
|
const contextValue = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
currentTrack,
|
currentTrack,
|
||||||
@@ -474,6 +493,9 @@ const PlayerProvider = ({ children }) => {
|
|||||||
seekBy,
|
seekBy,
|
||||||
stop,
|
stop,
|
||||||
setQueue,
|
setQueue,
|
||||||
|
isLooping,
|
||||||
|
setLooping,
|
||||||
|
toggleLooping,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
currentTrack,
|
currentTrack,
|
||||||
@@ -491,6 +513,9 @@ const PlayerProvider = ({ children }) => {
|
|||||||
seekTo,
|
seekTo,
|
||||||
shouldHideOnRoute,
|
shouldHideOnRoute,
|
||||||
stop,
|
stop,
|
||||||
|
isLooping,
|
||||||
|
setLooping,
|
||||||
|
toggleLooping,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default function LandingPage() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleLaunchAdventure = useCallback(() => {
|
const handleLaunchAdventure = useCallback(() => {
|
||||||
navigation.navigate(Routes.Register);
|
navigation.navigate(Routes.Login);
|
||||||
}, [navigation]);
|
}, [navigation]);
|
||||||
|
|
||||||
const { data: video } = useDataFromRef({
|
const { data: video } = useDataFromRef({
|
||||||
@@ -103,9 +103,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}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { Feather } from "@expo/vector-icons";
|
||||||
import useTrackController from "../../hooks/useTrackController";
|
import useTrackController from "../../hooks/useTrackController";
|
||||||
|
import usePlayer from "../../hooks/usePlayer";
|
||||||
import { Image as ExpoImage } from "expo-image";
|
import { Image as ExpoImage } from "expo-image";
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -58,6 +60,7 @@ const MusicDetails = ({ route }) => {
|
|||||||
const incrementDoneRef = useRef(false);
|
const incrementDoneRef = useRef(false);
|
||||||
const timerRef = useRef(null);
|
const timerRef = useRef(null);
|
||||||
const hasAutoPlayedRef = useRef(false);
|
const hasAutoPlayedRef = useRef(false);
|
||||||
|
const { isLooping, setLooping } = usePlayer() || {};
|
||||||
|
|
||||||
const { data: project } = useDataFromRef({
|
const { data: project } = useDataFromRef({
|
||||||
ref: projectId ? projectsRef.doc(projectId) : null,
|
ref: projectId ? projectsRef.doc(projectId) : null,
|
||||||
@@ -193,6 +196,7 @@ const MusicDetails = ({ route }) => {
|
|||||||
seekTo: seekTrackTo,
|
seekTo: seekTrackTo,
|
||||||
seekBy: seekTrackBy,
|
seekBy: seekTrackBy,
|
||||||
} = useTrackController(trackDescriptor);
|
} = useTrackController(trackDescriptor);
|
||||||
|
const loopEnabled = isCurrentTrack && !!isLooping;
|
||||||
|
|
||||||
// Karaoke aligned words (from timestamps)
|
// Karaoke aligned words (from timestamps)
|
||||||
const alignedWords = useMemo(() => {
|
const alignedWords = useMemo(() => {
|
||||||
@@ -325,6 +329,31 @@ const MusicDetails = ({ route }) => {
|
|||||||
[durationMs, trackDescriptor, isCurrentTrack, ensureLoaded, seekTrackTo]
|
[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(() => {
|
useEffect(() => {
|
||||||
if (!autoPlayRequested || hasAutoPlayedRef.current) return;
|
if (!autoPlayRequested || hasAutoPlayedRef.current) return;
|
||||||
if (!trackDescriptor || !songUrl) return;
|
if (!trackDescriptor || !songUrl) return;
|
||||||
@@ -746,6 +775,24 @@ const MusicDetails = ({ route }) => {
|
|||||||
<Text style={styles.name}>{artist}</Text>
|
<Text style={styles.name}>{artist}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={{ ...Style.containerRow, gap: 16 }}>
|
<View style={{ ...Style.containerRow, gap: 16 }}>
|
||||||
|
<PressableScale
|
||||||
|
onPress={handleToggleLoop}
|
||||||
|
disabled={!isCurrentTrack}
|
||||||
|
hitSlop={10}
|
||||||
|
style={{
|
||||||
|
...size({ size: 32 }),
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
opacity: isCurrentTrack ? 1 : 0.4,
|
||||||
|
}}
|
||||||
|
contentStyle={{ alignItems: "center", justifyContent: "center" }}
|
||||||
|
>
|
||||||
|
<Feather
|
||||||
|
name="repeat"
|
||||||
|
size={24}
|
||||||
|
color={loopEnabled ? Palette.primary : Palette.white}
|
||||||
|
/>
|
||||||
|
</PressableScale>
|
||||||
<PressableScale
|
<PressableScale
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
if (!projectId || !currentUID) return;
|
if (!projectId || !currentUID) return;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { BlurView } from "expo-blur";
|
import { BlurView } from "expo-blur";
|
||||||
import { Image as ExpoImage } from "expo-image";
|
import { Image as ExpoImage } from "expo-image";
|
||||||
|
import { Feather } from "@expo/vector-icons";
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@@ -33,6 +34,7 @@ import {
|
|||||||
} from "../../config/firebase";
|
} from "../../config/firebase";
|
||||||
import useDataFromRef from "../../hooks/useDataFromRef";
|
import useDataFromRef from "../../hooks/useDataFromRef";
|
||||||
import useTrackController from "../../hooks/useTrackController";
|
import useTrackController from "../../hooks/useTrackController";
|
||||||
|
import usePlayer from "../../hooks/usePlayer";
|
||||||
import Page from "../../layouts/Page";
|
import Page from "../../layouts/Page";
|
||||||
import { Palette } from "../../styles";
|
import { Palette } from "../../styles";
|
||||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||||
@@ -67,6 +69,7 @@ const MusicDetails = ({ route }) => {
|
|||||||
const incrementDoneRef = React.useRef(false);
|
const incrementDoneRef = React.useRef(false);
|
||||||
const timerRef = React.useRef(null);
|
const timerRef = React.useRef(null);
|
||||||
const hasAutoPlayedRef = React.useRef(false);
|
const hasAutoPlayedRef = React.useRef(false);
|
||||||
|
const { isLooping, setLooping } = usePlayer() || {};
|
||||||
|
|
||||||
const { data: project } = useDataFromRef({
|
const { data: project } = useDataFromRef({
|
||||||
ref: projectId ? projectsRef.doc(projectId) : null,
|
ref: projectId ? projectsRef.doc(projectId) : null,
|
||||||
@@ -275,6 +278,7 @@ const MusicDetails = ({ route }) => {
|
|||||||
} = useTrackController(trackDescriptor);
|
} = useTrackController(trackDescriptor);
|
||||||
|
|
||||||
const isPlaying = isTrackPlaying;
|
const isPlaying = isTrackPlaying;
|
||||||
|
const loopEnabled = isCurrentTrack && !!isLooping;
|
||||||
|
|
||||||
// Karaoke aligned words (from timestamps)
|
// Karaoke aligned words (from timestamps)
|
||||||
const alignedWords = useMemo(() => {
|
const alignedWords = useMemo(() => {
|
||||||
@@ -429,6 +433,31 @@ const MusicDetails = ({ route }) => {
|
|||||||
[trackDescriptor, durationMs, isCurrentTrack, ensureLoaded, seekTrackTo]
|
[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(() => {
|
useEffect(() => {
|
||||||
if (!autoPlayRequested || hasAutoPlayedRef.current) {
|
if (!autoPlayRequested || hasAutoPlayedRef.current) {
|
||||||
return;
|
return;
|
||||||
@@ -926,6 +955,27 @@ const MusicDetails = ({ route }) => {
|
|||||||
<Text style={styles.name}>{artist}</Text>
|
<Text style={styles.name}>{artist}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={{ ...Style.containerRow, gap: 12 }}>
|
<View style={{ ...Style.containerRow, gap: 12 }}>
|
||||||
|
<PressableScale
|
||||||
|
onPress={handleToggleLoop}
|
||||||
|
disabled={!isCurrentTrack}
|
||||||
|
hitSlop={10}
|
||||||
|
style={{
|
||||||
|
...size({ size: 32 }),
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
opacity: isCurrentTrack ? 1 : 0.4,
|
||||||
|
}}
|
||||||
|
contentStyle={{
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Feather
|
||||||
|
name="repeat"
|
||||||
|
size={24}
|
||||||
|
color={loopEnabled ? Palette.primary : Palette.white}
|
||||||
|
/>
|
||||||
|
</PressableScale>
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
if (!projectId || !currentUID) return;
|
if (!projectId || !currentUID) return;
|
||||||
@@ -972,9 +1022,7 @@ const MusicDetails = ({ route }) => {
|
|||||||
<Pressable onPress={() => handleSeekBySeconds(-10)}>
|
<Pressable onPress={() => handleSeekBySeconds(-10)}>
|
||||||
<RNImage
|
<RNImage
|
||||||
source={icons.forward}
|
source={icons.forward}
|
||||||
style={{
|
style={{ ...size({ size: 30 }) }}
|
||||||
...size({ size: 30 }),
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
{/* Play / Pause */}
|
{/* Play / Pause */}
|
||||||
@@ -987,7 +1035,7 @@ const MusicDetails = ({ route }) => {
|
|||||||
onPress={togglePlay}
|
onPress={togglePlay}
|
||||||
>
|
>
|
||||||
<RNImage
|
<RNImage
|
||||||
resizeMode={"contain"}
|
resizeMode="contain"
|
||||||
source={isPlaying ? icons.pause : icons.play}
|
source={isPlaying ? icons.pause : icons.play}
|
||||||
style={size({ size: 34 })}
|
style={size({ size: 34 })}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ import Page from "../../layouts/Page";
|
|||||||
import { Routes } from "../../navigation";
|
import { Routes } from "../../navigation";
|
||||||
import { push } from "../../navigation/NavigationService";
|
import { push } from "../../navigation/NavigationService";
|
||||||
import { useUser } from "../../providers/UserDataProvider";
|
import { useUser } from "../../providers/UserDataProvider";
|
||||||
import { getArtistDisplayName } from "../../utils/artistName";
|
|
||||||
import { getProjectLikes, LIKE_TARGET } from "../../utils/likes";
|
|
||||||
import { Palette } from "../../styles";
|
import { Palette } from "../../styles";
|
||||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||||
import Style, { gutters, size } from "../../styles/Style";
|
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";
|
import MusicCard from "../Library/components/MusicCard";
|
||||||
const Profile = () => {
|
const Profile = () => {
|
||||||
const { params } = useRoute();
|
const { params } = useRoute();
|
||||||
@@ -321,9 +321,7 @@ const Profile = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const displayedProjects = isSelf ? selfProjects : otherUserProjects;
|
const displayedProjects = isSelf ? selfProjects : otherUserProjects;
|
||||||
const displayedPlaybacksSource = isSelf
|
const displayedPlaybacksSource = isSelf ? selfPlaybacks : otherUserProjects;
|
||||||
? selfPlaybacks
|
|
||||||
: otherUserProjects;
|
|
||||||
const displayedPlaybacks = Array.isArray(displayedPlaybacksSource)
|
const displayedPlaybacks = Array.isArray(displayedPlaybacksSource)
|
||||||
? displayedPlaybacksSource.filter((project) => project?.playbackUrl)
|
? displayedPlaybacksSource.filter((project) => project?.playbackUrl)
|
||||||
: [];
|
: [];
|
||||||
|
|||||||
Reference in New Issue
Block a user