player, song generation again, home and design fixes

This commit is contained in:
Thomas Demirdjian
2025-11-14 13:51:53 +01:00
parent 5457030d4a
commit cd31d42c0c
14 changed files with 367 additions and 115 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" tools:node="remove"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+2 -2
View File
@@ -73,7 +73,7 @@
{
"photosPermission": "Nous avons besoin d'accéder à votre galerie pour vous permettre d'ajouter des photos à vos tâches.",
"cameraPermission": "Nous avons besoin d'accéder à votre appareil photo pour vous permettre de prendre des photos de vos tâches.",
"microphonePermission": false
"microphonePermission": "MusicLand uses the microphone so you can capture audio when recording videos and musical ideas."
}
],
[
@@ -101,7 +101,7 @@
"expo-camera",
{
"cameraPermission": "Allow $(PRODUCT_NAME) to access your camera",
"microphonePermission": false,
"microphonePermission": "MusicLand uses the microphone so you can record vocals, instruments, and other audio content inside the app.",
"recordAudioAndroid": false
}
],
+35 -2
View File
@@ -18,6 +18,8 @@ const {
SUNO_STATUS_PATH,
} = require("../config/suno");
const MAX_STORED_MUSIC_TRACKS = 4;
/**
* Marque un projet comme échoué suite à une erreur Suno
* @param {string} projectId - Identifiant du projet
@@ -673,8 +675,39 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
// 4) Mettre à jour le statut du projet
try {
const musicUrls = [p1?.url, p2?.url].filter(Boolean);
await refList.projects.doc(projectId).set(
const newMusicUrls = [p1?.url, p2?.url]
.filter((url) => typeof url === "string" && url.trim())
.map((url) => url.trim());
const projectRef = refList.projects.doc(projectId);
let existingMusicUrls = [];
try {
const projectSnap = await projectRef.get();
const projectData = projectSnap?.data() || {};
if (Array.isArray(projectData.musicUrls)) {
existingMusicUrls = projectData.musicUrls
.filter((url) => typeof url === "string" && url.trim())
.map((url) => url.trim());
}
} catch (readError) {
console.error(
"⚠️ [SunoCallback] Impossible de récupérer les anciennes musiques:",
readError,
);
}
const mergedMusicUrls = [...existingMusicUrls, ...newMusicUrls];
const uniqueMusicUrls = mergedMusicUrls.filter(
(url, index) => mergedMusicUrls.indexOf(url) === index,
);
const musicUrls =
uniqueMusicUrls.length > MAX_STORED_MUSIC_TRACKS
? uniqueMusicUrls.slice(
uniqueMusicUrls.length - MAX_STORED_MUSIC_TRACKS,
)
: uniqueMusicUrls;
await projectRef.set(
{
musicStatus: "GENERATED",
musicUrls,
+8 -6
View File
@@ -13,6 +13,7 @@ import { useUser } from "../../providers/UserDataProvider";
import { gutters, Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import { ensureAuthenticated } from "../../utils/authRedirect";
import { responsiveHeight } from "react-native-responsive-dimensions";
const formatDuration = (ms) => {
const totalSeconds = Math.max(0, Math.floor((ms || 0) / 1000));
@@ -103,13 +104,13 @@ const GlobalAudioPlayer = () => {
{
likedBy: next ? arrayUnion(currentUID) : arrayRemove(currentUID),
},
{ merge: true }
{ merge: true },
);
} catch (_error) {
setPendingLike(null);
}
},
[currentUID, effectiveIsLiked, isLikeProcessing, projectId]
[currentUID, effectiveIsLiked, isLikeProcessing, projectId],
);
const insets = useSafeAreaInsets();
@@ -138,7 +139,7 @@ const GlobalAudioPlayer = () => {
await resume();
}
},
[isPlaying, pause, resume]
[isPlaying, pause, resume],
);
const handleOpenDetails = useCallback(() => {
@@ -169,7 +170,9 @@ const GlobalAudioPlayer = () => {
});
}, [currentTrack, navigateToMusicDetails, projectId]);
const TAB_BAR_HEIGHT = 64;
const TAB_BAR_HEIGHT = isWeb
? 64
: (insets.bottom < 20 ? 30 : insets.bottom) + 60;
const GAP_WITH_TAB = 8;
const bottomOffset = (isWeb ? gutters : 5) + TAB_BAR_HEIGHT + GAP_WITH_TAB;
@@ -216,8 +219,7 @@ const GlobalAudioPlayer = () => {
style={[
styles.actionButton,
styles.likeButton,
(!projectId || isLikeProcessing) &&
styles.disabledAction,
(!projectId || isLikeProcessing) && styles.disabledAction,
]}
>
<Image
+2 -2
View File
@@ -12,7 +12,7 @@ import { Platform } from "react-native";
const functionsInstances = {};
const emulatorConfigured = {};
const emulatorHost = Platform.OS === "web" ? "localhost" : "192.168.1.146";
const emulatorHost = Platform.OS === "web" ? "localhost" : "192.168.1.62";
const configureFunctionsEmulator = (instance, regionKey = "us-central1") => {
if (!__DEV__ || !instance?.useEmulator || emulatorConfigured[regionKey]) {
@@ -25,7 +25,7 @@ const configureFunctionsEmulator = (instance, regionKey = "us-central1") => {
} catch (error) {
console.warn(
`[firebase] Unable to set functions emulator for region ${regionKey}`,
error?.message
error?.message,
);
}
};
+1
View File
@@ -54,6 +54,7 @@ const HIDDEN_ROUTE_NAMES = new Set([
Routes.ChangePassword,
Routes.Notifications,
Routes.Language,
Routes.Payments,
Routes.Login,
Routes.ResetPassword,
Routes.Register,
-7
View File
@@ -228,13 +228,6 @@ const HitParade = () => {
/>
<View style={{ flex: 1, gap: 24 }}>
<View style={{ gap: 11 }}>
<Pressable style={{ position: "absolute", right: 8, top: 20 }}>
<Image source={icons.calendar} />
</Pressable>
<Image
source={icons.hitParadeLogo}
style={{ alignSelf: "center" }}
/>
<Pressable onPress={handlePlayRandomSong}>
<Image
source={icons.play}
+96 -26
View File
@@ -10,6 +10,8 @@ import { Image as ExpoImage } from "expo-image";
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
import { background, cardsImg, icons } from "../../assets";
import Alert from "../../components/Alert";
import BorderGradient from "../../components/BorderGradient/BorderGradient";
import { LinearGradient } from "../../components/LinearGradient/LinearGradient";
import FullscreenIntroVideo from "../../components/FullscreenIntroVideo";
import GradientButton from "../../components/GradientButton";
import MoreMenu from "../../components/MoreMenu";
@@ -474,21 +476,23 @@ const Home = ({ navigation, route }) => {
</View>
);
const topBar = (
<View style={styles.topBar}>
<ProjectDropDown
style={styles.projectDropDown}
projects={projects}
selectedProject={currentProject}
allowEmptySelection
onSelectProject={handleSelectProject}
onModifyProject={handleModifyProject}
onCreateProject={handleStartNew}
formatDate={formatDate}
/>
</View>
);
const journeyContent = (
<>
<View style={styles.topBar}>
<ProjectDropDown
style={styles.projectDropDown}
projects={projects}
selectedProject={currentProject}
allowEmptySelection
onSelectProject={handleSelectProject}
onModifyProject={handleModifyProject}
onCreateProject={handleStartNew}
formatDate={formatDate}
/>
</View>
<MoreMenu
visible={menuVisible}
top={menuAnchor?.top ?? 0}
@@ -499,7 +503,34 @@ const Home = ({ navigation, route }) => {
extraItems={moreMenuItems}
/>
<Text style={styles.subtitle}>5 espaces à découvrir</Text>
<View style={styles.subtitleWrapper}>
{isWeb ? (
<LinearGradient
colors={["#F94697", "#7023F7"]}
start={{ x: 0, y: 0.5 }}
end={{ x: 1, y: 0.5 }}
style={[styles.subtitleGradient, styles.subtitleGradientWeb]}
>
<View style={[styles.subtitleInner, styles.subtitleInnerWeb]}>
<Text style={styles.subtitle}>5 espaces à découvrir</Text>
</View>
</LinearGradient>
) : (
<BorderGradient
style={[styles.subtitleGradient, styles.subtitleBorder]}
gradientProps={{
colors: ["#F94697", "#7023F7"],
start: { x: 0, y: 0.5 },
end: { x: 1, y: 0.5 },
locations: [0, 1],
}}
>
<View style={styles.subtitleInner}>
<Text style={styles.subtitle}>5 espaces à découvrir</Text>
</View>
</BorderGradient>
)}
</View>
{stageCardsList}
@@ -530,15 +561,21 @@ const Home = ({ navigation, route }) => {
style={styles.centerImage}
/>
{isWeb ? (
<View style={styles.contentOverlay}>{journeyContent}</View>
) : (
<ScrollView
style={[styles.mobileScrollView, styles.contentOverlay]}
contentContainerStyle={styles.mobileScrollContent}
showsVerticalScrollIndicator={false}
>
<View style={styles.contentOverlay}>
{topBar}
{journeyContent}
</ScrollView>
</View>
) : (
<View style={[styles.contentOverlay, styles.mobileContent]}>
{topBar}
<ScrollView
style={styles.mobileScrollView}
contentContainerStyle={styles.mobileScrollContent}
showsVerticalScrollIndicator={false}
>
{journeyContent}
</ScrollView>
</View>
)}
</View>
</Page>
@@ -629,6 +666,7 @@ const styles = StyleSheet.create({
gap: 12,
marginTop: isWeb ? 24 : 0,
marginBottom: isWeb ? 8 : 16,
paddingHorizontal: isWeb ? 0 : gutters,
zIndex: 10,
},
projectDropDown: {
@@ -636,6 +674,10 @@ const styles = StyleSheet.create({
maxWidth: 420,
flexGrow: 1,
},
mobileContent: {
flex: 1,
width: "100%",
},
mobileScrollView: {
flex: 1,
width: "100%",
@@ -645,16 +687,44 @@ const styles = StyleSheet.create({
paddingTop: 24,
paddingBottom: gutters * 6,
},
subtitle: {
subtitleWrapper: {
width: "100%",
fontFamily: FONT_FAMILY.InterSemiBold,
marginBottom: 16,
marginTop: 15,
alignItems: "center",
},
subtitleGradient: {
width: "100%",
maxWidth: 420,
borderRadius: 999,
alignSelf: "center",
},
subtitleBorder: {
borderWidth: 1,
},
subtitleGradientWeb: {
padding: 2,
},
subtitleInner: {
width: "100%",
paddingHorizontal: 24,
paddingVertical: 10,
alignItems: "center",
justifyContent: "center",
borderRadius: 999,
backgroundColor: Palette.glass,
},
subtitleInnerWeb: {
paddingVertical: 12,
backgroundColor: Palette.lightPurple,
},
subtitle: {
fontFamily: FONT_FAMILY.InterMedium,
fontSize: 18,
color: Palette.white,
textAlign: "center",
textTransform: "uppercase",
letterSpacing: 1,
marginBottom: 16,
marginTop: 15,
},
cardsGrid: {
width: "100%",
+21 -5
View File
@@ -1,4 +1,5 @@
import React, { useMemo, useRef, useState } from "react";
import { useRoute } from "@react-navigation/native";
import { Dimensions, Modal, Text, View } from "react-native";
import SwiperFlatList from "react-native-swiper-flatlist";
import { background, icons } from "../../assets";
@@ -32,6 +33,7 @@ const ComposeSong = () => {
const [selectedIndex, setSelectedIndex] = useState(0);
const [progress, setProgress] = useState(18);
const [containerLayout, setContainerLayout] = useState(null);
const route = useRoute();
const {
selectedProjectId,
selectedProject,
@@ -50,6 +52,7 @@ const ComposeSong = () => {
const [isConfirmVisible, setIsConfirmVisible] = useState(false);
const [isProcessingConfirmation, setIsProcessingConfirmation] =
useState(false);
const isRegenerationFlow = route?.params?.isRegeneration === true;
const coinBalance = useMemo(() => {
const value = currentUserData?.coins;
@@ -216,6 +219,11 @@ const ComposeSong = () => {
}
};
const handleCancelGeneration = () => {
setIsConfirmVisible(false);
navigate(Routes.SongReady);
};
return (
<Page backgroundImg={background.studioBG2} headerType="NONE">
<MusicLandHeader
@@ -275,11 +283,19 @@ const ComposeSong = () => {
</SwiperFlatList>
</View>
{selectedIndex !== 4 && (
<GradientButton
title={selectedIndex === 3 ? "Générer" : "Suivant"}
onPress={onPressNext}
disabled={!isStepValid}
/>
<View style={{ gap: 12 }}>
{selectedIndex === 3 && isRegenerationFlow && (
<BorderGradientButton
title="Annuler la nouvelle génération"
onPress={handleCancelGeneration}
/>
)}
<GradientButton
title={selectedIndex === 3 ? "Générer" : "Suivant"}
onPress={onPressNext}
disabled={!isStepValid}
/>
</View>
)}
</View>
<Modal
+21 -5
View File
@@ -5,6 +5,7 @@ import React, {
useRef,
useState,
} from "react";
import { useRoute } from "@react-navigation/native";
import { Dimensions, FlatList, Modal, Text, View } from "react-native";
import { background } from "../../assets";
import BorderGradientButton from "../../components/BorderGradientButton";
@@ -38,6 +39,7 @@ const ComposeSong = () => {
const [progress, setProgress] = useState(18);
const [parentLayout, setParentLayout] = useState(null);
const containerWidth = parentLayout?.width || windowWidth || 1;
const route = useRoute();
const {
selectedProjectId,
selectedProject,
@@ -53,6 +55,7 @@ const ComposeSong = () => {
const [isConfirmVisible, setIsConfirmVisible] = useState(false);
const [isProcessingConfirmation, setIsProcessingConfirmation] =
useState(false);
const isRegenerationFlow = route?.params?.isRegeneration === true;
const coinBalance = useMemo(() => {
const value = currentUserData?.coins;
@@ -164,6 +167,11 @@ const ComposeSong = () => {
[containerWidth]
);
const handleCancelGeneration = () => {
setIsConfirmVisible(false);
navigate(Routes.SongReady);
};
const persistMusicConfig = useCallback(async () => {
try {
if (!selectedProjectId) return;
@@ -304,11 +312,19 @@ const ComposeSong = () => {
/>
</View>
{selectedIndex !== steps.length && (
<GradientButton
title={selectedIndex === steps.length - 1 ? "Générer" : "Suivant"}
onPress={onPressNext}
disabled={!isStepValid}
/>
<View style={{ gap: 12 }}>
{selectedIndex === steps.length - 1 && isRegenerationFlow && (
<BorderGradientButton
title="Annuler la nouvelle génération"
onPress={handleCancelGeneration}
/>
)}
<GradientButton
title={selectedIndex === steps.length - 1 ? "Générer" : "Suivant"}
onPress={onPressNext}
disabled={!isStepValid}
/>
</View>
)}
</View>
<Modal
+168 -6
View File
@@ -2,14 +2,16 @@
import { useFocusEffect } from "@react-navigation/native";
import { BlurView } from "expo-blur";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { Image, Pressable, Text, View } from "react-native";
import { Image, Modal, Pressable, Text, View } from "react-native";
import { responsiveHeight } from "react-native-responsive-dimensions";
import { background, icons } from "../../assets";
import alert from "../../components/Alert";
import BorderGradientButton from "../../components/BorderGradientButton";
import GradientButton from "../../components/GradientButton";
import ValidateModal from "../../components/modal/ValidateModal";
import MusicLandHeader from "../../components/MusicLandHeader";
import Slider from "../../components/Slider";
import CreditAmount from "../../components/CreditAmount";
import { isWeb } from "../../hooks/useLayoutType";
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
import Page from "../../layouts/Page";
@@ -18,8 +20,13 @@ import { navigate } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider";
import { Palette, Style } from "../../styles";
import { gutters, size } from "../../styles/Style";
import { FONT_FAMILY } from "../../styles/Fonts";
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
const MUSIC_GENERATION_COIN_COST = 8;
const SONG_OPTIONS_PER_GENERATION = 2;
const REGENERATE_MODAL_MAX_WIDTH = 540;
const SongReady = () => {
const {
selectedProjectId: projectId,
@@ -28,6 +35,7 @@ const SongReady = () => {
updateUserData,
} = useUser();
const [showValidateModal, setShowValidateModal] = useState(false);
const [showRegenerateModal, setShowRegenerateModal] = useState(false);
const [musicUrls, setMusicUrls] = useState([]);
const [selectedIndex, setSelectedIndex] = useState(0);
const [isPlaying, setIsPlaying] = useState({ 0: false, 1: false });
@@ -323,6 +331,38 @@ const SongReady = () => {
setShowValidateModal(true);
};
const handleConfirmRegenerate = async () => {
setShowRegenerateModal(false);
try {
await player0?.pause?.();
await player1?.pause?.();
} catch {}
navigate(Routes.ComposeSong, { isRegeneration: true });
};
const handleRegeneratePress = () => {
if (isWeb) {
alert(
"Re-générer le morceau",
`Tu vas pouvoir modifier tes choix pour re-générer ${SONG_OPTIONS_PER_GENERATION} nouveaux morceaux pour ${MUSIC_GENERATION_COIN_COST} crédits.\n\nLes crédits seront utilisés lors de l'étape de génération.`,
[
{
text: "Annuler",
style: "cancel",
},
{
text: "Oui",
style: "confirm",
onPress: () => handleConfirmRegenerate(),
},
],
{ cancelable: true },
);
return;
}
setShowRegenerateModal(true);
};
return (
<Page headerType="NONE" backgroundImg={background.studioBG2} maxWidth={800}>
<MusicLandHeader
@@ -499,16 +539,138 @@ const SongReady = () => {
onPress={handleChooseTrack}
disabled={!musicUrls?.length}
/>
<BorderGradientButton
title="Re-générer le morceau"
onPress={handleRegeneratePress}
/>
</View>
{!isWeb && (
<ValidateModal
visible={showValidateModal}
onClose={() => setShowValidateModal(false)}
onPressValidate={validateSelection}
/>
<>
<ValidateModal
visible={showValidateModal}
onClose={() => setShowValidateModal(false)}
onPressValidate={validateSelection}
/>
<RegenerateModal
visible={showRegenerateModal}
onClose={() => setShowRegenerateModal(false)}
onConfirm={handleConfirmRegenerate}
/>
</>
)}
</Page>
);
};
const RegenerateModal = ({ visible, onClose, onConfirm }) => {
return (
<Modal
animationType="fade"
transparent
visible={visible}
onRequestClose={onClose}
>
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
paddingHorizontal: gutters,
paddingVertical: gutters * 1.5,
backgroundColor: "rgba(0, 0, 0, 0.6)",
}}
>
<CreateLyricsHeader
containerStyle={{
width: "100%",
maxWidth: REGENERATE_MODAL_MAX_WIDTH,
alignSelf: "center",
paddingVertical: 24,
paddingHorizontal: 24,
gap: 24,
}}
>
<View style={{ gap: 24, alignItems: "center" }}>
<View
style={{
gap: 12,
alignItems: "center",
paddingHorizontal: 12,
}}
>
<Text
style={{
fontSize: 22,
color: Palette.white,
fontFamily: FONT_FAMILY.InterSemiBold,
textAlign: "center",
}}
>
Re-générer le morceau ?
</Text>
<Text
style={{
fontSize: 16,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
textAlign: "center",
}}
>
{`Tu vas pouvoir modifier tes choix pour re-générer ${SONG_OPTIONS_PER_GENERATION} nouveaux morceaux.`}
</Text>
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
flexWrap: "wrap",
gap: 6,
}}
>
<Text
style={{
fontSize: 16,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
textAlign: "center",
}}
>
Cette action coûte
</Text>
<CreditAmount
value={MUSIC_GENERATION_COIN_COST}
iconSize={18}
textStyle={{
fontSize: 16,
color: Palette.white,
fontFamily: FONT_FAMILY.InterSemiBold,
textAlign: "center",
}}
/>
</View>
<Text
style={{
fontSize: 16,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
textAlign: "center",
}}
>
Les crédits seront utilisés lors de l'étape de génération.
</Text>
</View>
<View style={{ width: "85%", alignSelf: "center", gap: 15 }}>
<GradientButton
title="Oui, modifier mes choix"
onPress={onConfirm}
/>
<BorderGradientButton title="Retour" onPress={onClose} />
</View>
</View>
</CreateLyricsHeader>
</View>
</Modal>
);
};
export default SongReady;
+4 -11
View File
@@ -147,9 +147,7 @@ const CreateLyricsWithAi = () => {
Array.isArray(customStructure) &&
fallbackStructure?.length &&
(customStructure.length !== fallbackStructure.length ||
customStructure.some(
(value, idx) => value !== fallbackStructure[idx]
))
customStructure.some((value, idx) => value !== fallbackStructure[idx]))
) {
setCustomStructure(fallbackStructure);
}
@@ -218,10 +216,7 @@ const CreateLyricsWithAi = () => {
fallbackProjectStructure,
]);
const progress = useMemo(
() => 16 + selectedIndex * 9,
[selectedIndex]
);
const progress = useMemo(() => 16 + selectedIndex * 9, [selectedIndex]);
const lyricsConfig = useMemo(() => {
const resolvedObjective = shouldUseOtherObjective
@@ -428,7 +423,7 @@ const CreateLyricsWithAi = () => {
<MusicLandHeader
onPressBack={onPressBack}
progress={progress}
logo={icons.musicLandWriting}
// logo={icons.musicLandWriting}
/>
<View
style={{
@@ -437,9 +432,7 @@ const CreateLyricsWithAi = () => {
gap: responsiveHeight(5),
}}
>
<View
style={{ flex: 1 }}
>
<View style={{ flex: 1 }}>
{currentStep ? (
<View
key={currentStep.key}
+5 -21
View File
@@ -251,27 +251,11 @@ const PouchReady = () => {
};
const playbackStage = getStageAction("director", projectForStage);
await setLoading(false);
alert(
"Playback prêt",
"Super ! Ta pochette est validée. Tu peux retourner à l'accueil ou continuer avec Theo pour produire ton playback.",
[
{
text: "Retour à l'accueil",
style: "cancel",
onPress: () => navigate(Routes.Home),
},
{
text: "Continuer avec Theo",
onPress: () => {
const targetRoute = playbackStage?.route || Routes.Playback;
const params = playbackStage?.params || {
project: projectForStage,
};
navigate(targetRoute, params);
},
},
],
);
const targetRoute = playbackStage?.route || Routes.Playback;
const params = playbackStage?.params || {
project: projectForStage,
};
navigate(targetRoute, params);
} catch (e) {
console.log("PouchReady: unable to validate cover", e?.message);
} finally {
+3 -21
View File
@@ -3,7 +3,6 @@ import React, { useCallback, useMemo, useState } from "react";
import { Pressable, Text, View } from "react-native";
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
import { background } from "../../assets";
import alert from "../../components/Alert";
import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader";
import { isWeb } from "../../hooks/useLayoutType";
@@ -116,26 +115,9 @@ const ValidateCover = () => {
};
const playbackStage = getStageAction("director", projectForStage);
await setIsLoading(false);
alert(
"Playback prêt",
"Super ! Ta pochette est validée. Tu peux retourner à l'accueil ou continuer avec Theo pour produire ton playback.",
[
{
text: "Retour à l'accueil",
style: "cancel",
onPress: () => navigate(Routes.Home),
},
{
text: "Continuer avec Theo",
onPress: () => {
const targetRoute = playbackStage?.route || Routes.Playback;
const params =
playbackStage?.params || { project: projectForStage };
navigate(targetRoute, params);
},
},
]
);
const targetRoute = playbackStage?.route || Routes.Playback;
const params = playbackStage?.params || { project: projectForStage };
navigate(targetRoute, params);
return;
} catch (e) {
console.log(e);