stripe embedded and other fixes
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
useWindowDimensions,
|
||||
} from "react-native";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import {
|
||||
@@ -74,6 +75,8 @@ const MusicDetails = ({ route }) => {
|
||||
const hasAutoPlayedRef = React.useRef(false);
|
||||
const { isLooping, setLooping } = usePlayer() || {};
|
||||
const isWeb = Platform.OS === "web";
|
||||
const { width: windowWidth = 0 } = useWindowDimensions();
|
||||
const isCompactLayout = (windowWidth || 0) < 1200;
|
||||
const handleBackPress = useCallback(() => {
|
||||
goBack();
|
||||
}, []);
|
||||
@@ -964,6 +967,7 @@ const MusicDetails = ({ route }) => {
|
||||
headerType="NAVIGATION"
|
||||
hideBackButton={isWeb}
|
||||
topStickyContent={renderWebBackButton}
|
||||
width={isCompactLayout ? "100%" : undefined}
|
||||
title={action === "userProfile" ? "Mon profil" : "Détail musique"}
|
||||
backgroundImg={
|
||||
action === "userProfile"
|
||||
@@ -993,11 +997,12 @@ const MusicDetails = ({ route }) => {
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingBottom: gutters * 2,
|
||||
flexDirection: "row",
|
||||
flexDirection: isCompactLayout ? "column" : "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
alignItems: isCompactLayout ? "stretch" : "center",
|
||||
height: "auto",
|
||||
gap: 24,
|
||||
gap: isCompactLayout ? 16 : 24,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{/* Left column: player */}
|
||||
@@ -1005,13 +1010,14 @@ const MusicDetails = ({ route }) => {
|
||||
intensity={40}
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingRight: 8,
|
||||
height: 400,
|
||||
paddingRight: isCompactLayout ? 0 : 8,
|
||||
height: isCompactLayout ? undefined : 400,
|
||||
borderRadius: 12,
|
||||
padding: 10,
|
||||
borderWidth: 1,
|
||||
borderColor: Palette.transparentWhite,
|
||||
maxWidth: "50%",
|
||||
maxWidth: isCompactLayout ? "100%" : "50%",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
@@ -1159,20 +1165,25 @@ const MusicDetails = ({ route }) => {
|
||||
intensity={40}
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingLeft: 8,
|
||||
paddingLeft: isCompactLayout ? 0 : 8,
|
||||
padding: 10,
|
||||
borderWidth: 1,
|
||||
borderColor: Palette.transparentWhite,
|
||||
borderRadius: 12,
|
||||
height: 400,
|
||||
maxWidth: "50%",
|
||||
height: isCompactLayout ? undefined : 400,
|
||||
maxWidth: isCompactLayout ? "100%" : "50%",
|
||||
width: "100%",
|
||||
marginTop: isCompactLayout ? 12 : 0,
|
||||
}}
|
||||
>
|
||||
{sections.length ? (
|
||||
<ScrollView
|
||||
ref={lyricsRef}
|
||||
style={{ flex: 1 }}
|
||||
contentContainerStyle={{ paddingBottom: 40, height: 400 }}
|
||||
contentContainerStyle={{
|
||||
paddingBottom: 40,
|
||||
...(isCompactLayout ? {} : { height: 400 }),
|
||||
}}
|
||||
>
|
||||
{sections.map((section, sectionIdx) => (
|
||||
<View
|
||||
@@ -1231,7 +1242,10 @@ const MusicDetails = ({ route }) => {
|
||||
) : description?.length > 0 ? (
|
||||
<ScrollView
|
||||
style={{ flex: 1 }}
|
||||
contentContainerStyle={{ paddingBottom: 40 }}
|
||||
contentContainerStyle={{
|
||||
paddingBottom: 40,
|
||||
...(isCompactLayout ? {} : { height: 400 }),
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
|
||||
@@ -8,7 +8,7 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Text, TouchableOpacity, View } from "react-native";
|
||||
import { BackHandler, Text, TouchableOpacity, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import Svg, { Circle } from "react-native-svg";
|
||||
import alert from "../../components/Alert";
|
||||
@@ -54,6 +54,10 @@ const RecordPlayback = ({ route }) => {
|
||||
const checkSongEndRef = useRef(null);
|
||||
const stopRequestedRef = useRef(false);
|
||||
const restartRequestedRef = useRef(false);
|
||||
const manualRestartInFlightRef = useRef(false);
|
||||
const stopRequestedAtRef = useRef(0);
|
||||
const activeRecordingPromiseRef = useRef(null);
|
||||
const exitRequestedRef = useRef(false);
|
||||
const startedRef = useRef(false); // empêche les doubles démarrages
|
||||
const countdownActiveRef = useRef(false); // évite le déclenchement avant 1er tick
|
||||
const playbackStartedRef = useRef(false); // devient vrai lorsque l'audio progresse réellement
|
||||
@@ -259,45 +263,58 @@ const RecordPlayback = ({ route }) => {
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Reset complet
|
||||
const resetSession = useCallback(async () => {
|
||||
try {
|
||||
if (countdownTimerRef.current) {
|
||||
clearInterval(countdownTimerRef.current);
|
||||
countdownTimerRef.current = null;
|
||||
}
|
||||
if (listenTimerRef.current) {
|
||||
clearInterval(listenTimerRef.current);
|
||||
listenTimerRef.current = null;
|
||||
log("listenTimerRef cleared");
|
||||
}
|
||||
if (checkSongEndRef.current) {
|
||||
clearInterval(checkSongEndRef.current);
|
||||
checkSongEndRef.current = null;
|
||||
log("checkSongEndRef cleared");
|
||||
}
|
||||
const resetSession = useCallback(
|
||||
async ({
|
||||
preserveSongEndWatcher = false,
|
||||
preserveStopRequest = false,
|
||||
} = {}) => {
|
||||
try {
|
||||
if (countdownTimerRef.current) {
|
||||
clearInterval(countdownTimerRef.current);
|
||||
countdownTimerRef.current = null;
|
||||
}
|
||||
if (listenTimerRef.current) {
|
||||
clearInterval(listenTimerRef.current);
|
||||
listenTimerRef.current = null;
|
||||
log("listenTimerRef cleared");
|
||||
}
|
||||
if (checkSongEndRef.current) {
|
||||
if (preserveSongEndWatcher) {
|
||||
log("checkSongEndRef preserved");
|
||||
} else {
|
||||
clearInterval(checkSongEndRef.current);
|
||||
checkSongEndRef.current = null;
|
||||
log("checkSongEndRef cleared");
|
||||
}
|
||||
}
|
||||
|
||||
startedRef.current = false;
|
||||
stopRequestedRef.current = false;
|
||||
countdownActiveRef.current = false;
|
||||
listenedMsRef.current = 0;
|
||||
incrementDoneRef.current = false;
|
||||
playbackStartedRef.current = false;
|
||||
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 };
|
||||
log("Session reset");
|
||||
startedRef.current = false;
|
||||
if (!preserveStopRequest) {
|
||||
stopRequestedRef.current = false;
|
||||
stopRequestedAtRef.current = 0;
|
||||
}
|
||||
countdownActiveRef.current = false;
|
||||
listenedMsRef.current = 0;
|
||||
incrementDoneRef.current = false;
|
||||
playbackStartedRef.current = false;
|
||||
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 };
|
||||
log("Session reset");
|
||||
|
||||
setIsPreparing(false);
|
||||
setIsRecording(false);
|
||||
setShowProgress(false);
|
||||
setCountdown(0);
|
||||
setIsPreparing(false);
|
||||
setIsRecording(false);
|
||||
setShowProgress(false);
|
||||
setCountdown(0);
|
||||
|
||||
if (player) {
|
||||
try {
|
||||
if (player.playing) await player.pause?.();
|
||||
await player.seekTo?.(0);
|
||||
} catch (_) {}
|
||||
}
|
||||
} catch (_) {}
|
||||
}, [player]);
|
||||
if (player) {
|
||||
try {
|
||||
if (player.playing) await player.pause?.();
|
||||
await player.seekTo?.(0);
|
||||
} catch (_) {}
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
[player]
|
||||
);
|
||||
|
||||
const resetSessionRef = useRef(resetSession);
|
||||
useEffect(() => {
|
||||
@@ -307,6 +324,7 @@ const RecordPlayback = ({ route }) => {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
log("Screen focused, resetting session");
|
||||
exitRequestedRef.current = false;
|
||||
void resetSessionRef.current?.();
|
||||
return () => {
|
||||
log("Screen blurred, stopping playback");
|
||||
@@ -337,15 +355,47 @@ const RecordPlayback = ({ route }) => {
|
||||
}, [setLooping])
|
||||
);
|
||||
|
||||
const discardRecordingFile = useCallback(async (uri, reason = "") => {
|
||||
if (!uri) return;
|
||||
try {
|
||||
const info = await FileSystem.getInfoAsync(uri);
|
||||
if (info?.exists) {
|
||||
await FileSystem.deleteAsync(uri, { idempotent: true });
|
||||
log("Discarded recording file", { reason: reason || "cleanup" });
|
||||
}
|
||||
} catch (error) {
|
||||
log("Failed to discard recording", {
|
||||
reason: reason || "cleanup",
|
||||
message: error?.message || String(error || ""),
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Lancer le compte à rebours (le tick décrémente uniquement)
|
||||
const startCountdownThenRecord = async () => {
|
||||
const startCountdownThenRecord = async ({
|
||||
preserveRestartFlag = false,
|
||||
preserveSongEndWatcher = false,
|
||||
preserveStopRequest = false,
|
||||
} = {}) => {
|
||||
if (!songUrl) {
|
||||
log("startCountdownThenRecord aborted: missing song URL");
|
||||
return;
|
||||
}
|
||||
log("startCountdownThenRecord invoked", { projectId, songUrl });
|
||||
await resetSession();
|
||||
restartRequestedRef.current = false;
|
||||
log("startCountdownThenRecord invoked", {
|
||||
projectId,
|
||||
songUrl,
|
||||
preserveRestartFlag,
|
||||
preserveSongEndWatcher,
|
||||
preserveStopRequest,
|
||||
});
|
||||
await resetSession({
|
||||
preserveSongEndWatcher,
|
||||
preserveStopRequest,
|
||||
});
|
||||
if (!preserveRestartFlag) {
|
||||
restartRequestedRef.current = false;
|
||||
manualRestartInFlightRef.current = false;
|
||||
}
|
||||
setIsPreparing(true);
|
||||
setShowProgress(false);
|
||||
setCountdown(5);
|
||||
@@ -387,6 +437,7 @@ const RecordPlayback = ({ route }) => {
|
||||
const startRecordingWithMusic = async () => {
|
||||
try {
|
||||
stopRequestedRef.current = false;
|
||||
stopRequestedAtRef.current = 0;
|
||||
listenedMsRef.current = 0;
|
||||
incrementDoneRef.current = false;
|
||||
|
||||
@@ -399,6 +450,18 @@ const RecordPlayback = ({ route }) => {
|
||||
songUrl,
|
||||
hasCamera: !!cameraRef.current,
|
||||
});
|
||||
|
||||
if (activeRecordingPromiseRef.current) {
|
||||
log("Waiting for previous recording to finish before starting a new one");
|
||||
try {
|
||||
await activeRecordingPromiseRef.current;
|
||||
} catch (error) {
|
||||
log("Previous recording promise rejected", {
|
||||
message: error?.message || String(error || ""),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const recordPromise = (() => {
|
||||
const camera = cameraRef.current;
|
||||
if (!camera) throw new Error("Caméra indisponible");
|
||||
@@ -451,6 +514,7 @@ const RecordPlayback = ({ route }) => {
|
||||
"L'enregistrement vidéo n'est pas supporté sur cet appareil."
|
||||
);
|
||||
})();
|
||||
activeRecordingPromiseRef.current = recordPromise;
|
||||
|
||||
if (player && songUrl) {
|
||||
try {
|
||||
@@ -501,7 +565,18 @@ const RecordPlayback = ({ route }) => {
|
||||
log("Song end watcher armed");
|
||||
checkSongEndRef.current = setInterval(() => {
|
||||
try {
|
||||
if (!player || stopRequestedRef.current) return;
|
||||
if (!player) return;
|
||||
if (stopRequestedRef.current) {
|
||||
const sinceLastRequest = Date.now() - (stopRequestedAtRef.current || 0);
|
||||
if (sinceLastRequest >= 1200) {
|
||||
stopRequestedAtRef.current = Date.now();
|
||||
try {
|
||||
cameraRef.current?.stopRecording?.();
|
||||
log("stopRecording retried while awaiting stop");
|
||||
} catch (_) {}
|
||||
}
|
||||
return;
|
||||
}
|
||||
const duration = (player?.duration || 0) * 1000;
|
||||
const currentTime = (player?.currentTime || 0) * 1000;
|
||||
if (
|
||||
@@ -524,10 +599,7 @@ const RecordPlayback = ({ route }) => {
|
||||
playing: player?.playing,
|
||||
});
|
||||
stopRequestedRef.current = true;
|
||||
if (checkSongEndRef.current) {
|
||||
clearInterval(checkSongEndRef.current);
|
||||
checkSongEndRef.current = null;
|
||||
}
|
||||
stopRequestedAtRef.current = Date.now();
|
||||
try {
|
||||
cameraRef.current?.stopRecording?.();
|
||||
log("stopRecording triggered");
|
||||
@@ -539,6 +611,9 @@ const RecordPlayback = ({ route }) => {
|
||||
|
||||
const video = await recordPromise;
|
||||
log("Recording promise resolved", { hasVideo: !!video?.uri });
|
||||
activeRecordingPromiseRef.current = null;
|
||||
stopRequestedRef.current = false;
|
||||
stopRequestedAtRef.current = 0;
|
||||
|
||||
if (checkSongEndRef.current) {
|
||||
clearInterval(checkSongEndRef.current);
|
||||
@@ -559,30 +634,31 @@ const RecordPlayback = ({ route }) => {
|
||||
log("Recording flow completed", { hasVideo: !!video?.uri });
|
||||
playbackStartedRef.current = false;
|
||||
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 };
|
||||
const exitRequested = exitRequestedRef.current;
|
||||
const shouldRestart = restartRequestedRef.current;
|
||||
|
||||
if (exitRequested) {
|
||||
exitRequestedRef.current = false;
|
||||
restartRequestedRef.current = false;
|
||||
manualRestartInFlightRef.current = false;
|
||||
await discardRecordingFile(video?.uri, "exit");
|
||||
log("Recording aborted before completion, skipping navigation");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldRestart) {
|
||||
restartRequestedRef.current = false;
|
||||
}
|
||||
|
||||
if (video?.uri && shouldRestart) {
|
||||
try {
|
||||
const info = await FileSystem.getInfoAsync(video.uri);
|
||||
if (info?.exists) {
|
||||
await FileSystem.deleteAsync(video.uri, { idempotent: true });
|
||||
log("Discarded interim recording file");
|
||||
}
|
||||
} catch (error) {
|
||||
log("Failed to discard interim recording", {
|
||||
message: error?.message || String(error || ""),
|
||||
await discardRecordingFile(video?.uri, "restart");
|
||||
const manualRestartPending = manualRestartInFlightRef.current;
|
||||
manualRestartInFlightRef.current = false;
|
||||
if (manualRestartPending) {
|
||||
log("Manual restart already scheduled, waiting for countdown");
|
||||
} else {
|
||||
log("Restart requested, relaunching countdown");
|
||||
requestAnimationFrame(() => {
|
||||
void startCountdownThenRecord();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldRestart) {
|
||||
log("Restart requested, relaunching countdown");
|
||||
requestAnimationFrame(() => {
|
||||
void startCountdownThenRecord();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -602,6 +678,9 @@ const RecordPlayback = ({ route }) => {
|
||||
message: e?.message || String(e || ""),
|
||||
});
|
||||
console.log("RecordPlayback error:", e);
|
||||
activeRecordingPromiseRef.current = null;
|
||||
stopRequestedRef.current = false;
|
||||
stopRequestedAtRef.current = 0;
|
||||
setIsRecording(false);
|
||||
setIsPreparing(false);
|
||||
setShowProgress(false);
|
||||
@@ -617,13 +696,27 @@ const RecordPlayback = ({ route }) => {
|
||||
}
|
||||
playbackStartedRef.current = false;
|
||||
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 };
|
||||
const exitRequested = exitRequestedRef.current;
|
||||
const shouldRestart = restartRequestedRef.current;
|
||||
if (exitRequested) {
|
||||
exitRequestedRef.current = false;
|
||||
restartRequestedRef.current = false;
|
||||
manualRestartInFlightRef.current = false;
|
||||
log("Recording aborted, skipping error handling");
|
||||
return;
|
||||
}
|
||||
if (shouldRestart) {
|
||||
restartRequestedRef.current = false;
|
||||
log("Restart requested despite error, restarting flow");
|
||||
requestAnimationFrame(() => {
|
||||
void startCountdownThenRecord();
|
||||
});
|
||||
const manualRestartPending = manualRestartInFlightRef.current;
|
||||
manualRestartInFlightRef.current = false;
|
||||
if (manualRestartPending) {
|
||||
log("Manual restart already scheduled after error");
|
||||
} else {
|
||||
log("Restart requested despite error, restarting flow");
|
||||
requestAnimationFrame(() => {
|
||||
void startCountdownThenRecord();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Inform the user when using a simulator where recording isn't supported
|
||||
@@ -648,25 +741,94 @@ const RecordPlayback = ({ route }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBackPress = useCallback(() => {
|
||||
const busy = isPreparing || isRecording;
|
||||
log("Back button pressed", { isPreparing, isRecording, busy });
|
||||
if (busy) {
|
||||
exitRequestedRef.current = true;
|
||||
restartRequestedRef.current = false;
|
||||
manualRestartInFlightRef.current = false;
|
||||
stopRequestedRef.current = true;
|
||||
stopRequestedAtRef.current = Date.now();
|
||||
if (isPreparing && countdownTimerRef.current) {
|
||||
clearInterval(countdownTimerRef.current);
|
||||
countdownTimerRef.current = null;
|
||||
}
|
||||
try {
|
||||
if (player?.playing) {
|
||||
const maybePromise = player.pause?.();
|
||||
if (maybePromise && typeof maybePromise.catch === "function") {
|
||||
maybePromise.catch(() => {});
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
try {
|
||||
if (isRecording) {
|
||||
cameraRef.current?.stopRecording?.();
|
||||
}
|
||||
} catch (_) {}
|
||||
} else {
|
||||
exitRequestedRef.current = false;
|
||||
}
|
||||
try {
|
||||
goBack();
|
||||
} catch (_) {}
|
||||
return true;
|
||||
}, [goBack, isPreparing, isRecording, player]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const subscription = BackHandler.addEventListener(
|
||||
"hardwareBackPress",
|
||||
() => handleBackPress()
|
||||
);
|
||||
return () => subscription.remove();
|
||||
}, [handleBackPress])
|
||||
);
|
||||
|
||||
const handleRestartRecording = async () => {
|
||||
try {
|
||||
const hasRecordingPending = !!activeRecordingPromiseRef.current;
|
||||
log("Restart button pressed", {
|
||||
isPreparing,
|
||||
isRecording,
|
||||
hasRecordingPending,
|
||||
});
|
||||
if (isPreparing || !isRecording) {
|
||||
if (isPreparing) {
|
||||
if (hasRecordingPending) {
|
||||
await startCountdownThenRecord({
|
||||
preserveRestartFlag: true,
|
||||
preserveSongEndWatcher: true,
|
||||
preserveStopRequest: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
restartRequestedRef.current = false;
|
||||
manualRestartInFlightRef.current = false;
|
||||
await startCountdownThenRecord();
|
||||
return;
|
||||
}
|
||||
if (!isRecording) {
|
||||
restartRequestedRef.current = false;
|
||||
manualRestartInFlightRef.current = false;
|
||||
await startCountdownThenRecord();
|
||||
return;
|
||||
}
|
||||
restartRequestedRef.current = true;
|
||||
manualRestartInFlightRef.current = true;
|
||||
stopRequestedRef.current = true;
|
||||
stopRequestedAtRef.current = Date.now();
|
||||
try {
|
||||
if (player?.playing) await player.pause?.();
|
||||
} catch (_) {}
|
||||
try {
|
||||
cameraRef.current?.stopRecording?.();
|
||||
} catch (_) {}
|
||||
await startCountdownThenRecord({
|
||||
preserveRestartFlag: true,
|
||||
preserveSongEndWatcher: true,
|
||||
preserveStopRequest: true,
|
||||
});
|
||||
} catch (_) {}
|
||||
};
|
||||
|
||||
@@ -693,7 +855,7 @@ const RecordPlayback = ({ route }) => {
|
||||
paddingBottom: gutters * 2,
|
||||
}}
|
||||
>
|
||||
<MusicLandHeader progress={9} onPressBack={goBack} />
|
||||
<MusicLandHeader progress={9} onPressBack={handleBackPress} />
|
||||
|
||||
<View style={{ marginTop: 12, alignItems: "flex-end" }}>
|
||||
<CameraFacingSelector
|
||||
@@ -869,7 +1031,6 @@ const ProgressRing = ({ size = 50, strokeWidth = 12, progress = 0 }) => {
|
||||
height={size}
|
||||
style={{
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
backgroundColor: "#ffffff3d",
|
||||
borderRadius: size / 2,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -202,9 +202,51 @@ const SongReady = () => {
|
||||
|
||||
const handleRegeneratePress = () => {
|
||||
if (isWeb) {
|
||||
const descriptionTextStyle = {
|
||||
fontSize: 16,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
textAlign: "center",
|
||||
};
|
||||
const amountTextStyle = {
|
||||
...descriptionTextStyle,
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
};
|
||||
|
||||
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.`,
|
||||
(
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
<Text style={descriptionTextStyle}>
|
||||
{`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={descriptionTextStyle}>Cette action coûte</Text>
|
||||
<CreditAmount
|
||||
value={MUSIC_GENERATION_COIN_COST}
|
||||
iconSize={18}
|
||||
textStyle={amountTextStyle}
|
||||
/>
|
||||
</View>
|
||||
<Text style={descriptionTextStyle}>
|
||||
Les crédits seront utilisés lors de l'étape de génération.
|
||||
</Text>
|
||||
</View>
|
||||
),
|
||||
[
|
||||
{
|
||||
text: "Annuler",
|
||||
|
||||
Reference in New Issue
Block a user