end of home

This commit is contained in:
Thomas Demirdjian
2025-10-01 10:49:03 +02:00
parent 4097143559
commit 167e62fcfc
18 changed files with 928 additions and 558 deletions
+195 -128
View File
@@ -1,145 +1,188 @@
import { View, Text, Image, Platform } from "react-native";
import React, { useMemo, useState } from "react";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { Platform, StyleSheet, View } from "react-native";
import Page from "../../layouts/Page";
import { background, img } from "../../assets";
import { responsiveHeight } from "react-native-responsive-dimensions";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import { background } from "../../assets";
import { gutters, Palette } from "../../styles";
import { navigate } from "../../navigation/NavigationService";
import { Routes } from "../../navigation";
import { FlatList, Alert } from "react-native";
import { BlurView } from "expo-blur";
import { useUser } from "../../providers/UserDataProvider";
import MusicCard from "../Library/components/MusicCard";
import ProjectDropDown from "../../components/ProjectDropDown/ProjectDropDown";
import FeatureCarousel from "../../components/FeatureCarousel/FeatureCarousel";
import GradientButton from "../../components/GradientButton";
import MoreMenu from "../../components/MoreMenu";
import { projectsRef } from "../../config/firebase";
import { useGlobal } from "reactn";
import BorderGradientButton from "../../components/BorderGradientButton";
import { isWeb } from "../../hooks/useLayoutType.js";
import ShareBtn from "../../components/ShareBtn/ShareBtn";
import {
getCreationStageStates,
getStageAction,
} from "../../utils/projectStages";
const Home = () => {
const { userProjects = [], resetSelectedProject, selectProject } = useUser();
const {
userProjects = [],
resetSelectedProject,
selectProject,
selectedProject,
selectedProjectId,
} = useUser();
const projects = useMemo(
() => (Array.isArray(userProjects) ? userProjects : []),
[userProjects],
);
const [, setTooltip] = useGlobal("_tooltip");
const [menuTop, setMenuTop] = useState(0);
const [showMenu, setShowMenu] = useState(false);
const [menuProjectId, setMenuProjectId] = useState(null);
const currentProject = useMemo(() => {
if (!projects.length) return null;
const activeId = selectedProject?.id || selectedProjectId;
if (!activeId) {
return projects[0];
}
return projects.find((project) => project?.id === activeId) || projects[0];
}, [projects, selectedProject?.id, selectedProjectId]);
const formatDate = useCallback((timestamp) => {
try {
const value = timestamp?.toDate ? timestamp.toDate() : timestamp;
const date = value ? new Date(value) : null;
if (!date || Number.isNaN(date.getTime())) {
return "";
}
const day = date.getDate().toString().padStart(2, "0");
const month = (date.getMonth() + 1).toString().padStart(2, "0");
return `${day}/${month}`;
} catch (error) {
console.warn("Home.web: formatDate error", error);
return "";
}
}, []);
const handleSelectProject = (project) => {
if (!project?.id) return;
selectProject(project.id);
};
const handleModifyProject = (project) => {
if (!project?.id) return;
selectProject(project.id);
navigate(Routes.FlowSelection);
};
const handleCreateNew = () => {
resetSelectedProject();
navigate(Routes.FlowSelection);
};
const stageStates = useMemo(
() => getCreationStageStates(currentProject),
[currentProject],
);
const firstUnlockedIndex = useMemo(() => {
const index = stageStates.findIndex((stage) => !stage.isLocked);
return index === -1 ? 0 : index;
}, [stageStates]);
const [activeStageIndex, setActiveStageIndex] = useState(firstUnlockedIndex);
useEffect(() => {
setActiveStageIndex((prev) => {
if (prev == null || prev >= stageStates.length) {
return firstUnlockedIndex;
}
const current = stageStates[prev];
const fallback = stageStates[firstUnlockedIndex];
if (current?.isLocked && fallback && !fallback.isLocked) {
return firstUnlockedIndex;
}
return prev;
});
}, [stageStates, firstUnlockedIndex]);
const activeStage =
stageStates[activeStageIndex] || stageStates[firstUnlockedIndex];
const stageAction = useMemo(() => {
if (!activeStage) {
return null;
}
return getStageAction(activeStage.key, currentProject);
}, [activeStage, currentProject]);
const stageRoute = stageAction?.route || null;
const stageParams = stageAction?.params;
const stageLocked = activeStage?.isLocked ?? true;
const ensureProjectSelected = useCallback(() => {
if (!currentProject?.id) {
return;
}
if (selectedProject?.id !== currentProject.id) {
selectProject(currentProject.id);
}
}, [currentProject?.id, selectProject, selectedProject?.id]);
const handleStageAction = useCallback(() => {
if (stageLocked || !stageRoute) {
return;
}
ensureProjectSelected();
navigate(stageRoute, stageParams);
}, [ensureProjectSelected, stageLocked, stageParams, stageRoute]);
const primaryDisabled = stageLocked || !stageRoute;
return (
<Page backgroundImg={background.homeBG} headerType="NONE">
<View style={{ flex: 1 }}>
<Image
source={img.goodVibe}
style={{ alignSelf: "center", position: "absolute" }}
/>
{projects.length > 0 && (
<View
style={{
height: responsiveHeight(70),
paddingTop: responsiveHeight(6),
}}
>
<BlurView
intensity={Platform.OS !== "ios" ? 10 : 20}
style={{
flex: 1,
borderRadius: 20,
overflow: "hidden",
backgroundColor: Palette.glass,
padding: 12,
gap: 8,
}}
// experimentalBlurMethod={
// Platform.OS !== "ios" ? "dimezisBlurView" : "none"
// }
>
<Text
style={{
fontSize: 22,
color: Palette.white,
fontFamily: FONT_FAMILY.InterSemiBold,
marginBottom: 8,
textAlign: "center",
}}
>
Musiques en cours
</Text>
<FlatList
data={projects}
keyExtractor={(item) => item.id}
contentContainerStyle={{ gap: 10, paddingBottom: 10 }}
renderItem={({ item }) => (
<MusicCard
title={item?.title || "Sans titre"}
subtitle={"MusicLand"}
imageUri={item?.coverUrl || null}
projectId={item?.id}
likedBy={item?.likedBy || []}
onPress={() => {
selectProject(item.id);
navigate(Routes.FlowSelection);
}}
onPressMore={(posTop) => {
setMenuProjectId(item.id);
setMenuTop(posTop);
setShowMenu((prev) => !prev || posTop !== menuTop);
}}
/>
)}
/>
<MoreMenu
visible={showMenu}
top={menuTop}
onClose={() => setShowMenu(false)}
inPlaylist={false}
projectId={menuProjectId}
extraItems={[
{
label: "Supprimer",
onPress: () =>
Alert.alert(
"Confirmer la suppression",
"Cette action supprimera définitivement ce projet.",
[
{ text: "Annuler", style: "cancel" },
{
text: "Supprimer",
style: "destructive",
onPress: async () => {
try {
if (!menuProjectId) return;
await projectsRef.doc(menuProjectId).delete();
setTooltip({
type: "success",
text: "Projet supprimé",
});
} catch (e) {
setTooltip({
type: "error",
text: e?.message || "Suppression impossible",
});
}
},
},
],
{ cancelable: true },
),
},
]}
/>
</BlurView>
</View>
)}
<View style={{ paddingTop: 12, marginBottom: responsiveHeight(10) }}>
<Page shareBtn backgroundImg={background.homeBGWeb} headerType="NONE">
<View style={styles.root}>
<View
style={{
zIndex: 10,
position: "absolute",
top: 10,
width: 400,
alignSelf: "center",
flexDirection: isWeb ? "column" : "row",
alignItems: "center",
paddingHorizontal: isWeb ? 0 : gutters,
gap: 10,
}}
>
<ProjectDropDown
style={[isWeb ? { width: "100%" } : { flex: 1 }]}
projects={projects}
selectedProject={currentProject}
onSelectProject={handleSelectProject}
onModifyProject={handleModifyProject}
onCreateProject={handleCreateNew}
formatDate={formatDate}
/>
{!isWeb && <ShareBtn />}
</View>
<View style={styles.carouselSection}>
<FeatureCarousel
selectedProject={currentProject}
activeIndex={activeStageIndex}
onActiveIndexChange={setActiveStageIndex}
/>
</View>
<View
style={{
position: "absolute",
bottom: isWeb ? 180 : 120,
flexDirection: isWeb ? "row" : "column",
gap: 5,
alignSelf: "center",
}}
>
<GradientButton
title="Créer une nouvelle musique"
containerStyle={{ width: "80%", alignSelf: "center" }}
onPress={() => {
resetSelectedProject();
navigate(Routes.FlowSelection);
}}
containerStyle={{ width: Platform.OS === "web" ? 250 : "100%" }}
title={"Commencer à créer"}
onPress={handleStageAction}
disabled={primaryDisabled}
/>
<BorderGradientButton
containerStyle={{ width: Platform.OS === "web" ? 250 : "100%" }}
title={"Continuer la création"}
onPress={handleStageAction}
disabled={primaryDisabled}
/>
</View>
</View>
@@ -148,3 +191,27 @@ const Home = () => {
};
export default Home;
const styles = StyleSheet.create({
root: {
flex: 1,
},
heroImage: {
position: "absolute",
top: -60,
alignSelf: "center",
width: "80%",
maxWidth: 920,
height: 420,
opacity: 0.9,
},
shareIcon: {
width: 18,
height: 18,
tintColor: Palette.white,
},
carouselSection: {
width: "100%",
flex: 1,
},
});