new project modal & seperate playback and music likes
This commit is contained in:
@@ -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,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user