diff --git a/src/components/FeatureCarousel/FeatureCarousel.js b/src/components/FeatureCarousel/FeatureCarousel.js
index 6873d4e..081c5ef 100644
--- a/src/components/FeatureCarousel/FeatureCarousel.js
+++ b/src/components/FeatureCarousel/FeatureCarousel.js
@@ -9,38 +9,41 @@ const STAGE_CARD_CONTENT = [
key: "songwriter",
title: "Nathalie",
description: "Let’s write lyrics together !",
- image: ai.nathalie,
+ image: ai.leftIcon,
},
{
key: "beatmaker",
title: "Theo",
description: "Come back, when you'll have lyrics!",
- image: ai.theo,
+ image: ai.rightIcon,
},
{
key: "designer",
title: "Bena",
description: "Come back when you want to generate cover!",
- image: ai.bena,
+ image: ai.leftIcon,
},
{
key: "director",
title: "John",
description: "Come back when your audio is ready!",
- image: ai.john,
+ image: ai.rightIcon,
},
];
const FeatureCarousel = ({
style,
selectedProject,
+ stageStates: stageStatesProp,
activeIndex,
onActiveIndexChange,
}) => {
- const stageStates = useMemo(
- () => getCreationStageStates(selectedProject),
- [selectedProject],
- );
+ const stageStates = useMemo(() => {
+ if (stageStatesProp) {
+ return stageStatesProp;
+ }
+ return getCreationStageStates(selectedProject);
+ }, [stageStatesProp, selectedProject]);
const carouselItems = useMemo(
() =>
@@ -54,11 +57,24 @@ const FeatureCarousel = ({
const itemHeight = Math.max(windowHeight, 1);
const listRef = useRef(null);
+ const pendingScrollRef = useRef(false);
+ const activeIndexRef = useRef(activeIndex);
+ const onActiveIndexChangeRef = useRef(onActiveIndexChange);
+
+ useEffect(() => {
+ activeIndexRef.current = activeIndex;
+ }, [activeIndex]);
+
+ useEffect(() => {
+ onActiveIndexChangeRef.current = onActiveIndexChange;
+ }, [onActiveIndexChange]);
const keyExtractor = useCallback((item) => item.key, []);
const renderItem = useCallback(
- ({ item }) => ,
+ ({ item, index }) => (
+
+ ),
[],
);
@@ -73,18 +89,24 @@ const FeatureCarousel = ({
const viewabilityConfig = useRef({ viewAreaCoveragePercentThreshold: 60 });
- const handleViewableItemsChanged = useCallback(
- ({ viewableItems }) => {
- if (!onActiveIndexChange) {
+ const handleViewableItemsChangedRef = useRef();
+ if (!handleViewableItemsChangedRef.current) {
+ handleViewableItemsChangedRef.current = ({ viewableItems }) => {
+ if (!viewableItems?.length) return;
+ const firstVisible = viewableItems.find((item) => item?.isViewable);
+ if (!firstVisible || firstVisible.index == null) return;
+ if (pendingScrollRef.current) {
+ if (firstVisible.index === activeIndexRef.current) {
+ pendingScrollRef.current = false;
+ }
return;
}
- const firstVisible = viewableItems?.[0];
- if (firstVisible?.index != null) {
- onActiveIndexChange(firstVisible.index);
+ const callback = onActiveIndexChangeRef.current;
+ if (callback && firstVisible.index !== activeIndexRef.current) {
+ callback(firstVisible.index);
}
- },
- [onActiveIndexChange],
- );
+ };
+ }
useEffect(() => {
if (
@@ -96,8 +118,10 @@ const FeatureCarousel = ({
return;
}
try {
+ pendingScrollRef.current = true;
listRef.current.scrollToIndex({ index: activeIndex, animated: true });
} catch (_error) {
+ pendingScrollRef.current = false;
// Ignore scroll errors when list is not ready yet.
}
}, [activeIndex, carouselItems.length]);
@@ -109,7 +133,7 @@ const FeatureCarousel = ({
data={carouselItems}
keyExtractor={keyExtractor}
renderItem={renderItem}
- onViewableItemsChanged={handleViewableItemsChanged}
+ onViewableItemsChanged={handleViewableItemsChangedRef.current}
viewabilityConfig={viewabilityConfig.current}
getItemLayout={getItemLayout}
showsVerticalScrollIndicator={false}
diff --git a/src/components/ProjectDropDown/ProjectDropDown.js b/src/components/ProjectDropDown/ProjectDropDown.js
index 903849d..d3b2008 100644
--- a/src/components/ProjectDropDown/ProjectDropDown.js
+++ b/src/components/ProjectDropDown/ProjectDropDown.js
@@ -41,6 +41,7 @@ const ProjectDropDown = ({
onModifyProject,
onCreateProject,
formatDate = defaultFormatDate,
+ allowEmptySelection = false,
}) => {
const [isOpen, setIsOpen] = useState(false);
const [triggerLayout, setTriggerLayout] = useState(null);
@@ -48,9 +49,14 @@ const ProjectDropDown = ({
const normalizedProjects = Array.isArray(projects) ? projects : [];
const isDisabled = normalizedProjects.length === 0;
- const currentProject = selectedProject?.id
- ? selectedProject
- : normalizedProjects[0] || null;
+ const hasExplicitNullSelection =
+ allowEmptySelection && selectedProject === null;
+
+ const currentProject = hasExplicitNullSelection
+ ? null
+ : selectedProject?.id
+ ? selectedProject
+ : normalizedProjects[0] || null;
const dropdownProjects = currentProject
? normalizedProjects.filter((project) => {
@@ -212,19 +218,28 @@ const ProjectRow = ({
onModify?.(project, top);
});
return;
- } catch (error) {
+ } catch (_error) {
// Fallback to layout-derived position
}
}
onModify?.(project, menuPos);
};
+ const isEmptyProject = !project;
const coverUri =
project?.coverUrl ||
project?.coverUri ||
(typeof project?.cover === "string" ? project.cover : project?.cover?.uri);
- const title = project?.title || "Sans titre";
- const formattedDate = formatDate(project?.updatedAt || project?.createdAt);
+ const title =
+ project?.title || (isEmptyProject ? "Nouvelle musique" : "Sans titre");
+ const formattedDate = !isEmptyProject
+ ? formatDate(project?.updatedAt || project?.createdAt)
+ : "";
+ const subtitle = isEmptyProject
+ ? "Commencer une nouvelle musique"
+ : formattedDate
+ ? `Modifié le ${formattedDate}`
+ : "MusicLand";
return (
- {formattedDate ? `Modifié le ${formattedDate}` : "MusicLand"}
+ {subtitle}
{isTitle ? (
diff --git a/src/providers/UserDataProvider.js b/src/providers/UserDataProvider.js
index cf55a0d..d00374a 100644
--- a/src/providers/UserDataProvider.js
+++ b/src/providers/UserDataProvider.js
@@ -355,7 +355,9 @@ export default ({ children }) => {
useEffect(() => {
if (!Array.isArray(userProjects) || userProjects.length === 0) return;
if (selectedProjectId) return;
- if (currentUserDoc?.selectedProjectId) return;
+
+ const remoteSelectedId = currentUserDoc?.selectedProjectId;
+ if (remoteSelectedId !== undefined) return;
const fallbackProjectId = userProjects[0]?.id;
if (!fallbackProjectId) return;
diff --git a/src/screens/Home/Home.js b/src/screens/Home/Home.js
index 72ec7a4..f661e2c 100644
--- a/src/screens/Home/Home.js
+++ b/src/screens/Home/Home.js
@@ -15,6 +15,7 @@ import ShareBtn from "../../components/ShareBtn/ShareBtn";
import {
getCreationStageStates,
getStageAction,
+ findFirstUnlockedStageIndex,
} from "../../utils/projectStages";
const Home = () => {
@@ -33,6 +34,9 @@ const Home = () => {
const currentProject = useMemo(() => {
if (!projects.length) return null;
+ if (selectedProjectId === null) {
+ return null;
+ }
const activeId = selectedProject?.id || selectedProjectId;
if (!activeId) {
return projects[0];
@@ -59,16 +63,13 @@ const Home = () => {
const handleSelectProject = (project) => {
if (!project?.id) return;
selectProject(project.id);
+ setActiveStageIndex(findFirstUnlockedStageIndex(project));
};
const handleModifyProject = (project) => {
if (!project?.id) return;
selectProject(project.id);
- navigate(Routes.FlowSelection);
- };
-
- const handleCreateNew = () => {
- resetSelectedProject();
+ setActiveStageIndex(findFirstUnlockedStageIndex(project));
navigate(Routes.FlowSelection);
};
@@ -111,6 +112,8 @@ const Home = () => {
const stageParams = stageAction?.params;
const stageLocked = activeStage?.isLocked ?? true;
+ const hasActiveProject = !!currentProject;
+
const ensureProjectSelected = useCallback(() => {
if (!currentProject?.id) {
return;
@@ -121,14 +124,34 @@ const Home = () => {
}, [currentProject?.id, selectProject, selectedProject?.id]);
const handleStageAction = useCallback(() => {
- if (stageLocked || !stageRoute) {
+ if (!hasActiveProject || stageLocked || !stageRoute) {
return;
}
ensureProjectSelected();
navigate(stageRoute, stageParams);
- }, [ensureProjectSelected, stageLocked, stageParams, stageRoute]);
+ }, [
+ ensureProjectSelected,
+ hasActiveProject,
+ stageLocked,
+ stageParams,
+ stageRoute,
+ ]);
- const primaryDisabled = stageLocked || !stageRoute;
+ const songwriterAction = useMemo(
+ () => getStageAction("songwriter", null),
+ [],
+ );
+
+ const handleStartNew = useCallback(() => {
+ resetSelectedProject();
+ setActiveStageIndex(0);
+ if (songwriterAction?.route) {
+ navigate(songwriterAction.route, songwriterAction.params);
+ }
+ }, [resetSelectedProject, songwriterAction]);
+
+ const continueDisabled = !hasActiveProject || stageLocked || !stageRoute;
+ const startDisabled = !songwriterAction?.route;
return (
@@ -149,9 +172,10 @@ const Home = () => {
style={[isWeb ? { width: "100%" } : { flex: 1 }]}
projects={projects}
selectedProject={currentProject}
+ allowEmptySelection
onSelectProject={handleSelectProject}
onModifyProject={handleModifyProject}
- onCreateProject={handleCreateNew}
+ onCreateProject={handleStartNew}
formatDate={formatDate}
/>
{!isWeb && }
@@ -159,6 +183,7 @@ const Home = () => {
@@ -175,14 +200,14 @@ const Home = () => {