feat: play from dropdown

This commit is contained in:
2025-12-03 16:39:26 +01:00
parent 13f34d3ef1
commit 74e02fef79
3 changed files with 169 additions and 130 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

+122 -107
View File
@@ -1,8 +1,10 @@
import { BlurView } from "expo-blur"; import { BlurView } from "expo-blur";
import { Portal } from "@gorhom/portal"; import { Portal } from "@gorhom/portal";
import React, { import React, {
forwardRef,
useCallback, useCallback,
useEffect, useEffect,
useImperativeHandle,
useMemo, useMemo,
useRef, useRef,
useState, useState,
@@ -78,21 +80,25 @@ const defaultFormatDate = (timestamp) => {
} }
}; };
const ProjectDropDown = ({ const ProjectDropDown = forwardRef(
style, (
projects = [], {
selectedProject, style,
onSelectProject, projects = [],
onModifyProject, selectedProject,
onCreateProject, onSelectProject,
formatDate = defaultFormatDate, onModifyProject,
allowEmptySelection = false, onCreateProject,
}) => { formatDate = defaultFormatDate,
const [isOpen, setIsOpen] = useState(false); allowEmptySelection = false,
const [triggerLayout, setTriggerLayout] = useState(null); },
const [triggerWindowLayout, setTriggerWindowLayout] = useState(null); ref
const triggerRef = useRef(null); ) => {
const dropdownAnimation = useRef(new Animated.Value(0)).current; const [isOpen, setIsOpen] = useState(false);
const [triggerLayout, setTriggerLayout] = useState(null);
const [triggerWindowLayout, setTriggerWindowLayout] = useState(null);
const triggerRef = useRef(null);
const dropdownAnimation = useRef(new Animated.Value(0)).current;
const measureTriggerPosition = useCallback(() => { const measureTriggerPosition = useCallback(() => {
if (triggerRef.current?.measureInWindow) { if (triggerRef.current?.measureInWindow) {
@@ -133,39 +139,47 @@ const ProjectDropDown = ({
}) })
: normalizedProjects; : normalizedProjects;
const toggleDropdown = useCallback(() => { const toggleDropdown = useCallback(() => {
if (isDisabled) { if (isDisabled) {
return; return;
} }
if (!isOpen) { if (!isOpen) {
measureTriggerPosition(); measureTriggerPosition();
} }
setIsOpen((prev) => !prev); setIsOpen((prev) => !prev);
}, [isDisabled, isOpen, measureTriggerPosition]); }, [isDisabled, isOpen, measureTriggerPosition]);
const closeDropdown = useCallback(() => setIsOpen(false), []); const closeDropdown = useCallback(() => setIsOpen(false), []);
const handleSelect = useCallback( useImperativeHandle(
(project) => { ref,
if (!project?.id) return; () => ({
onSelectProject?.(project); close: closeDropdown,
}),
[closeDropdown]
);
const handleSelect = useCallback(
(project) => {
if (!project?.id) return;
onSelectProject?.(project);
closeDropdown();
},
[closeDropdown, onSelectProject]
);
const handleModify = useCallback(
(project, anchor) => {
if (!project?.id) return;
onModifyProject?.(project, anchor);
},
[onModifyProject]
);
const handleCreate = useCallback(() => {
onCreateProject?.();
closeDropdown(); closeDropdown();
}, }, [closeDropdown, onCreateProject]);
[closeDropdown, onSelectProject]
);
const handleModify = useCallback(
(project, anchor) => {
if (!project?.id) return;
onModifyProject?.(project, anchor);
},
[onModifyProject]
);
const handleCreate = useCallback(() => {
onCreateProject?.();
closeDropdown();
}, [closeDropdown, onCreateProject]);
const dropdownWidth = useMemo( const dropdownWidth = useMemo(
() => triggerLayout?.width || 0, () => triggerLayout?.width || 0,
@@ -286,74 +300,75 @@ const ProjectDropDown = ({
</ThemedBlurView> </ThemedBlurView>
); );
return ( return (
<View style={[styles.container, style]}> <View style={[styles.container, style]}>
<View <View
ref={triggerRef} ref={triggerRef}
style={styles.triggerWrapper} style={styles.triggerWrapper}
onLayout={(event) => { onLayout={(event) => {
setTriggerLayout(event.nativeEvent.layout); setTriggerLayout(event.nativeEvent.layout);
measureTriggerPosition(); measureTriggerPosition();
}} }}
>
<Animated.View
pointerEvents={isOpen ? "none" : "auto"}
style={[
styles.dropdownTriggerContainer,
{
opacity: triggerOpacity,
transform: [{ translateY: triggerTranslateY }],
},
]}
> >
<ThemedBlurView <Animated.View
pointerEvents={isOpen ? "none" : "auto"}
style={[ style={[
styles.dropdownBlur, styles.dropdownTriggerContainer,
isDisabled && styles.dropdownBlurDisabled, {
opacity: triggerOpacity,
transform: [{ translateY: triggerTranslateY }],
},
]} ]}
> >
<ProjectRow <ThemedBlurView
isTitle={true} style={[
isOpen={isOpen} styles.dropdownBlur,
project={currentProject} isDisabled && styles.dropdownBlurDisabled,
formatDate={formatDate} ]}
onSelect={toggleDropdown} >
/> <ProjectRow
</ThemedBlurView> isTitle={true}
</Animated.View> isOpen={isOpen}
project={currentProject}
formatDate={formatDate}
onSelect={toggleDropdown}
/>
</ThemedBlurView>
</Animated.View>
{isOpen && triggerWindowLayout ? ( {isOpen && triggerWindowLayout ? (
<Portal> <Portal>
<View style={styles.portalContainer}> <View style={styles.portalContainer}>
<Pressable style={styles.backdrop} onPress={closeDropdown}> <Pressable style={styles.backdrop} onPress={closeDropdown}>
<ThemedBlurView <ThemedBlurView
pointerEvents="none" pointerEvents="none"
style={styles.backdropBlur} style={styles.backdropBlur}
/> />
</Pressable> </Pressable>
<Animated.View <Animated.View
pointerEvents="auto" pointerEvents="auto"
style={[ style={[
styles.dropdownOverlayContainer, styles.dropdownOverlayContainer,
overlayPositionStyle, overlayPositionStyle,
{ {
opacity: dropdownAnimation, opacity: dropdownAnimation,
transform: [ transform: [
{ translateY: overlayTranslateY }, { translateY: overlayTranslateY },
{ scale: overlayScale }, { scale: overlayScale },
], ],
}, },
]} ]}
> >
{dropdownContent} {dropdownContent}
</Animated.View> </Animated.View>
</View> </View>
</Portal> </Portal>
) : null} ) : null}
</View>
</View> </View>
</View> );
); }
}; );
export default ProjectDropDown; export default ProjectDropDown;
+47 -23
View File
@@ -33,6 +33,7 @@ import {
getStageAction, getStageAction,
} from "../../utils/projectStages"; } from "../../utils/projectStages";
import { openCoinPackModal } from "../../utils/coinPackModal"; import { openCoinPackModal } from "../../utils/coinPackModal";
import useNavigateToMusicDetails from "../../hooks/useNavigateToMusicDetails";
import StageCard from "./components/StageCard"; import StageCard from "./components/StageCard";
import ClubCard from "./components/ClubCard"; import ClubCard from "./components/ClubCard";
@@ -66,7 +67,7 @@ const STAGE_CARD_CONTENT = [
key: "beatmaker", key: "beatmaker",
step: "ÉTAPE 2", step: "ÉTAPE 2",
description: description:
"Bienvenue au studio MusicLand.\nNous mettons en musique tes paroles en fonction de tes inspirations, ça va être top !", "Je suis Malik, responsable du studio de MusicLand.\nJe vais mettre en musique tes paroles en fonction de tes goûts musicaux, ça va être top !",
image: cardsImg.studio, image: cardsImg.studio,
imagePosition: "right", imagePosition: "right",
textAlign: "right", textAlign: "right",
@@ -86,7 +87,7 @@ const STAGE_CARD_CONTENT = [
key: "publisher", key: "publisher",
step: "ÉTAPE 4", step: "ÉTAPE 4",
description: description:
"À la sortie du studio, l'équipe production te propose les prochaines étapes pour partager ta musique avec le monde.", "Je suis Mr Benhaï, Producteur de MusicLand, et je vais te faire une proposition qui pourrait tintéresser. On se retrouve à la sortie du studio !",
image: cardsImg.production, image: cardsImg.production,
imagePosition: "right", imagePosition: "right",
textAlign: "right", textAlign: "right",
@@ -115,6 +116,7 @@ const Home = ({ navigation, route }) => {
} = useUser(); } = useUser();
const { setTooltip } = useMinuit(); const { setTooltip } = useMinuit();
const [videoUrl, setVideoUrl] = useState(null); const [videoUrl, setVideoUrl] = useState(null);
const navigateToMusicDetails = useNavigateToMusicDetails();
if (!currentUID) { if (!currentUID) {
return <LandingPage />; return <LandingPage />;
@@ -174,6 +176,7 @@ const Home = ({ navigation, route }) => {
const [menuAnchor, setMenuAnchor] = useState(null); const [menuAnchor, setMenuAnchor] = useState(null);
const [menuProject, setMenuProject] = useState(null); const [menuProject, setMenuProject] = useState(null);
const menuAnchorRef = useRef(null); const menuAnchorRef = useRef(null);
const projectDropdownRef = useRef(null);
const [hasLocalAdventureFlag, setHasLocalAdventureFlag] = useState(false); const [hasLocalAdventureFlag, setHasLocalAdventureFlag] = useState(false);
const handleSelectProject = useCallback( const handleSelectProject = useCallback(
@@ -232,28 +235,48 @@ const Home = ({ navigation, route }) => {
const moreMenuItems = useMemo(() => { const moreMenuItems = useMemo(() => {
if (!menuProject?.id) return []; if (!menuProject?.id) return [];
return [
{ const items = [];
label: "Supprimer", const hasSongUrl =
onPress: () => typeof menuProject?.songUrl === "string" &&
Alert( menuProject.songUrl.length > 0;
"Confirmer la suppression",
"Cette action supprimera définitivement ce projet.", if (hasSongUrl) {
[ items.push({
{ text: "Annuler", style: "cancel" }, label: "Jouer la musique",
{ onPress: () => {
text: "Supprimer", projectDropdownRef.current?.close?.();
style: "destructive", navigateToMusicDetails({
onPress: () => { projectId: menuProject.id,
handleDeleteProject(); songUrl: menuProject.songUrl,
}, project: menuProject,
});
},
});
}
items.push({
label: "Supprimer",
onPress: () =>
Alert(
"Confirmer la suppression",
"Cette action supprimera définitivement ce projet.",
[
{ text: "Annuler", style: "cancel" },
{
text: "Supprimer",
style: "destructive",
onPress: () => {
handleDeleteProject();
}, },
], },
{ cancelable: true }, ],
), { cancelable: true },
}, ),
]; });
}, [handleDeleteProject, menuProject?.id]);
return items;
}, [handleDeleteProject, menuProject, navigateToMusicDetails]);
useEffect(() => { useEffect(() => {
if (!route?.params?.showLyricsCongrats) { if (!route?.params?.showLyricsCongrats) {
@@ -495,6 +518,7 @@ const Home = ({ navigation, route }) => {
<View style={styles.topBar}> <View style={styles.topBar}>
{mobileInfoRow} {mobileInfoRow}
<ProjectDropDown <ProjectDropDown
ref={projectDropdownRef}
style={styles.projectDropDown} style={styles.projectDropDown}
projects={projects} projects={projects}
selectedProject={currentProject} selectedProject={currentProject}