player, song generation again, home and design fixes
This commit is contained in:
@@ -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"/>
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -54,6 +54,7 @@ const HIDDEN_ROUTE_NAMES = new Set([
|
||||
Routes.ChangePassword,
|
||||
Routes.Notifications,
|
||||
Routes.Language,
|
||||
Routes.Payments,
|
||||
Routes.Login,
|
||||
Routes.ResetPassword,
|
||||
Routes.Register,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,8 +476,7 @@ const Home = ({ navigation, route }) => {
|
||||
</View>
|
||||
);
|
||||
|
||||
const journeyContent = (
|
||||
<>
|
||||
const topBar = (
|
||||
<View style={styles.topBar}>
|
||||
<ProjectDropDown
|
||||
style={styles.projectDropDown}
|
||||
@@ -488,7 +489,10 @@ const Home = ({ navigation, route }) => {
|
||||
formatDate={formatDate}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
const journeyContent = (
|
||||
<>
|
||||
<MoreMenu
|
||||
visible={menuVisible}
|
||||
top={menuAnchor?.top ?? 0}
|
||||
@@ -499,7 +503,34 @@ const Home = ({ navigation, route }) => {
|
||||
extraItems={moreMenuItems}
|
||||
/>
|
||||
|
||||
<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>
|
||||
<View style={styles.contentOverlay}>
|
||||
{topBar}
|
||||
{journeyContent}
|
||||
</View>
|
||||
) : (
|
||||
<View style={[styles.contentOverlay, styles.mobileContent]}>
|
||||
{topBar}
|
||||
<ScrollView
|
||||
style={[styles.mobileScrollView, styles.contentOverlay]}
|
||||
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%",
|
||||
|
||||
@@ -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 && (
|
||||
<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
|
||||
|
||||
@@ -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 && (
|
||||
<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
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
<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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
],
|
||||
);
|
||||
} catch (e) {
|
||||
console.log("PouchReady: unable to validate cover", e?.message);
|
||||
} finally {
|
||||
|
||||
@@ -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 };
|
||||
const params = playbackStage?.params || { project: projectForStage };
|
||||
navigate(targetRoute, params);
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
|
||||
Reference in New Issue
Block a user