new project modal & seperate playback and music likes

This commit is contained in:
2025-10-24 16:12:07 +02:00
parent 47d5b92a57
commit 2599a25d77
12 changed files with 415 additions and 63 deletions
+244
View File
@@ -0,0 +1,244 @@
import React, { useCallback, useMemo, useState } from "react";
import {
Image,
Pressable,
ScrollView,
StyleSheet,
Text,
View,
} from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import AppActionSheet from "../AppActionSheet";
import GradientButton from "../GradientButton";
import { img } from "../../assets";
import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService";
import { useUserData } from "../../providers/UserDataProvider";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import Style, { size } from "../../styles/Style";
import { isWeb } from "../../hooks/useLayoutType";
const SHEET_ID = "PlaybackPicker";
const PlaybackPickerModal = () => {
const { userProjects = [], createNewProject } = useUserData();
const [isCreating, setIsCreating] = useState(false);
const [webVisible, setWebVisible] = useState(true);
const sanitizedProjects = useMemo(() => {
if (!Array.isArray(userProjects)) {
return [];
}
return userProjects.filter((project) => {
if (!project || project.playbackUrl) {
return false;
}
return !!project?.songUrl;
});
}, [userProjects]);
const hasAnyMusic = Array.isArray(userProjects) && userProjects.length > 0;
const hideSheet = useCallback(() => {
if (isWeb) {
setWebVisible(false);
}
try {
const maybePromise = SheetManager.hide(SHEET_ID);
return Promise.resolve(maybePromise);
} catch (error) {
console.error("[PlaybackPicker] hide error", error?.message || error);
return Promise.resolve();
}
}, [setWebVisible]);
const handleSelectProject = useCallback(
async (project) => {
if (!project?.id) return;
const hidePromise = hideSheet();
await Promise.race([
Promise.resolve(hidePromise),
new Promise((resolve) => setTimeout(resolve, 200)),
]).catch(() => {});
navigate(Routes.Playback, { project });
},
[hideSheet]
);
const handleCreateNew = useCallback(async () => {
if (isCreating) {
return;
}
setIsCreating(true);
try {
let projectId = null;
if (typeof createNewProject === "function") {
projectId = await createNewProject({ hasLyrics: false });
}
const hidePromise = hideSheet();
await Promise.race([
Promise.resolve(hidePromise),
new Promise((resolve) => setTimeout(resolve, 200)),
]);
if (projectId) {
navigate(Routes.WritingLyrics, { projectId });
} else {
navigate(Routes.WritingLyrics);
}
} catch (error) {
console.error("[PlaybackPicker] create project failed", error);
} finally {
setIsCreating(false);
}
}, [createNewProject, hideSheet, isCreating]);
const hasProjects = sanitizedProjects.length > 0;
if (isWeb && !webVisible) {
return null;
}
return (
<AppActionSheet id={SHEET_ID} webModal onClose={hideSheet}>
<View style={{ gap: 18 }}>
<Text style={styles.title}>Ajouter un playback</Text>
<Text style={styles.description}>
Choisis une musique déjà créée pour y ajouter un playback ou lance un
nouveau projet.
</Text>
{hasProjects ? (
<ScrollView
style={{ maxHeight: 320 }}
contentContainerStyle={{ gap: 12, paddingVertical: 4 }}
showsVerticalScrollIndicator={false}
>
{sanitizedProjects.map((project) => {
const coverUri = project?.coverUrl || null;
return (
<Pressable
key={project.id}
onPress={() => handleSelectProject(project)}
style={styles.projectCard}
>
<Image
source={coverUri ? { uri: coverUri } : img.placeholder}
style={styles.cover}
resizeMode="cover"
/>
<View style={{ flex: 1, gap: 4 }}>
<Text numberOfLines={1} style={styles.projectTitle}>
{project?.title || "Sans titre"}
</Text>
<Text numberOfLines={1} style={styles.projectSubtitle}>
{project?.userName || "Moi"}
</Text>
<Text style={styles.projectHint}>
Playback à créer
</Text>
</View>
</Pressable>
);
})}
</ScrollView>
) : (
<View style={styles.emptyState}>
<Text style={styles.emptyTitle}>
{hasAnyMusic
? "Aucune musique n'est prête pour l'étape playback"
: "Tu n'as pas encore de musique prête pour le playback"}
</Text>
<Text style={styles.emptyDescription}>
{hasAnyMusic
? "Complète d'abord la création de ta chanson, puis reviens ici pour enregistrer le playback."
: "Crée un nouveau projet pour composer ta musique et lancer ton playback."}
</Text>
</View>
)}
<GradientButton
title="Créer un nouveau projet"
onPress={handleCreateNew}
containerStyle={{ alignSelf: "center", minWidth: 220 }}
disabled={isCreating}
/>
</View>
</AppActionSheet>
);
};
export default PlaybackPickerModal;
const styles = StyleSheet.create({
title: {
fontSize: 22,
color: Palette.white,
fontFamily: FONT_FAMILY.InterSemiBold,
textAlign: "center",
},
description: {
fontSize: 14,
color: Palette.white,
opacity: 0.85,
fontFamily: FONT_FAMILY.InterRegular,
textAlign: "center",
lineHeight: 20,
},
projectCard: {
...Style.containerRow,
gap: 12,
padding: 12,
borderRadius: 16,
backgroundColor: Palette.glass,
},
cover: {
...size({ size: 60 }),
borderRadius: 12,
backgroundColor: Palette.black,
},
projectTitle: {
fontSize: 16,
color: Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
},
projectSubtitle: {
fontSize: 12,
color: Palette.gray,
fontFamily: FONT_FAMILY.InterRegular,
},
projectHint: {
fontSize: 12,
color: Palette.white,
opacity: 0.7,
fontFamily: FONT_FAMILY.InterRegular,
},
emptyState: {
alignItems: "center",
gap: 8,
paddingVertical: 40,
paddingHorizontal: 20,
borderRadius: 20,
backgroundColor: Palette.glass,
},
emptyTitle: {
fontSize: 18,
color: Palette.white,
fontFamily: FONT_FAMILY.InterSemiBold,
textAlign: "center",
},
emptyDescription: {
fontSize: 14,
color: Palette.white,
opacity: 0.8,
fontFamily: FONT_FAMILY.InterRegular,
textAlign: "center",
lineHeight: 20,
},
});