more tickets
This commit is contained in:
@@ -99,8 +99,10 @@ export default ({ navigation }) => {
|
||||
<Input
|
||||
label="Adresse email"
|
||||
placeholder="Votre adresse email"
|
||||
type="email"
|
||||
value={email}
|
||||
setValue={setEmail}
|
||||
isBlur
|
||||
containerStyle={{ marginBottom: 20 }}
|
||||
textInputProps={{
|
||||
keyboardType: "email-address",
|
||||
|
||||
+117
-63
@@ -5,7 +5,7 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { ScrollView, StyleSheet, Text, View } from "react-native";
|
||||
import { Image as ExpoImage } from "expo-image";
|
||||
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
|
||||
import { background, cardsImg, icons } from "../../assets";
|
||||
@@ -14,7 +14,6 @@ import FullscreenIntroVideo from "../../components/FullscreenIntroVideo";
|
||||
import GradientButton from "../../components/GradientButton";
|
||||
import MoreMenu from "../../components/MoreMenu";
|
||||
import ProjectDropDown from "../../components/ProjectDropDown/ProjectDropDown";
|
||||
import ShareBtn from "../../components/ShareBtn/ShareBtn";
|
||||
import { projectsRef, usersRef } from "../../config/firebase";
|
||||
import { isWeb } from "../../hooks/useLayoutType.js";
|
||||
import Page from "../../layouts/Page";
|
||||
@@ -22,7 +21,7 @@ import LandingPage from "../LandingPage";
|
||||
import { navigate } from "../../navigation/NavigationService";
|
||||
import { Routes } from "../../navigation/Routes";
|
||||
import { useUser } from "../../providers/UserDataProvider";
|
||||
import { Palette } from "../../styles";
|
||||
import { Palette, gutters } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import {
|
||||
getCreationStageStates,
|
||||
@@ -43,6 +42,8 @@ const isProjectEmpty = (project) => {
|
||||
|
||||
const HOME_BACKGROUND_WIDTH = isWeb ? 1280 : 520;
|
||||
const HOME_BACKGROUND_HEIGHT = isWeb ? 760 : 360;
|
||||
const HOME_BACKGROUND_STYLE_WIDTH = HOME_BACKGROUND_WIDTH + 120;
|
||||
const HOME_BACKGROUND_STYLE_HEIGHT = HOME_BACKGROUND_HEIGHT + 80;
|
||||
|
||||
const STAGE_CARD_CONTENT = [
|
||||
{
|
||||
@@ -59,7 +60,7 @@ const STAGE_CARD_CONTENT = [
|
||||
key: "beatmaker",
|
||||
step: "ÉTAPE 2",
|
||||
description:
|
||||
"Je suis Malik, responsable du studio de musicland, je vais mettre en musique tes paroles en fonction de tes goûts musicaux, ça va être top!",
|
||||
"Bienvenue au studio MusicLand.\nNous mettons en musique tes paroles en fonction de tes inspirations, ça va être top !",
|
||||
image: cardsImg.studio,
|
||||
imagePosition: "right",
|
||||
textAlign: "right",
|
||||
@@ -79,7 +80,7 @@ const STAGE_CARD_CONTENT = [
|
||||
key: "publisher",
|
||||
step: "ÉTAPE 4",
|
||||
description:
|
||||
"Je suis Mr Benhaï, Producteur de MusicLand et je vais te faire une proposition qui pourrait t’intéresser, on se retrouve à la sotie du studio!",
|
||||
"À la sortie du studio, l'équipe production te propose les prochaines étapes pour partager ta musique avec le monde.",
|
||||
image: cardsImg.production,
|
||||
imagePosition: "right",
|
||||
textAlign: "right",
|
||||
@@ -292,14 +293,14 @@ const Home = ({ navigation, route }) => {
|
||||
: null;
|
||||
Alert(
|
||||
"Céline",
|
||||
"Bravo ! Vous venez de terminer de créer les paroles de votre musique ! Maintenant vous pouvez passer à la prochaine étape : le Studio avec Malik. Dans cette étape vous allez pouvoir donner vie à votre chanson !",
|
||||
"Bravo ! Vous venez de terminer de créer les paroles de votre musique ! Maintenant vous pouvez passer à la prochaine étape : le Studio pour donner vie à votre chanson !",
|
||||
[
|
||||
{
|
||||
text: "Retour à l'accueil",
|
||||
style: "cancel",
|
||||
},
|
||||
{
|
||||
text: "Continuer avec Malik",
|
||||
text: "Continuer vers le Studio",
|
||||
onPress: () => {
|
||||
if (!currentProject?.id) {
|
||||
return;
|
||||
@@ -448,6 +449,68 @@ const Home = ({ navigation, route }) => {
|
||||
navigate(Routes.Payments);
|
||||
}, []);
|
||||
|
||||
const stageCardContainerStyle = isWeb ? styles.cardsGrid : styles.cardsStack;
|
||||
const stageCardItemStyle = isWeb
|
||||
? styles.webStageCard
|
||||
: styles.mobileStageCard;
|
||||
|
||||
const stageCardsList = (
|
||||
<View style={stageCardContainerStyle}>
|
||||
{stageCards.map((card) => (
|
||||
<StageCard
|
||||
key={card.key}
|
||||
step={card.step}
|
||||
description={card.description}
|
||||
image={card.image}
|
||||
imagePosition={card.imagePosition}
|
||||
textAlign={card.textAlign}
|
||||
isLocked={card.isLocked}
|
||||
lockSide={card.lockSide}
|
||||
onPress={() => handleStagePress(card.key, card.isLocked)}
|
||||
containerStyle={stageCardItemStyle}
|
||||
variant={isWeb ? "web" : "mobile"}
|
||||
/>
|
||||
))}
|
||||
</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}
|
||||
position={menuAnchor}
|
||||
onClose={handleCloseMenu}
|
||||
inPlaylist={false}
|
||||
projectId={menuProject?.id || null}
|
||||
extraItems={moreMenuItems}
|
||||
/>
|
||||
|
||||
<Text style={styles.subtitle}>5 espaces à découvrir</Text>
|
||||
|
||||
{stageCardsList}
|
||||
|
||||
<ClubCard
|
||||
image={CLUB_CARD_IMAGE}
|
||||
onPress={handleClubPress}
|
||||
hasActiveSubscription={hasActiveSubscription}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return adventureStarted ? (
|
||||
<View style={styles.root}>
|
||||
<Page
|
||||
@@ -466,54 +529,17 @@ const Home = ({ navigation, route }) => {
|
||||
contentFit="cover"
|
||||
style={styles.centerImage}
|
||||
/>
|
||||
|
||||
<View style={styles.topBar}>
|
||||
<ProjectDropDown
|
||||
style={styles.projectDropDown}
|
||||
projects={projects}
|
||||
selectedProject={currentProject}
|
||||
allowEmptySelection
|
||||
onSelectProject={handleSelectProject}
|
||||
onModifyProject={handleModifyProject}
|
||||
onCreateProject={handleStartNew}
|
||||
formatDate={formatDate}
|
||||
/>
|
||||
{!isWeb && <ShareBtn />}
|
||||
</View>
|
||||
|
||||
<MoreMenu
|
||||
visible={menuVisible}
|
||||
top={menuAnchor?.top ?? 0}
|
||||
position={menuAnchor}
|
||||
onClose={handleCloseMenu}
|
||||
inPlaylist={false}
|
||||
projectId={menuProject?.id || null}
|
||||
extraItems={moreMenuItems}
|
||||
/>
|
||||
|
||||
<Text style={styles.subtitle}>5 espaces à découvrir</Text>
|
||||
|
||||
<View style={styles.cardsGrid}>
|
||||
{stageCards.map((card) => (
|
||||
<StageCard
|
||||
key={card.key}
|
||||
step={card.step}
|
||||
description={card.description}
|
||||
image={card.image}
|
||||
imagePosition={card.imagePosition}
|
||||
textAlign={card.textAlign}
|
||||
isLocked={card.isLocked}
|
||||
lockSide={card.lockSide}
|
||||
onPress={() => handleStagePress(card.key, card.isLocked)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<ClubCard
|
||||
image={CLUB_CARD_IMAGE}
|
||||
onPress={handleClubPress}
|
||||
hasActiveSubscription={hasActiveSubscription}
|
||||
/>
|
||||
{isWeb ? (
|
||||
<View style={styles.contentOverlay}>{journeyContent}</View>
|
||||
) : (
|
||||
<ScrollView
|
||||
style={[styles.mobileScrollView, styles.contentOverlay]}
|
||||
contentContainerStyle={styles.mobileScrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{journeyContent}
|
||||
</ScrollView>
|
||||
)}
|
||||
</View>
|
||||
</Page>
|
||||
</View>
|
||||
@@ -534,7 +560,7 @@ const Home = ({ navigation, route }) => {
|
||||
}}
|
||||
>
|
||||
<GradientButton
|
||||
url={videos.landing}
|
||||
url={videoUrl}
|
||||
title="Commencer l'aventure MusicLand"
|
||||
onPress={handleStartVisit}
|
||||
/>
|
||||
@@ -572,20 +598,27 @@ const styles = StyleSheet.create({
|
||||
alignSelf: "stretch",
|
||||
alignItems: "stretch",
|
||||
justifyContent: "flex-start",
|
||||
position: "relative",
|
||||
},
|
||||
centerImage: {
|
||||
width: HOME_BACKGROUND_WIDTH + 120,
|
||||
height: HOME_BACKGROUND_HEIGHT + 80,
|
||||
width: HOME_BACKGROUND_STYLE_WIDTH,
|
||||
height: HOME_BACKGROUND_STYLE_HEIGHT,
|
||||
borderRadius: 22,
|
||||
overflow: "hidden",
|
||||
position: "absolute",
|
||||
top: "45%",
|
||||
top: isWeb ? "45%" : "50%",
|
||||
left: "50%",
|
||||
transform: [
|
||||
{ translateX: -HOME_BACKGROUND_WIDTH / 2 },
|
||||
{ translateY: -HOME_BACKGROUND_HEIGHT / 2 },
|
||||
{ translateX: -HOME_BACKGROUND_STYLE_WIDTH / 2 },
|
||||
{ translateY: -HOME_BACKGROUND_STYLE_HEIGHT / 2 },
|
||||
],
|
||||
pointerEvents: "none",
|
||||
zIndex: 0,
|
||||
},
|
||||
contentOverlay: {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
flexGrow: 1,
|
||||
},
|
||||
topBar: {
|
||||
width: "100%",
|
||||
@@ -594,8 +627,8 @@ const styles = StyleSheet.create({
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 12,
|
||||
marginTop: 24,
|
||||
marginBottom: 8,
|
||||
marginTop: isWeb ? 24 : 0,
|
||||
marginBottom: isWeb ? 8 : 16,
|
||||
zIndex: 10,
|
||||
},
|
||||
projectDropDown: {
|
||||
@@ -603,6 +636,15 @@ const styles = StyleSheet.create({
|
||||
maxWidth: 420,
|
||||
flexGrow: 1,
|
||||
},
|
||||
mobileScrollView: {
|
||||
flex: 1,
|
||||
width: "100%",
|
||||
},
|
||||
mobileScrollContent: {
|
||||
paddingHorizontal: gutters,
|
||||
paddingTop: 24,
|
||||
paddingBottom: gutters * 6,
|
||||
},
|
||||
subtitle: {
|
||||
width: "100%",
|
||||
fontFamily: FONT_FAMILY.InterSemiBold,
|
||||
@@ -621,4 +663,16 @@ const styles = StyleSheet.create({
|
||||
justifyContent: "space-between",
|
||||
rowGap: 20,
|
||||
},
|
||||
cardsStack: {
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
alignItems: "stretch",
|
||||
},
|
||||
webStageCard: {
|
||||
width: "48%",
|
||||
},
|
||||
mobileStageCard: {
|
||||
width: "100%",
|
||||
marginBottom: 20,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -14,21 +14,41 @@ const StageCard = ({
|
||||
textAlign = "left",
|
||||
isLocked,
|
||||
onPress,
|
||||
containerStyle = null,
|
||||
variant = "web",
|
||||
}) => {
|
||||
const isImageOnLeft = imagePosition !== "right";
|
||||
const isTextRight = textAlign === "right";
|
||||
const isMobileVariant = variant === "mobile";
|
||||
const isImageOnLeft = !isMobileVariant && imagePosition !== "right";
|
||||
const isTextRight = !isMobileVariant && textAlign === "right";
|
||||
|
||||
const imageBlock = (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
const basePressableStyle =
|
||||
variant === "web"
|
||||
? { width: "48%" }
|
||||
: {
|
||||
width: "100%",
|
||||
borderRadius: 24,
|
||||
overflow: "hidden",
|
||||
zIndex: 10,
|
||||
},
|
||||
isImageOnLeft
|
||||
};
|
||||
|
||||
const imageContainerBaseStyle = {
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
overflow: "hidden",
|
||||
zIndex: 10,
|
||||
};
|
||||
|
||||
const imageContainerVariantStyle = isMobileVariant
|
||||
? {
|
||||
width: "100%",
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
marginBottom: 0,
|
||||
}
|
||||
: {
|
||||
flex: 1,
|
||||
...(isImageOnLeft
|
||||
? {
|
||||
borderTopLeftRadius: 20,
|
||||
borderBottomLeftRadius: 20,
|
||||
@@ -40,22 +60,26 @@ const StageCard = ({
|
||||
borderBottomRightRadius: 20,
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
},
|
||||
]}
|
||||
>
|
||||
}),
|
||||
};
|
||||
|
||||
const imageBlock = (
|
||||
<View style={[imageContainerBaseStyle, imageContainerVariantStyle]}>
|
||||
<ExpoImage
|
||||
source={image}
|
||||
contentFit="contain"
|
||||
style={[
|
||||
{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
minHeight: 210,
|
||||
height: isMobileVariant ? 190 : "100%",
|
||||
minHeight: isMobileVariant ? 190 : 210,
|
||||
borderRadius: 0,
|
||||
},
|
||||
isImageOnLeft
|
||||
? { alignSelf: "flex-end" }
|
||||
: { alignSelf: "flex-start" },
|
||||
isMobileVariant
|
||||
? { alignSelf: "center" }
|
||||
: isImageOnLeft
|
||||
? { alignSelf: "flex-end" }
|
||||
: { alignSelf: "flex-start" },
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
@@ -70,9 +94,11 @@ const StageCard = ({
|
||||
alignItems: "center",
|
||||
marginBottom: 8,
|
||||
},
|
||||
isTextRight
|
||||
? { justifyContent: "flex-end" }
|
||||
: { justifyContent: "flex-start" },
|
||||
isMobileVariant
|
||||
? { justifyContent: "center" }
|
||||
: isTextRight
|
||||
? { justifyContent: "flex-end" }
|
||||
: { justifyContent: "flex-start" },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
@@ -83,7 +109,11 @@ const StageCard = ({
|
||||
color: Palette.white,
|
||||
flexShrink: 1,
|
||||
},
|
||||
isTextRight ? { textAlign: "right" } : { textAlign: "left" },
|
||||
isMobileVariant
|
||||
? { textAlign: "center" }
|
||||
: isTextRight
|
||||
? { textAlign: "right" }
|
||||
: { textAlign: "left" },
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
@@ -92,21 +122,29 @@ const StageCard = ({
|
||||
</View>
|
||||
);
|
||||
|
||||
const textBlock = (
|
||||
<BlurView
|
||||
tint="dark"
|
||||
intensity={30}
|
||||
style={[
|
||||
{
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
maxWidth: 380,
|
||||
paddingVertical: 20,
|
||||
paddingHorizontal: 20,
|
||||
justifyContent: "center",
|
||||
alignSelf: "center",
|
||||
},
|
||||
isImageOnLeft
|
||||
const textBlockBaseStyle = {
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
paddingVertical: 20,
|
||||
paddingHorizontal: 20,
|
||||
justifyContent: "center",
|
||||
alignSelf: "center",
|
||||
};
|
||||
|
||||
const textBlockVariantStyle = isMobileVariant
|
||||
? {
|
||||
width: "100%",
|
||||
borderBottomLeftRadius: 24,
|
||||
borderBottomRightRadius: 24,
|
||||
alignItems: "center",
|
||||
paddingVertical: 20,
|
||||
paddingHorizontal: 20,
|
||||
marginTop: 0,
|
||||
gap: 6,
|
||||
}
|
||||
: {
|
||||
maxWidth: 380,
|
||||
...(isImageOnLeft
|
||||
? {
|
||||
borderTopRightRadius: 20,
|
||||
borderBottomRightRadius: 20,
|
||||
@@ -114,9 +152,17 @@ const StageCard = ({
|
||||
: {
|
||||
borderTopLeftRadius: 20,
|
||||
borderBottomLeftRadius: 20,
|
||||
},
|
||||
isTextRight ? { alignItems: "flex-end" } : { alignItems: "flex-start" },
|
||||
]}
|
||||
}),
|
||||
...(isTextRight
|
||||
? { alignItems: "flex-end" }
|
||||
: { alignItems: "flex-start" }),
|
||||
};
|
||||
|
||||
const textBlock = (
|
||||
<BlurView
|
||||
tint="dark"
|
||||
intensity={30}
|
||||
style={[textBlockBaseStyle, textBlockVariantStyle]}
|
||||
>
|
||||
{isLocked ? (
|
||||
<View
|
||||
@@ -132,7 +178,11 @@ const StageCard = ({
|
||||
justifyContent: "center",
|
||||
zIndex: 2,
|
||||
},
|
||||
isTextRight ? { left: 10 } : { right: 10 },
|
||||
isMobileVariant
|
||||
? { right: 10 }
|
||||
: isTextRight
|
||||
? { left: 10 }
|
||||
: { right: 10 },
|
||||
]}
|
||||
>
|
||||
<FontAwesome name="lock" size={18} color={Palette.primary} />
|
||||
@@ -141,7 +191,11 @@ const StageCard = ({
|
||||
{header}
|
||||
<Text
|
||||
style={[
|
||||
isTextRight ? { textAlign: "right" } : { textAlign: "left" },
|
||||
isMobileVariant
|
||||
? { textAlign: "center" }
|
||||
: isTextRight
|
||||
? { textAlign: "right" }
|
||||
: { textAlign: "left" },
|
||||
{
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 13,
|
||||
@@ -155,7 +209,12 @@ const StageCard = ({
|
||||
</BlurView>
|
||||
);
|
||||
|
||||
const content = isImageOnLeft ? (
|
||||
const content = isMobileVariant ? (
|
||||
<>
|
||||
{imageBlock}
|
||||
{textBlock}
|
||||
</>
|
||||
) : isImageOnLeft ? (
|
||||
<>
|
||||
{imageBlock}
|
||||
{textBlock}
|
||||
@@ -172,15 +231,14 @@ const StageCard = ({
|
||||
onPress={onPress}
|
||||
disabled={isLocked}
|
||||
style={({ pressed }) => [
|
||||
{
|
||||
width: "48%",
|
||||
},
|
||||
basePressableStyle,
|
||||
containerStyle,
|
||||
pressed && !isLocked && { opacity: 0.85 },
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
flexDirection: isMobileVariant ? "column" : "row",
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
|
||||
@@ -159,7 +159,7 @@ const MusicDetails = ({ route }) => {
|
||||
title,
|
||||
artist,
|
||||
}),
|
||||
[projectId, title, artist]
|
||||
[projectId, title, artist],
|
||||
);
|
||||
|
||||
const handleShare = useCallback(() => {
|
||||
@@ -488,7 +488,13 @@ const MusicDetails = ({ route }) => {
|
||||
console.log("MusicDetails seek error", e?.message);
|
||||
}
|
||||
},
|
||||
[trackDescriptor, sliderDurationMs, isCurrentTrack, ensureLoaded, seekTrackTo]
|
||||
[
|
||||
trackDescriptor,
|
||||
sliderDurationMs,
|
||||
isCurrentTrack,
|
||||
ensureLoaded,
|
||||
seekTrackTo,
|
||||
],
|
||||
);
|
||||
|
||||
const handleToggleLoop = useCallback(async () => {
|
||||
@@ -582,13 +588,7 @@ const MusicDetails = ({ route }) => {
|
||||
wasPlayingBeforeSeek.current = false;
|
||||
lastSeekTargetMs.current = null;
|
||||
}
|
||||
}, [
|
||||
ensureLoaded,
|
||||
isCurrentTrack,
|
||||
positionMs,
|
||||
resumeTrack,
|
||||
seekTrackTo,
|
||||
]);
|
||||
}, [ensureLoaded, isCurrentTrack, positionMs, resumeTrack, seekTrackTo]);
|
||||
|
||||
const handleSeekBySeconds = useCallback(
|
||||
async (deltaSeconds) => {
|
||||
@@ -605,7 +605,7 @@ const MusicDetails = ({ route }) => {
|
||||
console.log("MusicDetails seekBy error", e?.message);
|
||||
}
|
||||
},
|
||||
[trackDescriptor, positionMs, isCurrentTrack, ensureLoaded, seekTrackBy]
|
||||
[trackDescriptor, positionMs, isCurrentTrack, ensureLoaded, seekTrackBy],
|
||||
);
|
||||
|
||||
const handleLyricsSeek = useCallback(
|
||||
@@ -638,7 +638,7 @@ const MusicDetails = ({ route }) => {
|
||||
seekTrackTo,
|
||||
isTrackPlaying,
|
||||
resumeTrack,
|
||||
]
|
||||
],
|
||||
);
|
||||
const description = useMemo(() => {
|
||||
// Build a readable text from lyrics with section labels
|
||||
@@ -676,12 +676,12 @@ const MusicDetails = ({ route }) => {
|
||||
// Current time in seconds for highlighting
|
||||
const currentTimeS = useMemo(
|
||||
() => Math.max(0, (positionMs || 0) / 1000),
|
||||
[positionMs]
|
||||
[positionMs],
|
||||
);
|
||||
// Preview lead: show words 0.5s earlier
|
||||
const visibleTimeS = useMemo(
|
||||
() => Math.max(0, currentTimeS + 0.5),
|
||||
[currentTimeS]
|
||||
[currentTimeS],
|
||||
);
|
||||
// Helpers to group aligned words into sections and timed lines
|
||||
const SECTION_TAG_REGEX = /^\s*\[([^\]]+)\]\s*$/i;
|
||||
@@ -844,11 +844,11 @@ const MusicDetails = ({ route }) => {
|
||||
const startS = lines.reduce(
|
||||
(acc, line) =>
|
||||
Math.min(acc, Number.isFinite(line.startS) ? line.startS : acc),
|
||||
Number.POSITIVE_INFINITY
|
||||
Number.POSITIVE_INFINITY,
|
||||
);
|
||||
const endS = lines.reduce(
|
||||
(acc, line) => Math.max(acc, Number(line.endS || 0)),
|
||||
0
|
||||
0,
|
||||
);
|
||||
grouped.push({
|
||||
key: current.key,
|
||||
@@ -910,7 +910,7 @@ const MusicDetails = ({ route }) => {
|
||||
|
||||
const flatLines = useMemo(
|
||||
() => sections.flatMap((section) => section.lines),
|
||||
[sections]
|
||||
[sections],
|
||||
);
|
||||
const currentLineIdx = useMemo(() => {
|
||||
if (!flatLines || flatLines.length === 0) return -1;
|
||||
@@ -941,14 +941,14 @@ const MusicDetails = ({ route }) => {
|
||||
lineRelativeYRef.current[lineIdx] = { sectionKey, relativeY };
|
||||
updateLineAbsoluteOffset(lineIdx);
|
||||
},
|
||||
[updateLineAbsoluteOffset]
|
||||
[updateLineAbsoluteOffset],
|
||||
);
|
||||
const registerSectionOffset = useCallback(
|
||||
(sectionKey, y, lines = []) => {
|
||||
sectionYRef.current[sectionKey] = y;
|
||||
lines.forEach((line) => updateLineAbsoluteOffset(line.globalIdx));
|
||||
},
|
||||
[updateLineAbsoluteOffset]
|
||||
[updateLineAbsoluteOffset],
|
||||
);
|
||||
useEffect(() => {
|
||||
const y = lineYRef.current?.[currentLineIdx];
|
||||
@@ -966,7 +966,9 @@ const MusicDetails = ({ route }) => {
|
||||
topStickyContent={renderWebBackButton}
|
||||
title={action === "userProfile" ? "Mon profil" : "Détail musique"}
|
||||
backgroundImg={
|
||||
action === "userProfile" ? background.profileBG : background.libraryBG2
|
||||
action === "userProfile"
|
||||
? background.profileBG
|
||||
: background.libraryBG2Web
|
||||
}
|
||||
shareBtn={
|
||||
sharePayload
|
||||
@@ -1179,7 +1181,7 @@ const MusicDetails = ({ route }) => {
|
||||
registerSectionOffset(
|
||||
section.key,
|
||||
e.nativeEvent?.layout?.y,
|
||||
section.lines
|
||||
section.lines,
|
||||
)
|
||||
}
|
||||
style={[
|
||||
@@ -1198,7 +1200,7 @@ const MusicDetails = ({ route }) => {
|
||||
registerLineRelativeOffset(
|
||||
line.globalIdx,
|
||||
section.key,
|
||||
e.nativeEvent?.layout?.y ?? 0
|
||||
e.nativeEvent?.layout?.y ?? 0,
|
||||
);
|
||||
}}
|
||||
style={{
|
||||
|
||||
@@ -2,7 +2,7 @@ import FontAwesome from "@expo/vector-icons/FontAwesome";
|
||||
import { BlurView } from "expo-blur";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import { Image, Platform, Pressable, Text, View } from "react-native";
|
||||
import { ai, background } from "../assets";
|
||||
import { ai, background, cardsImg } from "../assets";
|
||||
import Page from "../layouts/Page";
|
||||
import { navigate } from "../navigation/NavigationService";
|
||||
import { useUserData } from "../providers/UserDataProvider";
|
||||
@@ -32,15 +32,15 @@ const CREATE_DATA = [
|
||||
stageKey: "director",
|
||||
img: ai.john,
|
||||
bg: background.playbackBG,
|
||||
label: "John",
|
||||
desc: "Come back when your\naudio is ready!",
|
||||
label: "Theo",
|
||||
desc: "Theo t'accompagne pour créer ton playback.",
|
||||
type: "Director",
|
||||
},
|
||||
{
|
||||
stageKey: "publisher",
|
||||
img: ai.bena,
|
||||
img: cardsImg.production,
|
||||
bg: background.productionBG2,
|
||||
label: "Bena",
|
||||
label: "Publication",
|
||||
desc: "Publions ta vidéo sur YouTube !",
|
||||
type: "Producteur",
|
||||
},
|
||||
|
||||
+86
-125
@@ -1,7 +1,6 @@
|
||||
import React from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Linking,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
Text,
|
||||
@@ -17,13 +16,7 @@ import { background, subBadges } from "../assets";
|
||||
import { Palette, gutters } from "../styles";
|
||||
import { FONT_FAMILY } from "../styles/Fonts";
|
||||
import { isWeb } from "../hooks/useLayoutType";
|
||||
import { getFunctionsClient } from "../config/firebase";
|
||||
|
||||
const FUNCTIONS_REGION = "europe-west1";
|
||||
|
||||
const STRIPE_SUCCESS_URL =
|
||||
"https://dashboard.stripe.com/test/billing/starter-guide/checkout-success";
|
||||
const STRIPE_CANCEL_URL = STRIPE_SUCCESS_URL;
|
||||
import { useStripe } from "../providers/StripeProvider";
|
||||
|
||||
const formatCurrency = (amount, currency = "eur") => {
|
||||
if (typeof amount !== "number") {
|
||||
@@ -298,120 +291,114 @@ export default function Payments() {
|
||||
initialPack && initialPack !== "packs" ? initialPack : null;
|
||||
|
||||
const backgroundImage = background.bgTrans;
|
||||
const [plans, setPlans] = React.useState({ monthly: [], annual: [] });
|
||||
const {
|
||||
subscriptions,
|
||||
isCatalogLoading,
|
||||
catalogError,
|
||||
createSubscriptionCheckout,
|
||||
} = useStripe();
|
||||
const [selectedPriceIds, setSelectedPriceIds] = React.useState({
|
||||
monthly: null,
|
||||
annual: null,
|
||||
});
|
||||
const [billingPeriod, setBillingPeriod] = React.useState("monthly");
|
||||
const [isLoadingPlans, setIsLoadingPlans] = React.useState(true);
|
||||
const [processingPriceId, setProcessingPriceId] = React.useState(null);
|
||||
const [errorMessage, setErrorMessage] = React.useState(null);
|
||||
const initialPackHandledRef = React.useRef(false);
|
||||
|
||||
const normalizedPlans = React.useMemo(
|
||||
() => ({
|
||||
monthly: normalizePlans(subscriptions?.monthly, "monthly"),
|
||||
annual: normalizePlans(subscriptions?.annual, "annual"),
|
||||
}),
|
||||
[subscriptions?.monthly, subscriptions?.annual],
|
||||
);
|
||||
const hasAnyPlan =
|
||||
(normalizedPlans?.monthly?.length || 0) > 0 ||
|
||||
(normalizedPlans?.annual?.length || 0) > 0;
|
||||
const isLoadingPlans = isCatalogLoading && !hasAnyPlan;
|
||||
const combinedErrorMessage = errorMessage || catalogError;
|
||||
|
||||
React.useEffect(() => {
|
||||
initialPackHandledRef.current = false;
|
||||
}, [initialSubscriptionPack]);
|
||||
|
||||
const fetchPlans = React.useCallback(async () => {
|
||||
setIsLoadingPlans(true);
|
||||
setErrorMessage(null);
|
||||
try {
|
||||
const callable = getFunctionsClient(FUNCTIONS_REGION).httpsCallable(
|
||||
"subscription-listSubscriptionPlans",
|
||||
);
|
||||
const { data } = await callable();
|
||||
const normalizedPlans = {
|
||||
monthly: normalizePlans(data?.plans?.monthly, "monthly"),
|
||||
annual: normalizePlans(data?.plans?.annual, "annual"),
|
||||
};
|
||||
React.useEffect(() => {
|
||||
const shouldApplyPack =
|
||||
Boolean(initialSubscriptionPack) && !initialPackHandledRef.current;
|
||||
|
||||
setPlans(normalizedPlans);
|
||||
let matchedPeriod = null;
|
||||
let matchedPriceId = null;
|
||||
|
||||
let matchedPeriod = null;
|
||||
let matchedPriceId = null;
|
||||
const shouldApplyPack =
|
||||
Boolean(initialSubscriptionPack) && !initialPackHandledRef.current;
|
||||
|
||||
if (shouldApplyPack) {
|
||||
const desired = initialSubscriptionPack.trim().toLowerCase();
|
||||
for (const [periodKey, mapping] of Object.entries(
|
||||
PACK_PRICE_ID_BY_PERIOD,
|
||||
)) {
|
||||
const candidateId = mapping[desired];
|
||||
if (!candidateId) continue;
|
||||
const exists = (normalizedPlans[periodKey] || []).some(
|
||||
(plan) => plan?.priceId === candidateId,
|
||||
);
|
||||
if (exists) {
|
||||
matchedPeriod = periodKey;
|
||||
matchedPriceId = candidateId;
|
||||
break;
|
||||
}
|
||||
if (shouldApplyPack && normalizedPlans) {
|
||||
const desired = initialSubscriptionPack.trim().toLowerCase();
|
||||
for (const [periodKey, mapping] of Object.entries(
|
||||
PACK_PRICE_ID_BY_PERIOD,
|
||||
)) {
|
||||
const candidateId = mapping[desired];
|
||||
if (!candidateId) continue;
|
||||
const exists = (normalizedPlans[periodKey] || []).some(
|
||||
(plan) => plan?.priceId === candidateId,
|
||||
);
|
||||
if (exists) {
|
||||
matchedPeriod = periodKey;
|
||||
matchedPriceId = candidateId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedPriceIds((current) => {
|
||||
const next = { ...current };
|
||||
setSelectedPriceIds((current) => {
|
||||
const next = { ...current };
|
||||
|
||||
PLAN_SEGMENTS.forEach(({ key }) => {
|
||||
const periodPlans = normalizedPlans[key] || [];
|
||||
if (!periodPlans.length) {
|
||||
next[key] = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const alreadySelected = periodPlans.some(
|
||||
(plan) => plan.priceId === next[key],
|
||||
);
|
||||
|
||||
if (shouldApplyPack && matchedPeriod === null) {
|
||||
const matched = periodPlans.find((plan) => {
|
||||
const label = (plan?.product?.name || plan?.nickname || "")
|
||||
.toString()
|
||||
.toLowerCase();
|
||||
return label.includes(initialSubscriptionPack);
|
||||
});
|
||||
|
||||
if (matched) {
|
||||
matchedPeriod = key;
|
||||
matchedPriceId = matched.priceId;
|
||||
}
|
||||
}
|
||||
|
||||
next[key] = alreadySelected ? next[key] : periodPlans[0].priceId;
|
||||
});
|
||||
|
||||
if (matchedPeriod && matchedPriceId) {
|
||||
next[matchedPeriod] = matchedPriceId;
|
||||
PLAN_SEGMENTS.forEach(({ key }) => {
|
||||
const periodPlans = normalizedPlans[key] || [];
|
||||
if (!periodPlans.length) {
|
||||
next[key] = null;
|
||||
return;
|
||||
}
|
||||
|
||||
return next;
|
||||
const alreadySelected = periodPlans.some(
|
||||
(plan) => plan.priceId === next[key],
|
||||
);
|
||||
|
||||
if (shouldApplyPack && matchedPeriod === null) {
|
||||
const matched = periodPlans.find((plan) => {
|
||||
const label = (plan?.product?.name || plan?.nickname || "")
|
||||
.toString()
|
||||
.toLowerCase();
|
||||
return label.includes(initialSubscriptionPack);
|
||||
});
|
||||
|
||||
if (matched) {
|
||||
matchedPeriod = key;
|
||||
matchedPriceId = matched.priceId;
|
||||
}
|
||||
}
|
||||
|
||||
next[key] = alreadySelected ? next[key] : periodPlans[0].priceId;
|
||||
});
|
||||
|
||||
setProcessingPriceId(null);
|
||||
|
||||
if (matchedPeriod && matchedPriceId) {
|
||||
setBillingPeriod(matchedPeriod);
|
||||
initialPackHandledRef.current = true;
|
||||
} else if (shouldApplyPack) {
|
||||
initialPackHandledRef.current = true;
|
||||
next[matchedPeriod] = matchedPriceId;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[Payments] fetchPlans error", error);
|
||||
setErrorMessage(
|
||||
error?.message || "Impossible de récupérer les abonnements Stripe.",
|
||||
);
|
||||
} finally {
|
||||
setIsLoadingPlans(false);
|
||||
|
||||
return next;
|
||||
});
|
||||
|
||||
if (matchedPeriod && matchedPriceId) {
|
||||
setBillingPeriod(matchedPeriod);
|
||||
initialPackHandledRef.current = true;
|
||||
} else if (shouldApplyPack && !isCatalogLoading) {
|
||||
initialPackHandledRef.current = true;
|
||||
}
|
||||
}, [initialSubscriptionPack]);
|
||||
}, [
|
||||
normalizedPlans,
|
||||
initialSubscriptionPack,
|
||||
isCatalogLoading,
|
||||
]);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchPlans();
|
||||
}, [fetchPlans]);
|
||||
|
||||
const currentPlans = plans[billingPeriod] || [];
|
||||
const currentPlans = normalizedPlans[billingPeriod] || [];
|
||||
const selectedPriceId = selectedPriceIds[billingPeriod];
|
||||
|
||||
const handleSelect = React.useCallback(
|
||||
@@ -433,33 +420,7 @@ export default function Payments() {
|
||||
setProcessingPriceId(selectedPriceId);
|
||||
setErrorMessage(null);
|
||||
try {
|
||||
const callable = getFunctionsClient(FUNCTIONS_REGION).httpsCallable(
|
||||
"subscription-createSubscriptionCheckoutSession",
|
||||
);
|
||||
const { data } = await callable({
|
||||
priceId: selectedPriceId,
|
||||
returnUrls: {
|
||||
successUrl: STRIPE_SUCCESS_URL,
|
||||
cancelUrl: STRIPE_CANCEL_URL,
|
||||
},
|
||||
});
|
||||
|
||||
const checkoutUrl = data?.url;
|
||||
if (!checkoutUrl) {
|
||||
throw new Error("Session Stripe introuvable.");
|
||||
}
|
||||
|
||||
if (isWeb) {
|
||||
if (typeof window !== "undefined") {
|
||||
window.location.assign(checkoutUrl);
|
||||
}
|
||||
} else {
|
||||
const canOpen = await Linking.canOpenURL(checkoutUrl);
|
||||
if (!canOpen) {
|
||||
throw new Error("Impossible d'ouvrir l'URL de paiement.");
|
||||
}
|
||||
await Linking.openURL(checkoutUrl);
|
||||
}
|
||||
await createSubscriptionCheckout(selectedPriceId);
|
||||
} catch (error) {
|
||||
console.error("[Payments] checkout error", error);
|
||||
setErrorMessage(
|
||||
@@ -469,7 +430,7 @@ export default function Payments() {
|
||||
} finally {
|
||||
setProcessingPriceId(null);
|
||||
}
|
||||
}, [isWeb, selectedPriceId]);
|
||||
}, [selectedPriceId, createSubscriptionCheckout]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setSelectedPriceIds((current) => {
|
||||
@@ -523,8 +484,8 @@ export default function Payments() {
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{errorMessage ? (
|
||||
<Text style={styles.errorText}>{errorMessage}</Text>
|
||||
{combinedErrorMessage ? (
|
||||
<Text style={styles.errorText}>{combinedErrorMessage}</Text>
|
||||
) : null}
|
||||
|
||||
{isLoadingPlans && !currentPlans.length ? (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Image, StyleSheet, View } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import React from "react";
|
||||
import Page from "../../layouts/Page";
|
||||
import { ai, background } from "../../assets";
|
||||
import { background } from "../../assets";
|
||||
import { gutters } from "../../styles";
|
||||
import GradientButton from "../../components/GradientButton";
|
||||
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||
@@ -12,7 +12,6 @@ import { Routes } from "../../navigation";
|
||||
const Production = () => {
|
||||
return (
|
||||
<Page headerType="NONE" backgroundImg={background.productionBG}>
|
||||
<Image source={ai.bena} style={styles.img} resizeMode="contain" />
|
||||
<MusicLandHeader onPressBack={goBack} progress={25} />
|
||||
<View
|
||||
style={{
|
||||
@@ -43,13 +42,3 @@ const Production = () => {
|
||||
};
|
||||
|
||||
export default Production;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
img: {
|
||||
width: "100%",
|
||||
height: "70%",
|
||||
position: "absolute",
|
||||
bottom: -40,
|
||||
right: -30,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Routes } from "../../navigation/Routes";
|
||||
import { useUserData } from "../../providers/UserDataProvider";
|
||||
import { gutters, Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import { formatDate, toDate } from "../../utils/dateFormatting";
|
||||
|
||||
const FUNCTIONS_REGION = "europe-west1";
|
||||
|
||||
@@ -73,49 +74,6 @@ const getStatusColors = (status) => {
|
||||
}
|
||||
};
|
||||
|
||||
const toDate = (value) => {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
if (typeof value.toDate === "function") {
|
||||
try {
|
||||
return value.toDate();
|
||||
} catch (_error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
if (value > 1e12) {
|
||||
return new Date(value);
|
||||
}
|
||||
return new Date(value * 1000);
|
||||
}
|
||||
if (typeof value === "object" && Number.isFinite(value.seconds)) {
|
||||
return new Date(value.seconds * 1000);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const formatDate = (date) => {
|
||||
if (!date) {
|
||||
return "À déterminer";
|
||||
}
|
||||
try {
|
||||
return new Intl.DateTimeFormat("fr-FR", {
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}).format(date);
|
||||
} catch (_error) {
|
||||
return date.toString();
|
||||
}
|
||||
};
|
||||
|
||||
const capitalize = (value) => {
|
||||
if (typeof value !== "string" || !value) {
|
||||
return null;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useRoute } from "@react-navigation/native";
|
||||
import { Image } from "expo-image";
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import { Linking, Text, View } from "react-native";
|
||||
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
|
||||
import { ai, background } from "../../assets";
|
||||
import { background } from "../../assets";
|
||||
import BorderGradientButton from "../../components/BorderGradientButton";
|
||||
import GradientButton from "../../components/GradientButton";
|
||||
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||
@@ -58,10 +57,10 @@ const PublishYoutube = () => {
|
||||
: "Publier sur YouTube";
|
||||
const statusMessage = useMemo(() => {
|
||||
if (!playbackReady) {
|
||||
return "Termine la création de ton playback avec John pour débloquer la publication YouTube.";
|
||||
return "Termine la création de ton playback avec Theo pour débloquer la publication YouTube.";
|
||||
}
|
||||
if (isPublishing) {
|
||||
return "Bena s'occupe de mettre ta vidéo en ligne... Patiente un instant.";
|
||||
return "La publication de ta vidéo est en cours... Patiente un instant.";
|
||||
}
|
||||
if (youtubeError || youtubeStatus === "FAILED") {
|
||||
return "La dernière tentative de publication a échoué. Tu peux réessayer ci-dessous.";
|
||||
@@ -69,7 +68,7 @@ const PublishYoutube = () => {
|
||||
if (hasYoutubePublication) {
|
||||
return "Ta vidéo est publiée sur la chaîne YouTube MusicLand. Tu peux la partager dès maintenant.";
|
||||
}
|
||||
return "Bena t’accompagne pour partager ton playback sur la chaîne YouTube MusicLand.";
|
||||
return "Partage ton playback sur la chaîne YouTube MusicLand.";
|
||||
}, [
|
||||
hasYoutubePublication,
|
||||
isPublishing,
|
||||
@@ -175,11 +174,6 @@ const PublishYoutube = () => {
|
||||
}}
|
||||
>
|
||||
<View style={{ alignItems: "center", gap: 18 }}>
|
||||
<Image
|
||||
source={ai.bena}
|
||||
style={{ width: 220, height: 320 }}
|
||||
contentFit="contain"
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 22,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Image, StyleSheet, View } from "react-native";
|
||||
import { ai, background } from "../../assets";
|
||||
import { View } from "react-native";
|
||||
import { background } from "../../assets";
|
||||
import FullscreenIntroVideo from "../../components/FullscreenIntroVideo";
|
||||
import GradientButton from "../../components/GradientButton";
|
||||
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||
@@ -21,7 +21,6 @@ const Compose = () => {
|
||||
}, []);
|
||||
return (
|
||||
<Page backgroundImg={background.studioBG2} headerType="NONE">
|
||||
<Image source={ai.malik} style={styles.img} resizeMode="contain" />
|
||||
<MusicLandHeader
|
||||
onPressBack={goBack}
|
||||
progress={9}
|
||||
@@ -53,13 +52,3 @@ const Compose = () => {
|
||||
};
|
||||
|
||||
export default Compose;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
img: {
|
||||
width: "100%",
|
||||
height: "70%",
|
||||
position: "absolute",
|
||||
bottom: -40,
|
||||
right: -30,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import moment from "moment";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Image, Platform, Pressable, Text, View } from "react-native";
|
||||
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
|
||||
import { ai, icons } from "../../assets";
|
||||
import { icons } from "../../assets";
|
||||
import AppAlert from "../../components/Alert";
|
||||
import GradientButton from "../../components/GradientButton";
|
||||
import ProgressBar from "../../components/ProgressBar";
|
||||
@@ -194,22 +194,6 @@ const CreatingSong = ({ active, config }) => {
|
||||
height: "50%",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
zIndex: 1,
|
||||
position: "absolute",
|
||||
top: -150,
|
||||
width: "50%",
|
||||
height: "80%",
|
||||
alignSelf: "center",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={ai.malik}
|
||||
style={{ width: "100%", height: "100%", right: -10 }}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
|
||||
@@ -338,7 +338,7 @@ const SongReady = () => {
|
||||
<View style={{ flex: 1, marginTop: 16 }}>
|
||||
<CreateLyricsHeader
|
||||
title="Ta chanson est prête !"
|
||||
subTitle="Qu’en penses-tu ?"
|
||||
subTitle="Choisis ton morceau"
|
||||
containerStyle={{
|
||||
marginBottom: responsiveHeight(2),
|
||||
}}
|
||||
@@ -432,7 +432,11 @@ const SongReady = () => {
|
||||
try {
|
||||
const player = idx === 0 ? player0 : player1;
|
||||
if (player && wasPlayingBeforeSeek.current[idx]) {
|
||||
await player.play?.();
|
||||
if (player.resume) {
|
||||
await player.resume?.();
|
||||
} else {
|
||||
await player.play?.();
|
||||
}
|
||||
setIsPlaying((p) => ({ ...p, [idx]: true }));
|
||||
}
|
||||
wasPlayingBeforeSeek.current[idx] = false;
|
||||
|
||||
@@ -286,7 +286,7 @@ const Lyrics = ({ navigation }) => {
|
||||
await setIsLoading(false);
|
||||
alert(
|
||||
"Céline",
|
||||
"Bravo ! Vous venez de terminer de créer les paroles de votre musique ! Maintenant vous pouvez passer à la prochaine étape : le Studio avec Malik. Dans cette étape vous allez pouvoir donner vie à votre chanson !",
|
||||
"Bravo ! Vous venez de terminer de créer les paroles de votre musique ! Maintenant vous pouvez passer à la prochaine étape : le Studio pour donner vie à votre chanson !",
|
||||
[
|
||||
{
|
||||
text: "Retour à l'accueil",
|
||||
@@ -300,7 +300,7 @@ const Lyrics = ({ navigation }) => {
|
||||
}),
|
||||
},
|
||||
{
|
||||
text: "Continuer avec Malik",
|
||||
text: "Continuer vers le Studio",
|
||||
onPress: () => {
|
||||
const targetRoute = beatmakerStage?.route || Routes.Compose;
|
||||
navigate(targetRoute, beatmakerStage?.params);
|
||||
|
||||
@@ -8,7 +8,7 @@ const SongTo = ({ audience, setAudience }) => {
|
||||
<View style={{ flex: 1, gap: 10, marginTop: 16 }}>
|
||||
<CreateLyricsHeader title="À qui s’adresse ta chanson ?" />
|
||||
<CustomInput
|
||||
placeholder="Ecrire mon contexte"
|
||||
placeholder="Écris à qui s'adresse ta chanson"
|
||||
height={283}
|
||||
value={audience}
|
||||
setValue={setAudience}
|
||||
|
||||
@@ -11,7 +11,7 @@ const SpecificityContext = ({ context, setContext }) => {
|
||||
subTitle="Dis-nous en un peu plus pour qu’on puisse mieux t’aider."
|
||||
/>
|
||||
<CustomInput
|
||||
placeholder="Ecrire mon contexte"
|
||||
placeholder="Écris à qui s'adresse ta chanson"
|
||||
height={283}
|
||||
value={context}
|
||||
setValue={setContext}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { BlurView } from "expo-blur";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Image, Modal, Pressable, StyleSheet, Text, View } from "react-native";
|
||||
import { Modal, Pressable, StyleSheet, Text, View } from "react-native";
|
||||
import useMinuit from "react-native-minuit/src/hooks/useMinuit";
|
||||
import { ai } from "../../assets";
|
||||
import BorderGradientButton from "../../components/BorderGradientButton";
|
||||
import FullscreenIntroVideo from "../../components/FullscreenIntroVideo";
|
||||
import GradientButton from "../../components/GradientButton";
|
||||
@@ -277,17 +276,6 @@ export const ChooseCoverType = () => {
|
||||
backgroundColor={"#2A2E33"}
|
||||
headerType="NONE"
|
||||
>
|
||||
<Image
|
||||
source={ai.bena}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "70%",
|
||||
position: "absolute",
|
||||
bottom: -40,
|
||||
right: -30,
|
||||
}}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<MusicLandHeader onPressBack={goBack} progress={9} />
|
||||
<View
|
||||
style={{ flex: 1, position: "relative", justifyContent: "flex-end" }}
|
||||
|
||||
@@ -252,8 +252,8 @@ const PouchReady = () => {
|
||||
const playbackStage = getStageAction("director", projectForStage);
|
||||
await setLoading(false);
|
||||
alert(
|
||||
"Malik",
|
||||
"Super ! Ta pochette est validée. Tu peux retourner à l'accueil ou continuer avec John pour produire ton playback.",
|
||||
"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",
|
||||
@@ -261,7 +261,7 @@ const PouchReady = () => {
|
||||
onPress: () => navigate(Routes.Home),
|
||||
},
|
||||
{
|
||||
text: "Continuer avec John",
|
||||
text: "Continuer avec Theo",
|
||||
onPress: () => {
|
||||
const targetRoute = playbackStage?.route || Routes.Playback;
|
||||
const params = playbackStage?.params || {
|
||||
|
||||
@@ -117,8 +117,8 @@ const ValidateCover = () => {
|
||||
const playbackStage = getStageAction("director", projectForStage);
|
||||
await setIsLoading(false);
|
||||
alert(
|
||||
"Malik",
|
||||
"Super ! Ta pochette est validée. Tu peux retourner à l'accueil ou continuer avec John pour produire ton playback.",
|
||||
"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",
|
||||
@@ -126,7 +126,7 @@ const ValidateCover = () => {
|
||||
onPress: () => navigate(Routes.Home),
|
||||
},
|
||||
{
|
||||
text: "Continuer avec John",
|
||||
text: "Continuer avec Theo",
|
||||
onPress: () => {
|
||||
const targetRoute = playbackStage?.route || Routes.Playback;
|
||||
const params =
|
||||
|
||||
Reference in New Issue
Block a user