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 { Portal } from "@gorhom/portal";
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useRef,
useState,
@@ -78,21 +80,25 @@ const defaultFormatDate = (timestamp) => {
}
};
const ProjectDropDown = ({
style,
projects = [],
selectedProject,
onSelectProject,
onModifyProject,
onCreateProject,
formatDate = defaultFormatDate,
allowEmptySelection = false,
}) => {
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 ProjectDropDown = forwardRef(
(
{
style,
projects = [],
selectedProject,
onSelectProject,
onModifyProject,
onCreateProject,
formatDate = defaultFormatDate,
allowEmptySelection = false,
},
ref
) => {
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(() => {
if (triggerRef.current?.measureInWindow) {
@@ -133,39 +139,47 @@ const ProjectDropDown = ({
})
: normalizedProjects;
const toggleDropdown = useCallback(() => {
if (isDisabled) {
return;
}
if (!isOpen) {
measureTriggerPosition();
}
setIsOpen((prev) => !prev);
}, [isDisabled, isOpen, measureTriggerPosition]);
const toggleDropdown = useCallback(() => {
if (isDisabled) {
return;
}
if (!isOpen) {
measureTriggerPosition();
}
setIsOpen((prev) => !prev);
}, [isDisabled, isOpen, measureTriggerPosition]);
const closeDropdown = useCallback(() => setIsOpen(false), []);
const closeDropdown = useCallback(() => setIsOpen(false), []);
const handleSelect = useCallback(
(project) => {
if (!project?.id) return;
onSelectProject?.(project);
useImperativeHandle(
ref,
() => ({
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, onSelectProject]
);
const handleModify = useCallback(
(project, anchor) => {
if (!project?.id) return;
onModifyProject?.(project, anchor);
},
[onModifyProject]
);
const handleCreate = useCallback(() => {
onCreateProject?.();
closeDropdown();
}, [closeDropdown, onCreateProject]);
}, [closeDropdown, onCreateProject]);
const dropdownWidth = useMemo(
() => triggerLayout?.width || 0,
@@ -286,74 +300,75 @@ const ProjectDropDown = ({
</ThemedBlurView>
);
return (
<View style={[styles.container, style]}>
<View
ref={triggerRef}
style={styles.triggerWrapper}
onLayout={(event) => {
setTriggerLayout(event.nativeEvent.layout);
measureTriggerPosition();
}}
>
<Animated.View
pointerEvents={isOpen ? "none" : "auto"}
style={[
styles.dropdownTriggerContainer,
{
opacity: triggerOpacity,
transform: [{ translateY: triggerTranslateY }],
},
]}
return (
<View style={[styles.container, style]}>
<View
ref={triggerRef}
style={styles.triggerWrapper}
onLayout={(event) => {
setTriggerLayout(event.nativeEvent.layout);
measureTriggerPosition();
}}
>
<ThemedBlurView
<Animated.View
pointerEvents={isOpen ? "none" : "auto"}
style={[
styles.dropdownBlur,
isDisabled && styles.dropdownBlurDisabled,
styles.dropdownTriggerContainer,
{
opacity: triggerOpacity,
transform: [{ translateY: triggerTranslateY }],
},
]}
>
<ProjectRow
isTitle={true}
isOpen={isOpen}
project={currentProject}
formatDate={formatDate}
onSelect={toggleDropdown}
/>
</ThemedBlurView>
</Animated.View>
<ThemedBlurView
style={[
styles.dropdownBlur,
isDisabled && styles.dropdownBlurDisabled,
]}
>
<ProjectRow
isTitle={true}
isOpen={isOpen}
project={currentProject}
formatDate={formatDate}
onSelect={toggleDropdown}
/>
</ThemedBlurView>
</Animated.View>
{isOpen && triggerWindowLayout ? (
<Portal>
<View style={styles.portalContainer}>
<Pressable style={styles.backdrop} onPress={closeDropdown}>
<ThemedBlurView
pointerEvents="none"
style={styles.backdropBlur}
/>
</Pressable>
<Animated.View
pointerEvents="auto"
style={[
styles.dropdownOverlayContainer,
overlayPositionStyle,
{
opacity: dropdownAnimation,
transform: [
{ translateY: overlayTranslateY },
{ scale: overlayScale },
],
},
]}
>
{dropdownContent}
</Animated.View>
</View>
</Portal>
) : null}
{isOpen && triggerWindowLayout ? (
<Portal>
<View style={styles.portalContainer}>
<Pressable style={styles.backdrop} onPress={closeDropdown}>
<ThemedBlurView
pointerEvents="none"
style={styles.backdropBlur}
/>
</Pressable>
<Animated.View
pointerEvents="auto"
style={[
styles.dropdownOverlayContainer,
overlayPositionStyle,
{
opacity: dropdownAnimation,
transform: [
{ translateY: overlayTranslateY },
{ scale: overlayScale },
],
},
]}
>
{dropdownContent}
</Animated.View>
</View>
</Portal>
) : null}
</View>
</View>
</View>
);
};
);
}
);
export default ProjectDropDown;
+47 -23
View File
@@ -33,6 +33,7 @@ import {
getStageAction,
} from "../../utils/projectStages";
import { openCoinPackModal } from "../../utils/coinPackModal";
import useNavigateToMusicDetails from "../../hooks/useNavigateToMusicDetails";
import StageCard from "./components/StageCard";
import ClubCard from "./components/ClubCard";
@@ -66,7 +67,7 @@ const STAGE_CARD_CONTENT = [
key: "beatmaker",
step: "ÉTAPE 2",
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,
imagePosition: "right",
textAlign: "right",
@@ -86,7 +87,7 @@ const STAGE_CARD_CONTENT = [
key: "publisher",
step: "ÉTAPE 4",
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,
imagePosition: "right",
textAlign: "right",
@@ -115,6 +116,7 @@ const Home = ({ navigation, route }) => {
} = useUser();
const { setTooltip } = useMinuit();
const [videoUrl, setVideoUrl] = useState(null);
const navigateToMusicDetails = useNavigateToMusicDetails();
if (!currentUID) {
return <LandingPage />;
@@ -174,6 +176,7 @@ const Home = ({ navigation, route }) => {
const [menuAnchor, setMenuAnchor] = useState(null);
const [menuProject, setMenuProject] = useState(null);
const menuAnchorRef = useRef(null);
const projectDropdownRef = useRef(null);
const [hasLocalAdventureFlag, setHasLocalAdventureFlag] = useState(false);
const handleSelectProject = useCallback(
@@ -232,28 +235,48 @@ const Home = ({ navigation, route }) => {
const moreMenuItems = useMemo(() => {
if (!menuProject?.id) return [];
return [
{
label: "Supprimer",
onPress: () =>
Alert(
"Confirmer la suppression",
"Cette action supprimera définitivement ce projet.",
[
{ text: "Annuler", style: "cancel" },
{
text: "Supprimer",
style: "destructive",
onPress: () => {
handleDeleteProject();
},
const items = [];
const hasSongUrl =
typeof menuProject?.songUrl === "string" &&
menuProject.songUrl.length > 0;
if (hasSongUrl) {
items.push({
label: "Jouer la musique",
onPress: () => {
projectDropdownRef.current?.close?.();
navigateToMusicDetails({
projectId: menuProject.id,
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 },
),
},
];
}, [handleDeleteProject, menuProject?.id]);
},
],
{ cancelable: true },
),
});
return items;
}, [handleDeleteProject, menuProject, navigateToMusicDetails]);
useEffect(() => {
if (!route?.params?.showLyricsCongrats) {
@@ -495,6 +518,7 @@ const Home = ({ navigation, route }) => {
<View style={styles.topBar}>
{mobileInfoRow}
<ProjectDropDown
ref={projectDropdownRef}
style={styles.projectDropDown}
projects={projects}
selectedProject={currentProject}