feat: fixes and formatter

This commit is contained in:
2026-01-12 16:01:32 +01:00
parent 85c6084351
commit 11e632acff
353 changed files with 23315 additions and 27361 deletions
+22 -29
View File
@@ -1,19 +1,19 @@
import { View, Text } from "react-native";
import React, { useState } from "react";
import Page from "../../layouts/Page";
import { background } from "../../assets";
import MusicLandHeader from "../../components/MusicLandHeader";
import { goBack, navigate } from "../../navigation/NavigationService";
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
import ItemContainer from "../../components/ItemContainer/ItemContainer";
import GradientButton from "../../components/GradientButton";
import { CHOOSE_DECOR } from "../../data/data";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import { Routes } from "../../navigation";
import { View, Text } from 'react-native'
import React, { useState } from 'react'
import Page from '../../layouts/Page'
import { background } from '../../assets'
import MusicLandHeader from '../../components/MusicLandHeader'
import { goBack, navigate } from '../../navigation/NavigationService'
import CreateLyricsHeader from '../Writing/components/CreateLyricsHeader'
import ItemContainer from '../../components/ItemContainer/ItemContainer'
import GradientButton from '../../components/GradientButton'
import { CHOOSE_DECOR } from '../../data/data'
import { Palette } from '../../styles'
import { FONT_FAMILY } from '../../styles/Fonts'
import { Routes } from '../../navigation'
const ChooseDecor = () => {
const [selected, setSelected] = useState("");
const [selected, setSelected] = useState('')
return (
<Page headerType="NONE" backgroundImg={background.playbackBG2}>
@@ -27,7 +27,7 @@ const ChooseDecor = () => {
<ItemContainer height={402}>
<View style={{ gap: 10, marginTop: 5, paddingHorizontal: 5 }}>
{CHOOSE_DECOR.map((item, index) => {
const selectedItem = selected === item;
const selectedItem = selected === item
return (
<CreateLyricsHeader
@@ -39,12 +39,8 @@ const ChooseDecor = () => {
borderRadius: 14,
}}
onPress={() => setSelected(item)}
colors={
selectedItem
? ["#99999900", "#FFFFFF"]
: [Palette.tran, Palette.tran]
}
tint={selectedItem ? "default" : "dark"}
colors={selectedItem ? ['#99999900', '#FFFFFF'] : [Palette.tran, Palette.tran]}
tint={selectedItem ? 'default' : 'dark'}
>
<View style={{ paddingVertical: 6 }}>
<Text
@@ -58,18 +54,15 @@ const ChooseDecor = () => {
</Text>
</View>
</CreateLyricsHeader>
);
)
})}
</View>
</ItemContainer>
</View>
<GradientButton
title="Suivant"
onPress={() => navigate(Routes.CreatingDecor)}
/>
<GradientButton title="Suivant" onPress={() => navigate(Routes.CreatingDecor)} />
</View>
</Page>
);
};
)
}
export default ChooseDecor;
export default ChooseDecor
+40 -40
View File
@@ -1,33 +1,33 @@
import { View, Text, Image, Platform } from "react-native";
import React, { useEffect, useState } from "react";
import Page from "../../layouts/Page";
import { ai, background } from "../../assets";
import MusicLandHeader from "../../components/MusicLandHeader";
import { goBack, navigate } from "../../navigation/NavigationService";
import { Palette, Style } from "../../styles";
import { BlurView } from "expo-blur";
import { FONT_FAMILY } from "../../styles/Fonts";
import ProgressBar from "../../components/ProgressBar";
import { Routes } from "../../navigation";
import GradientButton from "../../components/GradientButton";
import { View, Text, Image, Platform } from 'react-native'
import React, { useEffect, useState } from 'react'
import Page from '../../layouts/Page'
import { ai, background } from '../../assets'
import MusicLandHeader from '../../components/MusicLandHeader'
import { goBack, navigate } from '../../navigation/NavigationService'
import { Palette, Style } from '../../styles'
import { BlurView } from 'expo-blur'
import { FONT_FAMILY } from '../../styles/Fonts'
import ProgressBar from '../../components/ProgressBar'
import { Routes } from '../../navigation'
import GradientButton from '../../components/GradientButton'
const CreatingDecor = () => {
const [progress, setProgress] = useState(0);
const [progress, setProgress] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setProgress((prevProgress) => {
if (prevProgress >= 100) {
clearInterval(interval);
clearInterval(interval)
return 100;
return 100
}
return prevProgress + 1;
});
}, 100);
return prevProgress + 1
})
}, 100)
return () => clearInterval(interval);
}, []);
return () => clearInterval(interval)
}, [])
return (
<Page backgroundImg={background.playbackBG2} headerType="NONE">
@@ -41,23 +41,23 @@ const CreatingDecor = () => {
>
<View
style={{
width: "100%",
height: "50%",
width: '100%',
height: '50%',
}}
>
<View
style={{
zIndex: 1,
position: "absolute",
position: 'absolute',
top: -150,
width: "50%",
height: "80%",
alignSelf: "center",
width: '50%',
height: '80%',
alignSelf: 'center',
}}
>
<Image
source={ai.john}
style={{ width: "100%", height: "100%", right: -10 }}
style={{ width: '100%', height: '100%', right: -10 }}
resizeMode="contain"
/>
</View>
@@ -65,42 +65,42 @@ const CreatingDecor = () => {
style={{
flex: 1,
borderRadius: 20,
overflow: "hidden",
backgroundColor: "#0F0C1933",
overflow: 'hidden',
backgroundColor: '#0F0C1933',
}}
>
<BlurView
intensity={Platform.OS !== "ios" ? 10 : 40}
intensity={Platform.OS !== 'ios' ? 10 : 40}
tint="dark"
style={{ flex: 1, padding: 10, paddingBottom: 20 }}
// experimentalBlurMethod={
// Platform.OS !== "ios" ? "dimezisBlurView" : "none"
// }
>
<View style={{ flex: 1, justifyContent: "flex-end", gap: 20 }}>
<View style={{ flex: 1, justifyContent: 'flex-end', gap: 20 }}>
<View>
<Text
style={{
fontSize: 22,
color: Palette.white,
fontFamily: FONT_FAMILY.InterSemiBold,
textAlign: "center",
textAlign: 'center',
}}
>
Ton décor est en cours de{"\n"}création
Ton décor est en cours de{'\n'}création
</Text>
<Text
style={{
fontSize: 14,
color: Palette.gray,
fontFamily: FONT_FAMILY.InterRegular,
textAlign: "center",
textAlign: 'center',
}}
>
Veuillez patienter
</Text>
</View>
<View style={{ alignItems: "center", gap: 16 }}>
<View style={{ alignItems: 'center', gap: 16 }}>
<ProgressBar gradient progress={progress} />
<Text
style={{
@@ -115,8 +115,8 @@ const CreatingDecor = () => {
<GradientButton
title="Découvrir mon Playback"
containerStyle={{
width: "80%",
alignSelf: "center",
width: '80%',
alignSelf: 'center',
}}
onPress={() => navigate(Routes.VideoFinalize)}
/>
@@ -126,7 +126,7 @@ const CreatingDecor = () => {
</View>
</View>
</Page>
);
};
)
}
export default CreatingDecor;
export default CreatingDecor
+37 -44
View File
@@ -1,51 +1,48 @@
import React, { useCallback, useState } from "react";
import { Image, StyleSheet, View } from "react-native";
import { ai, background } from "../../assets";
import BorderGradientButton from "../../components/BorderGradientButton";
import FullscreenIntroVideo from "../../components/FullscreenIntroVideo";
import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader";
import Page from "../../layouts/Page";
import { isWeb } from "../../hooks/useLayoutType";
import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider";
import { gutters } from "../../styles";
import React, { useCallback, useState } from 'react'
import { Image, StyleSheet, View } from 'react-native'
import { ai, background } from '../../assets'
import BorderGradientButton from '../../components/BorderGradientButton'
import FullscreenIntroVideo from '../../components/FullscreenIntroVideo'
import GradientButton from '../../components/GradientButton'
import MusicLandHeader from '../../components/MusicLandHeader'
import Page from '../../layouts/Page'
import { isWeb } from '../../hooks/useLayoutType'
import { Routes } from '../../navigation'
import { navigate } from '../../navigation/NavigationService'
import { useUser } from '../../providers/UserDataProvider'
import { gutters } from '../../styles'
const Playback = ({ route, navigation }) => {
const { project } = route.params || {};
const { videos } = useUser();
const { project } = route.params || {}
const { videos } = useUser()
// const benhaiUrl = isWeb ? videos?.benhaiWeb || null : videos?.benhai;
const theoUrl = isWeb ? videos?.theoWeb : videos?.theo;
const [showIntro, setShowIntro] = useState(theoUrl);
const theoUrl = isWeb ? videos?.theoWeb : videos?.theo
const [showIntro, setShowIntro] = useState(theoUrl)
const handleCloseIntro = () => {
setShowIntro(null);
};
setShowIntro(null)
}
const onPressRecord = useCallback(() => {
navigate(Routes.RecordPlayback, { project });
}, [project]);
navigate(Routes.RecordPlayback, { project })
}, [project])
return (
<Page headerType="NONE" backgroundImg={isWeb ? background.playbackWeb : background.playbackMobile}>
<Page
headerType="NONE"
backgroundImg={isWeb ? background.playbackWeb : background.playbackMobile}
>
{/* <Image source={ai.theo} style={styles.img} resizeMode="contain" /> */}
<MusicLandHeader
onPressBack={() => navigation.navigate(Routes.Home)}
progress={25}
/>
<MusicLandHeader onPressBack={() => navigation.navigate(Routes.Home)} progress={25} />
<View
style={{
flex: 1,
paddingBottom: gutters * 2,
justifyContent: "flex-end",
justifyContent: 'flex-end',
}}
>
<View style={{ width: "80%", alignSelf: "center", gap: 12 }}>
<GradientButton
title="Enregistrer mon Playback"
onPress={onPressRecord}
/>
<View style={{ width: '80%', alignSelf: 'center', gap: 12 }}>
<GradientButton title="Enregistrer mon Playback" onPress={onPressRecord} />
<BorderGradientButton
title="Guide du Playbacker"
onPress={() => navigate(Routes.PlaybackGuide)}
@@ -53,22 +50,18 @@ const Playback = ({ route, navigation }) => {
{/* <BorderGradientButton title="Importer une vidéo" /> */}
</View>
</View>
<FullscreenIntroVideo
url={showIntro}
visible={showIntro}
onClose={handleCloseIntro}
/>
<FullscreenIntroVideo url={showIntro} visible={showIntro} onClose={handleCloseIntro} />
</Page>
);
};
)
}
export default Playback;
export default Playback
const styles = StyleSheet.create({
img: {
width: "100%",
height: "70%",
position: "absolute",
width: '100%',
height: '70%',
position: 'absolute',
bottom: -40,
},
});
})
+330 -312
View File
@@ -1,341 +1,359 @@
import React from "react";
import { ScrollView, StyleSheet, Text, View, Platform } from "react-native";
import { BlurView } from "expo-blur";
import { background } from "../../assets";
import MusicLandHeader from "../../components/MusicLandHeader";
import Page from "../../layouts/Page";
import { Routes } from "../../navigation";
import { gutters, Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import { isWeb } from "../../hooks/useLayoutType";
import React from 'react'
import { ScrollView, StyleSheet, Text, View, Platform } from 'react-native'
import { BlurView } from 'expo-blur'
import { background } from '../../assets'
import MusicLandHeader from '../../components/MusicLandHeader'
import Page from '../../layouts/Page'
import { Routes } from '../../navigation'
import { gutters, Palette } from '../../styles'
import { FONT_FAMILY } from '../../styles/Fonts'
import { isWeb } from '../../hooks/useLayoutType'
const PlaybackGuide = ({ navigation }) => {
return (
<Page headerType="NONE" backgroundImg={isWeb ? background.playbackWeb : background.playbackMobile}>
<MusicLandHeader onPressBack={() => navigation.goBack()} />
<View style={styles.container}>
<BlurView intensity={Platform.OS === "ios" ? 80 : 40} style={styles.blurContainer} experimentalBlurMethod="dimezisBlurView">
<ScrollView
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
>
<Text style={styles.title}>Le Guide Ultime du Playback de TA Chanson : L'Expérience Créative Totale</Text>
return (
<Page
headerType="NONE"
backgroundImg={isWeb ? background.playbackWeb : background.playbackMobile}
>
<MusicLandHeader onPressBack={() => navigation.goBack()} />
<View style={styles.container}>
<BlurView
intensity={Platform.OS === 'ios' ? 80 : 40}
style={styles.blurContainer}
experimentalBlurMethod="dimezisBlurView"
>
<ScrollView
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
>
<Text style={styles.title}>
Le Guide Ultime du Playback de TA Chanson : L'Expérience Créative Totale
</Text>
<Text style={styles.sectionTitle}>Introduction : Tu Es l'Artiste ET le Performeur !</Text>
<Text style={styles.text}>
Imagine ça : tu as créé Ta chanson, TES paroles, TA mélodie, TON univers.
Et maintenant, tu vas lui donner vie en playback. C'est une expérience UNIQUE au monde !
Tu n'imites personne - tu deviens la version scénique de toi-même. C'est comme avoir un
jumeau qui chante pendant que toi, tu performes. Double dose de créativité, double dose de
fun !
</Text>
<Text style={styles.sectionTitle}>
Introduction : Tu Es l'Artiste ET le Performeur !
</Text>
<Text style={styles.text}>
Imagine ça : tu as créé Ta chanson, TES paroles, TA mélodie, TON univers. Et
maintenant, tu vas lui donner vie en playback. C'est une expérience UNIQUE au monde !
Tu n'imites personne - tu deviens la version scénique de toi-même. C'est comme avoir
un jumeau qui chante pendant que toi, tu performes. Double dose de créativité, double
dose de fun !
</Text>
<Text style={styles.chapterTitle}>Chapitre 1 : Le Lip-Sync de Ta Propre Création - L'Avantage Ultime</Text>
<Text style={styles.chapterTitle}>
Chapitre 1 : Le Lip-Sync de Ta Propre Création - L'Avantage Ultime
</Text>
<Text style={styles.subTitle}>Le Pouvoir d'Être le Créateur</Text>
<Text style={styles.text}>
Tu as un super-pouvoir que personne d'autre n'a : tu connais CHAQUE intention derrière
chaque mot, chaque respiration, chaque silence. Tu n'as pas à deviner ce que l'artiste
voulait dire - c'est TOI l'artiste ! Cette connexion intime avec ta chanson va transparaître
dans chaque mouvement de tes lèvres.
</Text>
<Text style={styles.subTitle}>Le Pouvoir d'Être le Créateur</Text>
<Text style={styles.text}>
Tu as un super-pouvoir que personne d'autre n'a : tu connais CHAQUE intention derrière
chaque mot, chaque respiration, chaque silence. Tu n'as pas à deviner ce que l'artiste
voulait dire - c'est TOI l'artiste ! Cette connexion intime avec ta chanson va
transparaître dans chaque mouvement de tes lèvres.
</Text>
<Text style={styles.subTitle}>La Règle des 15 Écoutes - Version Créateur</Text>
<Text style={styles.text}>
Oui, même pour TA chanson, les 15 écoutes sont cruciales ! Mais avec une twist magique :
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Écoutes 1-5 : Redécouvre Ta Création</Text> Écoute ta chanson comme si c'était la première
fois. Tu vas être surpris de détails que tu avais oubliés !
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Écoutes 6-10 : L'Analyse du Créateur</Text>
{"\n"}• Où as-tu placé les émotions fortes ?
{"\n"}• Quels mots méritent d'être sur-articulés ?
{"\n"}• Où sont tes respirations naturelles dans l'enregistrement ?
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Écoutes 11-15 : La Transformation</Text> Maintenant, détache-toi du "toi qui a enregistré" pour
devenir le "toi qui performe". C'est là que la magie opère !
</Text>
<Text style={styles.subTitle}>La Règle des 15 Écoutes - Version Créateur</Text>
<Text style={styles.text}>
Oui, même pour TA chanson, les 15 écoutes sont cruciales ! Mais avec une twist magique
:
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Écoutes 1-5 : Redécouvre Ta Création</Text> Écoute ta
chanson comme si c'était la première fois. Tu vas être surpris de détails que tu avais
oubliés !
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Écoutes 6-10 : L'Analyse du Créateur</Text>
{'\n'}• Où as-tu placé les émotions fortes ?{'\n'}• Quels mots méritent d'être
sur-articulés ?{'\n'}• Où sont tes respirations naturelles dans l'enregistrement ?
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Écoutes 11-15 : La Transformation</Text> Maintenant,
détache-toi du "toi qui a enregistré" pour devenir le "toi qui performe". C'est là que
la magie opère !
</Text>
<Text style={styles.subTitle}>Les Secrets du Créateur-Performeur</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Le Phénomène de Dédoublement</Text> C'est normal de se sentir bizarre au début - tu entends
ta voix mais tu ne chantes pas ! Embrasse cette sensation unique. C'est comme être ton
propre fan numéro un.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>La Liberté de Réinterpréter</Text> Tu as le droit de changer l'émotion ! Si tu as enregistré la
chanson en étant mélancolique mais qu'aujourd'hui tu la sens joyeuse - VAS-Y ! C'est TA
chanson, tu peux la réinventer à chaque performance.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>L'Anticipation Parfaite</Text> Tu connais chaque micro-pause, chaque inflexion. Utilise cet
avantage ! Anticipe les moments forts avec ton expression faciale une fraction de seconde
avant qu'ils arrivent.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Le Truc du "Nouveau Moi"</Text> Crée un personnage scénique de toi-même. Peut-être que le
"toi studio" est introverti, mais le "toi playback" est extraverti. Cette dualité est fascinante et
totalement unique à ton expérience !
</Text>
<Text style={styles.subTitle}>Les Secrets du Créateur-Performeur</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Le Phénomène de Dédoublement</Text> C'est normal de se
sentir bizarre au début - tu entends ta voix mais tu ne chantes pas ! Embrasse cette
sensation unique. C'est comme être ton propre fan numéro un.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>La Liberté de Réinterpréter</Text> Tu as le droit de changer
l'émotion ! Si tu as enregistré la chanson en étant mélancolique mais qu'aujourd'hui
tu la sens joyeuse - VAS-Y ! C'est TA chanson, tu peux la réinventer à chaque
performance.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>L'Anticipation Parfaite</Text> Tu connais chaque
micro-pause, chaque inflexion. Utilise cet avantage ! Anticipe les moments forts avec
ton expression faciale une fraction de seconde avant qu'ils arrivent.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Le Truc du "Nouveau Moi"</Text> Crée un personnage scénique
de toi-même. Peut-être que le "toi studio" est introverti, mais le "toi playback" est
extraverti. Cette dualité est fascinante et totalement unique à ton expérience !
</Text>
<Text style={styles.chapterTitle}>Chapitre 2 : Capturer l'Essence de Ta Création</Text>
<Text style={styles.chapterTitle}>Chapitre 2 : Capturer l'Essence de Ta Création</Text>
<Text style={styles.subTitle}>Ton Smartphone, Ton Studio Visuel</Text>
<Text style={styles.text}>
Quand tu filmes le playback de TA chanson, tu complètes l'œuvre. La musique était l'audio,
maintenant tu ajoutes le visuel !
</Text>
<Text style={styles.subTitle}>Ton Smartphone, Ton Studio Visuel</Text>
<Text style={styles.text}>
Quand tu filmes le playback de TA chanson, tu complètes l'œuvre. La musique était
l'audio, maintenant tu ajoutes le visuel !
</Text>
<Text style={styles.subTitle}>Les Angles Qui Racontent Ton Histoire</Text>
<Text style={styles.text}>
<Text style={styles.bold}>L'Angle "Confession" (Très Proche)</Text> Pour les parties intimes de ta chanson. Le spectateur
doit sentir que tu lui confies un secret.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>L'Angle "Concert" (Plan Large)</Text> Montre l'artiste complet en toi ! Parfait pour les refrains
énergiques où tu veux bouger.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>L'Angle "Clip Musical" (Variété)</Text> Change d'angle selon les parties de ta chanson. Verse 1
proche, refrain large, pont en profil... Raconte visuellement l'histoire de ta musique.
</Text>
<Text style={styles.subTitle}>Les Angles Qui Racontent Ton Histoire</Text>
<Text style={styles.text}>
<Text style={styles.bold}>L'Angle "Confession" (Très Proche)</Text> Pour les parties
intimes de ta chanson. Le spectateur doit sentir que tu lui confies un secret.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>L'Angle "Concert" (Plan Large)</Text> Montre l'artiste
complet en toi ! Parfait pour les refrains énergiques où tu veux bouger.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>L'Angle "Clip Musical" (Variété)</Text> Change d'angle selon
les parties de ta chanson. Verse 1 proche, refrain large, pont en profil... Raconte
visuellement l'histoire de ta musique.
</Text>
<Text style={styles.subTitle}>L'Accessoire Signature : L'Instrument de Ta Création</Text>
<Text style={styles.text}>
C'est encore plus puissant quand c'est TA chanson !
</Text>
<Text style={styles.text}>
Si tu as composé avec un instrument :
{"\n"}• Mime-le même si tu ne l'as pas sous la main
{"\n"}• Montre les accords que TU as choisis
{"\n"}• Tes mains connaissent les mouvements par cœur
</Text>
<Text style={styles.text}>
Si c'est une création digitale :
{"\n"}• Un casque audio autour du cou = le créateur
{"\n"}• Un smartphone en main = le compositeur moderne
{"\n"}• Les mains qui "mixent" dans l'air = le producteur
</Text>
<Text style={styles.text}>
L'accessoire émotionnel :
{"\n"}• Un carnet (tes paroles originales !)
{"\n"}• Un objet qui t'a inspiré la chanson
{"\n"}• Un élément qui représente le thème de ta chanson
</Text>
<Text style={styles.subTitle}>
L'Accessoire Signature : L'Instrument de Ta Création
</Text>
<Text style={styles.text}>C'est encore plus puissant quand c'est TA chanson !</Text>
<Text style={styles.text}>
Si tu as composé avec un instrument :{'\n'}• Mime-le même si tu ne l'as pas sous la
main
{'\n'}• Montre les accords que TU as choisis
{'\n'}• Tes mains connaissent les mouvements par cœur
</Text>
<Text style={styles.text}>
Si c'est une création digitale :{'\n'}• Un casque audio autour du cou = le créateur
{'\n'}• Un smartphone en main = le compositeur moderne
{'\n'}• Les mains qui "mixent" dans l'air = le producteur
</Text>
<Text style={styles.text}>
L'accessoire émotionnel :{'\n'}• Un carnet (tes paroles originales !)
{'\n'}• Un objet qui t'a inspiré la chanson
{'\n'}• Un élément qui représente le thème de ta chanson
</Text>
<Text style={styles.chapterTitle}>Chapitre 3 : La Scène de Ta Première</Text>
<Text style={styles.chapterTitle}>Chapitre 3 : La Scène de Ta Première</Text>
<Text style={styles.subTitle}>Chaque Lieu Devient TON Lieu</Text>
<Text style={styles.text}>
Quand tu performes ta propre chanson, chaque endroit prend une signification spéciale. Ce
n'est pas juste un décor, c'est le lieu de naissance visuelle de ta création !
</Text>
<Text style={styles.subTitle}>Chaque Lieu Devient TON Lieu</Text>
<Text style={styles.text}>
Quand tu performes ta propre chanson, chaque endroit prend une signification spéciale.
Ce n'est pas juste un décor, c'est le lieu de naissance visuelle de ta création !
</Text>
<Text style={styles.subTitle}>Les Scènes Qui Amplifient Ta Création</Text>
<Text style={styles.text}>
<Text style={styles.bold}>L'Endroit Où Tu l'as Écrite - Le Retour aux Sources</Text> Si tu as écrit ta chanson dans ta
chambre, filme-là ! C'est puissant de montrer l'artiste là où la magie a commencé.
L'authenticité sera palpable.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Le Studio Maison - Là Où Tu l'as Enregistrée</Text> Montre ton setup, même basique ! Un
micro, un ordinateur, des câbles... C'est TON univers créatif. Les gens adorent voir les
coulisses.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Le Lieu Qui t'a Inspiré - La Boucle Créative</Text> Tu as écrit sur la plage ? Sur l'amour au café
? Sur la solitude au parc ? Retourne-y ! La cohérence entre ton inspiration et ta performance
est magique.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>La Scène Imaginaire - Ce Que Tu Visualisais</Text> Quand tu as créé cette chanson, où
t'imaginais-tu la performer ? Un grand stade ? Un petit club intime ? Recrée cette ambiance
avec ce que tu as !
</Text>
<Text style={styles.subTitle}>Les Scènes Qui Amplifient Ta Création</Text>
<Text style={styles.text}>
<Text style={styles.bold}>L'Endroit Où Tu l'as Écrite - Le Retour aux Sources</Text>{' '}
Si tu as écrit ta chanson dans ta chambre, filme-là ! C'est puissant de montrer
l'artiste là où la magie a commencé. L'authenticité sera palpable.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Le Studio Maison - Là Où Tu l'as Enregistrée</Text> Montre
ton setup, même basique ! Un micro, un ordinateur, des câbles... C'est TON univers
créatif. Les gens adorent voir les coulisses.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Le Lieu Qui t'a Inspiré - La Boucle Créative</Text> Tu as
écrit sur la plage ? Sur l'amour au café ? Sur la solitude au parc ? Retourne-y ! La
cohérence entre ton inspiration et ta performance est magique.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>La Scène Imaginaire - Ce Que Tu Visualisais</Text> Quand tu
as créé cette chanson, où t'imaginais-tu la performer ? Un grand stade ? Un petit club
intime ? Recrée cette ambiance avec ce que tu as !
</Text>
<Text style={styles.subTitle}>Suggestions Créatives Uniques :</Text>
<Text style={styles.text}>
• La Voiture : Si c'est là que tu as eu l'idée de la mélodie
{"\n"}• La Douche : Avec le téléphone protégé, si c'est là que tu chantes toujours
{"\n"}• Le Toit/Balcon : Pour une sensation de "crier au monde" ta création
{"\n"}• Devant le Miroir : Le dialogue entre toi créateur et toi performeur
{"\n"}• Dans le Noir avec une Seule Lumière : Pour l'aspect dramatique et intime
</Text>
<Text style={styles.subTitle}>Suggestions Créatives Uniques :</Text>
<Text style={styles.text}>
• La Voiture : Si c'est là que tu as eu l'idée de la mélodie
{'\n'}• La Douche : Avec le téléphone protégé, si c'est là que tu chantes toujours
{'\n'}• Le Toit/Balcon : Pour une sensation de "crier au monde" ta création
{'\n'}• Devant le Miroir : Le dialogue entre toi créateur et toi performeur
{'\n'}• Dans le Noir avec une Seule Lumière : Pour l'aspect dramatique et intime
</Text>
<Text style={styles.chapterTitle}>Chapitre 4 : Le Mindset du Créateur-Performeur</Text>
<Text style={styles.chapterTitle}>Chapitre 4 : Le Mindset du Créateur-Performeur</Text>
<Text style={styles.subTitle}>L'Expérience Unique au Monde</Text>
<Text style={styles.text}>
Ce que tu fais est EXTRAORDINAIRE. Tu es à la fois Mozart et le pianiste, Beyoncé et la
performeuse, le compositeur ET l'interprète. Cette dualité créative est rare et précieuse !
</Text>
<Text style={styles.subTitle}>L'Expérience Unique au Monde</Text>
<Text style={styles.text}>
Ce que tu fais est EXTRAORDINAIRE. Tu es à la fois Mozart et le pianiste, Beyoncé et
la performeuse, le compositeur ET l'interprète. Cette dualité créative est rare et
précieuse !
</Text>
<Text style={styles.subTitle}>Les Mantras du Créateur en Playback</Text>
<Text style={styles.text}>
<Text style={styles.bold}>"C'est MON Bébé Musical"</Text> Cette chanson est née de toi. Chaque note, chaque mot est ton
enfant créatif. Quand tu la performes en playback, tu lui donnes une nouvelle dimension.
Sois fier !
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>"Je Me Réinvente à Chaque Prise"</Text> L'avantage de faire le playback de TA chanson ? Tu
peux explorer 1000 façons de l'interpréter ! Mélancolique le matin, énergique le soir. C'est ta
playground créative.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>"Mes Deux Moi Collaborent"</Text> Le toi qui a chanté et le toi qui performe sont une équipe.
C'est une collaboration unique avec toi-même à travers le temps !
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>"C'est Mon Histoire, Mes Règles"</Text> Personne ne peut te dire "ce n'est pas comme ça que
l'artiste le fait" - parce que l'artiste, c'est TOI !
</Text>
<Text style={styles.subTitle}>Les Mantras du Créateur en Playback</Text>
<Text style={styles.text}>
<Text style={styles.bold}>"C'est MON Bébé Musical"</Text> Cette chanson est née de
toi. Chaque note, chaque mot est ton enfant créatif. Quand tu la performes en
playback, tu lui donnes une nouvelle dimension. Sois fier !
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>"Je Me Réinvente à Chaque Prise"</Text> L'avantage de faire
le playback de TA chanson ? Tu peux explorer 1000 façons de l'interpréter !
Mélancolique le matin, énergique le soir. C'est ta playground créative.
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>"Mes Deux Moi Collaborent"</Text> Le toi qui a chanté et le
toi qui performe sont une équipe. C'est une collaboration unique avec toi-même à
travers le temps !
</Text>
<Text style={styles.text}>
<Text style={styles.bold}>"C'est Mon Histoire, Mes Règles"</Text> Personne ne peut te
dire "ce n'est pas comme ça que l'artiste le fait" - parce que l'artiste, c'est TOI !
</Text>
<Text style={styles.subTitle}>La Célébration de Ta Création</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Le Rituel du Créateur :</Text>
{"\n"}1. Écoute ta chanson une fois les yeux fermés - Reconnecte avec ton intention originale
{"\n"}2. Dis à voix haute : "J'ai créé quelque chose qui n'existait pas avant"
{"\n"}3. Danse 30 secondes sur TA création - Célèbre-toi !
{"\n"}4. Filme avec fierté - Ce moment est historique dans ton parcours
</Text>
<Text style={styles.subTitle}>La Célébration de Ta Création</Text>
<Text style={styles.text}>
<Text style={styles.bold}>Le Rituel du Créateur :</Text>
{'\n'}1. Écoute ta chanson une fois les yeux fermés - Reconnecte avec ton intention
originale
{'\n'}2. Dis à voix haute : "J'ai créé quelque chose qui n'existait pas avant"
{'\n'}3. Danse 30 secondes sur TA création - Célèbre-toi !{'\n'}4. Filme avec fierté -
Ce moment est historique dans ton parcours
</Text>
<Text style={styles.subTitle}>Le Partage de Ton Univers</Text>
<Text style={styles.text}>
Quand tu partages le playback de ta propre chanson, tu offres une expérience complète :
{"\n"}• Les gens découvrent ta musique
{"\n"}• Ils voient ta personnalité
{"\n"}• Ils connectent avec l'artiste complet que tu es
{"\n"}• Ils witness la naissance visuelle de ton art
</Text>
<Text style={styles.subTitle}>Le Partage de Ton Univers</Text>
<Text style={styles.text}>
Quand tu partages le playback de ta propre chanson, tu offres une expérience complète
:{'\n'}• Les gens découvrent ta musique
{'\n'}• Ils voient ta personnalité
{'\n'}• Ils connectent avec l'artiste complet que tu es
{'\n'}• Ils witness la naissance visuelle de ton art
</Text>
<Text style={styles.chapterTitle}>Résumé : Ton Parcours Créatif Unique</Text>
<Text style={styles.chapterTitle}>Résumé : Ton Parcours Créatif Unique</Text>
<Text style={styles.subTitle}>🎵 Chapitre 1 - Ton Lip-Sync Signature</Text>
<Text style={styles.text}>
• Tu connais chaque intention : Avantage ultime du créateur
{"\n"}• 15 écoutes pour redécouvrir : Même ta création mérite cette attention
{"\n"}• Le dédoublement créatif : Embrasse d'être ton propre interprète
{"\n"}• Liberté totale : Réinterprète selon ton humeur du jour
</Text>
<Text style={styles.subTitle}>🎵 Chapitre 1 - Ton Lip-Sync Signature</Text>
<Text style={styles.text}>
• Tu connais chaque intention : Avantage ultime du créateur
{'\n'}• 15 écoutes pour redécouvrir : Même ta création mérite cette attention
{'\n'}• Le dédoublement créatif : Embrasse d'être ton propre interprète
{'\n'}• Liberté totale : Réinterprète selon ton humeur du jour
</Text>
<Text style={styles.subTitle}>🎬 Chapitre 2 - Ton Setup Créatif</Text>
<Text style={styles.text}>
• Les angles qui racontent : Confession, concert, clip
{"\n"}• L'instrument de création : Montre ton processus créatif
{"\n"}• Accessoires significatifs : Objets liés à ton inspiration
{"\n"}• L'authenticité avant tout : Ton setup, tes règles
</Text>
<Text style={styles.subTitle}>🎬 Chapitre 2 - Ton Setup Créatif</Text>
<Text style={styles.text}>
• Les angles qui racontent : Confession, concert, clip
{'\n'}• L'instrument de création : Montre ton processus créatif
{'\n'}• Accessoires significatifs : Objets liés à ton inspiration
{'\n'}• L'authenticité avant tout : Ton setup, tes règles
</Text>
<Text style={styles.subTitle}>🎭 Chapitre 3 - Tes Lieux de Création</Text>
<Text style={styles.text}>
• Retour aux sources : Là où tu as créé
{"\n"}• Le lieu d'inspiration : Boucle ta vision créative
{"\n"}• La scène rêvée : Matérialise ta vision
{"\n"}• Chaque lieu = ton histoire : Personnalise à fond
</Text>
<Text style={styles.subTitle}>🎭 Chapitre 3 - Tes Lieux de Création</Text>
<Text style={styles.text}>
• Retour aux sources : Là où tu as créé
{'\n'}• Le lieu d'inspiration : Boucle ta vision créative
{'\n'}• La scène rêvée : Matérialise ta vision
{'\n'}• Chaque lieu = ton histoire : Personnalise à fond
</Text>
<Text style={styles.subTitle}>🌟 Chapitre 4 - Ton Mindset Unique</Text>
<Text style={styles.text}>
• Double créativité : Compositeur ET performeur
{"\n"}• Fierté créative : Tu as créé quelque chose de rien
{"\n"}• Réinvention constante : Explore toutes les facettes
{"\n"}• Histoire complète : Tu offres l'expérience totale
</Text>
<Text style={styles.subTitle}>🌟 Chapitre 4 - Ton Mindset Unique</Text>
<Text style={styles.text}>
• Double créativité : Compositeur ET performeur
{'\n'}• Fierté créative : Tu as créé quelque chose de rien
{'\n'}• Réinvention constante : Explore toutes les facettes
{'\n'}• Histoire complète : Tu offres l'expérience totale
</Text>
<Text style={styles.chapterTitle}>Ton Défi Ultime : La Première Mondiale !</Text>
<Text style={styles.text}>
Tu es sur le point de faire quelque chose qu'AUCUN autre artiste ne peut faire : créer la
performance visuelle parfaite de TA chanson. C'est ta première mondiale, ton moment
unique !
</Text>
<Text style={styles.text}>
Dans les 24 prochaines heures, donne vie visuellement à ta création. Choisis ta chanson
préférée (parmi les tiennes !), trouve ton spot créatif, et MONTRE au monde non seulement
ta musique, mais TOI en train de la vivre.
</Text>
<Text style={styles.text}>
Rappelle-toi : Tu n'es pas en train d'imiter. Tu n'es pas en train de copier. Tu es en train de
COMPLÉTER ton œuvre. La chanson était le tableau, le playback est le cadre qui le
sublime.
</Text>
<Text style={styles.text}>
C'est ton moment. Ta chanson. Ta performance. Ton histoire.
</Text>
<Text style={styles.text}>
Le monde attend de découvrir l'artiste complet que tu es ! 🎤🎵✨
</Text>
<Text style={styles.text}>
#MonPlaybackMaChanson #CréateurPerformeur #DoubleArtiste
</Text>
<Text style={styles.text}>
P.S. : Garde chaque prise, même les "ratées". Dans 10 ans, tu regarderas ces vidéos et tu
verras le début de ton parcours d'artiste. C'est de l'or pur !
</Text>
</ScrollView>
</BlurView>
</View>
</Page>
);
};
<Text style={styles.chapterTitle}>Ton Défi Ultime : La Première Mondiale !</Text>
<Text style={styles.text}>
Tu es sur le point de faire quelque chose qu'AUCUN autre artiste ne peut faire : créer
la performance visuelle parfaite de TA chanson. C'est ta première mondiale, ton moment
unique !
</Text>
<Text style={styles.text}>
Dans les 24 prochaines heures, donne vie visuellement à ta création. Choisis ta
chanson préférée (parmi les tiennes !), trouve ton spot créatif, et MONTRE au monde
non seulement ta musique, mais TOI en train de la vivre.
</Text>
<Text style={styles.text}>
Rappelle-toi : Tu n'es pas en train d'imiter. Tu n'es pas en train de copier. Tu es en
train de COMPLÉTER ton œuvre. La chanson était le tableau, le playback est le cadre
qui le sublime.
</Text>
<Text style={styles.text}>
C'est ton moment. Ta chanson. Ta performance. Ton histoire.
</Text>
<Text style={styles.text}>
Le monde attend de découvrir l'artiste complet que tu es ! 🎤🎵✨
</Text>
<Text style={styles.text}>
#MonPlaybackMaChanson #CréateurPerformeur #DoubleArtiste
</Text>
<Text style={styles.text}>
P.S. : Garde chaque prise, même les "ratées". Dans 10 ans, tu regarderas ces vidéos et
tu verras le début de ton parcours d'artiste. C'est de l'or pur !
</Text>
</ScrollView>
</BlurView>
</View>
</Page>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginHorizontal: Platform.OS === "web" ? gutters : 0,
marginBottom: gutters,
borderRadius: 20,
overflow: "hidden",
},
blurContainer: {
flex: 1,
backgroundColor: Palette.glass,
},
scrollContent: {
padding: gutters,
paddingBottom: gutters * 3,
},
title: {
fontFamily: FONT_FAMILY.InterBold,
fontSize: 24,
color: Palette.white,
textAlign: "center",
marginBottom: 20,
},
sectionTitle: {
fontFamily: FONT_FAMILY.InterBold,
fontSize: 20,
color: Palette.primary,
marginBottom: 10,
marginTop: 10,
},
chapterTitle: {
fontFamily: FONT_FAMILY.InterBold,
fontSize: 22,
color: Palette.primary,
marginBottom: 15,
marginTop: 25,
textAlign: "center",
},
subTitle: {
fontFamily: FONT_FAMILY.InterSemiBold,
fontSize: 18,
color: Palette.white,
marginBottom: 8,
marginTop: 15,
},
text: {
fontFamily: FONT_FAMILY.InterRegular,
fontSize: 16,
color: Palette.white,
marginBottom: 10,
lineHeight: 24,
},
bold: {
fontFamily: FONT_FAMILY.InterBold,
},
});
container: {
flex: 1,
marginHorizontal: Platform.OS === 'web' ? gutters : 0,
marginBottom: gutters,
borderRadius: 20,
overflow: 'hidden',
},
blurContainer: {
flex: 1,
backgroundColor: Palette.glass,
},
scrollContent: {
padding: gutters,
paddingBottom: gutters * 3,
},
title: {
fontFamily: FONT_FAMILY.InterBold,
fontSize: 24,
color: Palette.white,
textAlign: 'center',
marginBottom: 20,
},
sectionTitle: {
fontFamily: FONT_FAMILY.InterBold,
fontSize: 20,
color: Palette.primary,
marginBottom: 10,
marginTop: 10,
},
chapterTitle: {
fontFamily: FONT_FAMILY.InterBold,
fontSize: 22,
color: Palette.primary,
marginBottom: 15,
marginTop: 25,
textAlign: 'center',
},
subTitle: {
fontFamily: FONT_FAMILY.InterSemiBold,
fontSize: 18,
color: Palette.white,
marginBottom: 8,
marginTop: 15,
},
text: {
fontFamily: FONT_FAMILY.InterRegular,
fontSize: 16,
color: Palette.white,
marginBottom: 10,
lineHeight: 24,
},
bold: {
fontFamily: FONT_FAMILY.InterBold,
},
})
export default PlaybackGuide;
export default PlaybackGuide
+13 -16
View File
@@ -1,11 +1,11 @@
import { View, Text } from "react-native";
import React from "react";
import Page from "../../layouts/Page";
import { background } from "../../assets";
import { gutters } from "../../styles";
import GradientButton from "../../components/GradientButton";
import { navigate } from "../../navigation/NavigationService";
import { Routes } from "../../navigation";
import { View, Text } from 'react-native'
import React from 'react'
import Page from '../../layouts/Page'
import { background } from '../../assets'
import { gutters } from '../../styles'
import GradientButton from '../../components/GradientButton'
import { navigate } from '../../navigation/NavigationService'
import { Routes } from '../../navigation'
const PlaybackOnboarding = () => {
return (
@@ -14,16 +14,13 @@ const PlaybackOnboarding = () => {
style={{
flex: 1,
paddingBottom: gutters * 2,
justifyContent: "flex-end",
justifyContent: 'flex-end',
}}
>
<GradientButton
title="Commence"
onPress={() => navigate(Routes.Playback)}
/>
<GradientButton title="Commence" onPress={() => navigate(Routes.Playback)} />
</View>
</Page>
);
};
)
}
export default PlaybackOnboarding;
export default PlaybackOnboarding
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+118 -126
View File
@@ -1,173 +1,167 @@
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
import * as FileSystem from "expo-file-system";
import { VideoView, useVideoPlayer } from "expo-video";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Image, Pressable, View } from "react-native";
import { background, icons } from "../../assets";
import BorderGradientButton from "../../components/BorderGradientButton";
import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader";
import ProgressSlider from "../../components/player/ProgressSlider";
import Page from "../../layouts/Page";
import { Routes } from "../../navigation";
import { goBack, navigate } from "../../navigation/NavigationService";
import { gutters, Palette } from "../../styles";
import useSharedAudioPlayer from '../../hooks/useSharedAudioPlayer'
import * as FileSystem from 'expo-file-system'
import { VideoView, useVideoPlayer } from 'expo-video'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Image, Pressable, View } from 'react-native'
import { background, icons } from '../../assets'
import BorderGradientButton from '../../components/BorderGradientButton'
import GradientButton from '../../components/GradientButton'
import MusicLandHeader from '../../components/MusicLandHeader'
import ProgressSlider from '../../components/player/ProgressSlider'
import Page from '../../layouts/Page'
import { Routes } from '../../navigation'
import { goBack, navigate } from '../../navigation/NavigationService'
import { gutters, Palette } from '../../styles'
const RecordedPlayback = ({ route }) => {
const { videoUri, project } = route.params || {};
const songUrl = project?.songUrl || null;
const { videoUri, project } = route.params || {}
const songUrl = project?.songUrl || null
const audioPlayer = useSharedAudioPlayer(songUrl ? { uri: songUrl } : undefined, {
id: project?.id ? `recorded-${project.id}` : songUrl ? `recorded-${songUrl}` : undefined,
title: typeof project?.title === "string" ? project.title : "Sans titre",
artist: typeof project?.userName === "string" ? project.userName : "MusicLand",
title: typeof project?.title === 'string' ? project.title : 'Sans titre',
artist: typeof project?.userName === 'string' ? project.userName : 'MusicLand',
artwork: project?.coverUrl || null,
coverUrl: project?.coverUrl || null,
metadata: { projectId: project?.id, screen: "RecordedPlayback" },
});
metadata: { projectId: project?.id, screen: 'RecordedPlayback' },
})
const videoPlayer = useVideoPlayer(videoUri || null, (p) => {
p.loop = false;
p.muted = true; // recorded video has no audio; keep muted anyway
p.timeUpdateEventInterval = 0.2;
});
p.loop = false
p.muted = true // recorded video has no audio; keep muted anyway
p.timeUpdateEventInterval = 0.2
})
const [progressInfo, setProgressInfo] = useState({
pos: 0,
dur: 0,
isPlaying: false,
});
const playbackEndedRef = useRef(false);
})
const playbackEndedRef = useRef(false)
// Start both players on mount
const stopPlayback = useCallback(async () => {
try {
if (audioPlayer?.playing) await audioPlayer.pause?.();
} catch (e) { }
if (audioPlayer?.playing) await audioPlayer.pause?.()
} catch (e) {}
try {
if (videoPlayer?.playing) videoPlayer.pause();
} catch (e) { }
}, [audioPlayer, videoPlayer]);
if (videoPlayer?.playing) videoPlayer.pause()
} catch (e) {}
}, [audioPlayer, videoPlayer])
useEffect(() => {
playbackEndedRef.current = false;
playbackEndedRef.current = false
const start = async () => {
try {
if (audioPlayer && songUrl) await audioPlayer.play?.();
if (videoPlayer) videoPlayer.play();
} catch (e) { }
};
start();
if (audioPlayer && songUrl) await audioPlayer.play?.()
if (videoPlayer) videoPlayer.play()
} catch (e) {}
}
start()
return () => {
playbackEndedRef.current = false;
void stopPlayback();
};
}, [audioPlayer, songUrl, stopPlayback, videoPlayer]);
playbackEndedRef.current = false
void stopPlayback()
}
}, [audioPlayer, songUrl, stopPlayback, videoPlayer])
// Poll from audio player for progress display; keep video in sync if drifting
useEffect(() => {
const id = global.setInterval(() => {
try {
const dur = (audioPlayer?.duration || 0) * 1000;
const pos = (audioPlayer?.currentTime || 0) * 1000;
const playing = !!audioPlayer?.playing || !!videoPlayer?.playing;
setProgressInfo({ pos, dur, isPlaying: playing });
const dur = (audioPlayer?.duration || 0) * 1000
const pos = (audioPlayer?.currentTime || 0) * 1000
const playing = !!audioPlayer?.playing || !!videoPlayer?.playing
setProgressInfo({ pos, dur, isPlaying: playing })
// basic drift correction: if desync > 300ms, align video
if (videoPlayer && !Number.isNaN(videoPlayer.currentTime)) {
const v = (videoPlayer.currentTime || 0) * 1000;
const drift = Math.abs(v - pos);
const v = (videoPlayer.currentTime || 0) * 1000
const drift = Math.abs(v - pos)
if (drift > 350) {
videoPlayer.currentTime = Math.max(0, (pos || 0) / 1000);
videoPlayer.currentTime = Math.max(0, (pos || 0) / 1000)
}
}
} catch (e) { }
}, 250);
return () => global.clearInterval(id);
}, [audioPlayer, videoPlayer]);
} catch (e) {}
}, 250)
return () => global.clearInterval(id)
}, [audioPlayer, videoPlayer])
const onSeek = async (targetMs) => {
try {
const dur = progressInfo.dur || 0;
const pos = Math.max(0, Math.min(dur, Math.floor(targetMs)));
if (audioPlayer && dur > 0)
await audioPlayer.seekTo?.(Math.floor(pos / 1000));
if (videoPlayer) videoPlayer.currentTime = Math.max(0, pos / 1000);
} catch (e) { }
};
const dur = progressInfo.dur || 0
const pos = Math.max(0, Math.min(dur, Math.floor(targetMs)))
if (audioPlayer && dur > 0) await audioPlayer.seekTo?.(Math.floor(pos / 1000))
if (videoPlayer) videoPlayer.currentTime = Math.max(0, pos / 1000)
} catch (e) {}
}
const onSeekStart = useCallback(() => {
playbackEndedRef.current = false;
}, []);
playbackEndedRef.current = false
}, [])
const pauseDuringSeek = useCallback(async () => {
try {
if (audioPlayer?.playing) await audioPlayer.pause?.();
} catch (e) { }
if (audioPlayer?.playing) await audioPlayer.pause?.()
} catch (e) {}
try {
if (videoPlayer?.playing) videoPlayer.pause();
} catch (e) { }
}, [audioPlayer, videoPlayer]);
if (videoPlayer?.playing) videoPlayer.pause()
} catch (e) {}
}, [audioPlayer, videoPlayer])
const resumeAfterSeek = useCallback(async () => {
try {
if (audioPlayer) await audioPlayer.play?.();
} catch (e) { }
if (audioPlayer) await audioPlayer.play?.()
} catch (e) {}
try {
if (videoPlayer) videoPlayer.play();
} catch (e) { }
}, [audioPlayer, videoPlayer]);
if (videoPlayer) videoPlayer.play()
} catch (e) {}
}, [audioPlayer, videoPlayer])
const sliderProgress = useMemo(() => {
return progressInfo.dur
? Math.min(1, (progressInfo.pos || 0) / progressInfo.dur)
: 0;
}, [progressInfo]);
return progressInfo.dur ? Math.min(1, (progressInfo.pos || 0) / progressInfo.dur) : 0
}, [progressInfo])
useEffect(() => {
const duration = progressInfo?.dur || 0;
if (!duration) return;
const position = progressInfo?.pos || 0;
const duration = progressInfo?.dur || 0
if (!duration) return
const position = progressInfo?.pos || 0
if (playbackEndedRef.current && duration - position > 1000) {
playbackEndedRef.current = false;
return;
playbackEndedRef.current = false
return
}
const remaining = Math.max(0, duration - position);
const remaining = Math.max(0, duration - position)
if (remaining <= 400 && !playbackEndedRef.current) {
playbackEndedRef.current = true;
void stopPlayback();
playbackEndedRef.current = true
void stopPlayback()
}
}, [progressInfo, stopPlayback]);
}, [progressInfo, stopPlayback])
const handleTogglePlayback = async () => {
try {
const duration = progressInfo?.dur || 0;
const position = progressInfo?.pos || 0;
const isAtEnd = duration > 0 && duration - position < 1000;
const isCurrentlyPlaying =
!!audioPlayer?.playing || !!videoPlayer?.playing;
const duration = progressInfo?.dur || 0
const position = progressInfo?.pos || 0
const isAtEnd = duration > 0 && duration - position < 1000
const isCurrentlyPlaying = !!audioPlayer?.playing || !!videoPlayer?.playing
if (isCurrentlyPlaying) {
playbackEndedRef.current = false;
if (audioPlayer?.playing) await audioPlayer.pause?.();
if (videoPlayer?.playing) videoPlayer.pause();
return;
playbackEndedRef.current = false
if (audioPlayer?.playing) await audioPlayer.pause?.()
if (videoPlayer?.playing) videoPlayer.pause()
return
}
if (isAtEnd) {
if (audioPlayer) await audioPlayer.seekTo?.(0);
if (videoPlayer) videoPlayer.currentTime = 0;
if (audioPlayer) await audioPlayer.seekTo?.(0)
if (videoPlayer) videoPlayer.currentTime = 0
}
playbackEndedRef.current = false;
if (songUrl && audioPlayer) await audioPlayer.play?.();
if (videoPlayer) videoPlayer.play();
} catch (e) { }
};
playbackEndedRef.current = false
if (songUrl && audioPlayer) await audioPlayer.play?.()
if (videoPlayer) videoPlayer.play()
} catch (e) {}
}
return (
<Page backgroundColor={Palette.grayMid} headerType="NONE">
<MusicLandHeader progress={19} onPressBack={goBack} />
<View
style={{ flex: 1, paddingTop: 12, gap: 14, paddingBottom: gutters * 2 }}
>
<View style={{ flex: 1, paddingTop: 12, gap: 14, paddingBottom: gutters * 2 }}>
<View style={{ flex: 1, gap: 22 }}>
{!!videoUri && (
<VideoView
@@ -175,12 +169,12 @@ const RecordedPlayback = ({ route }) => {
nativeControls={false}
contentFit="contain"
style={{
width: "80%",
width: '80%',
flex: 1,
alignSelf: "center",
alignSelf: 'center',
borderRadius: 16,
backgroundColor: "#00000066",
overflow: "hidden",
backgroundColor: '#00000066',
overflow: 'hidden',
}}
/>
)}
@@ -200,13 +194,13 @@ const RecordedPlayback = ({ route }) => {
width: 72,
height: 72,
borderRadius: 36,
alignSelf: "center",
alignSelf: 'center',
marginTop: 4,
alignItems: "center",
justifyContent: "center",
backgroundColor: "rgba(10, 5, 24, 0.65)",
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(10, 5, 24, 0.65)',
borderWidth: 1,
borderColor: "#F94697",
borderColor: '#F94697',
}}
>
<Image
@@ -215,41 +209,39 @@ const RecordedPlayback = ({ route }) => {
/>
</Pressable>
</View>
<View
style={{ width: "80%", alignSelf: "center", marginTop: 4, gap: 12 }}
>
<View style={{ width: '80%', alignSelf: 'center', marginTop: 4, gap: 12 }}>
<GradientButton
title="Je valide"
onPress={() => {
navigate(Routes.PlaybackDownload, {
action: "playback",
action: 'playback',
uri: videoUri,
project,
});
})
}}
/>
<BorderGradientButton
title="Recommencer"
onPress={async () => {
try {
await stopPlayback();
} catch (e) { }
await stopPlayback()
} catch (e) {}
try {
if (videoUri) {
const info = await FileSystem.getInfoAsync(videoUri);
const info = await FileSystem.getInfoAsync(videoUri)
if (info?.exists)
await FileSystem.deleteAsync(videoUri, {
idempotent: true,
});
})
}
} catch (e) { }
navigate(Routes.RecordPlayback, { project });
} catch (e) {}
navigate(Routes.RecordPlayback, { project })
}}
/>
</View>
</View>
</Page>
);
};
)
}
export default RecordedPlayback;
export default RecordedPlayback
+177 -196
View File
@@ -1,266 +1,247 @@
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { Image, Pressable, Text, View } from "react-native";
import { background, icons } from "../../assets";
import BorderGradientButton from "../../components/BorderGradientButton";
import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader";
import Slider from "../../components/Slider";
import Page from "../../layouts/Page";
import { Routes } from "../../navigation";
import { goBack, navigate } from "../../navigation/NavigationService";
import { gutters, Palette } from "../../styles";
import { isWeb } from "../../hooks/useLayoutType";
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
import { releaseBlobUrl } from "../../utils/blobUrlCache";
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Image, Pressable, Text, View } from 'react-native'
import { background, icons } from '../../assets'
import BorderGradientButton from '../../components/BorderGradientButton'
import GradientButton from '../../components/GradientButton'
import MusicLandHeader from '../../components/MusicLandHeader'
import Slider from '../../components/Slider'
import Page from '../../layouts/Page'
import { Routes } from '../../navigation'
import { goBack, navigate } from '../../navigation/NavigationService'
import { gutters, Palette } from '../../styles'
import { isWeb } from '../../hooks/useLayoutType'
import useSharedAudioPlayer from '../../hooks/useSharedAudioPlayer'
import { releaseBlobUrl } from '../../utils/blobUrlCache'
const fmtSeconds = (s) => {
const total = Math.max(0, Math.floor(Number(s || 0)));
const m = Math.floor(total / 60).toString();
const sec = (total % 60).toString().padStart(2, "0");
return `${m}:${sec}`;
};
const total = Math.max(0, Math.floor(Number(s || 0)))
const m = Math.floor(total / 60).toString()
const sec = (total % 60).toString().padStart(2, '0')
return `${m}:${sec}`
}
const toSeconds = (value) => {
const n = Number(value ?? 0);
if (!Number.isFinite(n) || n < 0) return 0;
return n > 10000 ? n / 1000 : n; // heuristique ms → s
};
const n = Number(value ?? 0)
if (!Number.isFinite(n) || n < 0) return 0
return n > 10000 ? n / 1000 : n // heuristique ms → s
}
const WEB_PREVIEW_WIDTH = 360;
const WEB_PREVIEW_WIDTH = 360
const RecordedPlayback = ({ route }) => {
const { videoUri, project } = route.params || {};
const songUrl = project?.songUrl || null;
const { videoUri, project } = route.params || {}
const songUrl = project?.songUrl || null
// AUDIO PLAYER (expo-audio → seconds)
const audioPlayer = useSharedAudioPlayer(
songUrl ? { uri: songUrl } : undefined,
{
id: project?.id
? `recorded-${project.id}`
: songUrl
? `recorded-${songUrl}`
: undefined,
title: typeof project?.title === "string" ? project.title : "Sans titre",
artist:
typeof project?.userName === "string" ? project.userName : "MusicLand",
artwork: project?.coverUrl || null,
coverUrl: project?.coverUrl || null,
metadata: { projectId: project?.id, screen: "RecordedPlayback" },
},
);
const audioPlayer = useSharedAudioPlayer(songUrl ? { uri: songUrl } : undefined, {
id: project?.id ? `recorded-${project.id}` : songUrl ? `recorded-${songUrl}` : undefined,
title: typeof project?.title === 'string' ? project.title : 'Sans titre',
artist: typeof project?.userName === 'string' ? project.userName : 'MusicLand',
artwork: project?.coverUrl || null,
coverUrl: project?.coverUrl || null,
metadata: { projectId: project?.id, screen: 'RecordedPlayback' },
})
// Optionnel: si tu veux quand même un rendu vidéo sur web si tu as une source
const videoElRef = useRef(null);
const playbackEndedRef = useRef(false);
const shouldPreserveBlobRef = useRef(false);
const videoElRef = useRef(null)
const playbackEndedRef = useRef(false)
const shouldPreserveBlobRef = useRef(false)
const [progress, setProgress] = useState({
posS: 0, // secondes
durS: 0, // secondes
playing: false,
});
})
const stopPlayback = useCallback(async () => {
try {
if (audioPlayer?.playing) await audioPlayer.pause?.();
} catch { }
if (audioPlayer?.playing) await audioPlayer.pause?.()
} catch {}
try {
if (videoElRef.current && !videoElRef.current.paused) {
videoElRef.current.pause();
videoElRef.current.pause()
}
} catch { }
}, [audioPlayer]);
} catch {}
}, [audioPlayer])
useEffect(() => {
return () => {
if (!shouldPreserveBlobRef.current) {
releaseBlobUrl(videoUri || null);
releaseBlobUrl(videoUri || null)
}
};
}, [videoUri]);
}
}, [videoUri])
// Démarrage / arrêt
useEffect(() => {
playbackEndedRef.current = false;
playbackEndedRef.current = false
const start = async () => {
try {
if (audioPlayer && songUrl) {
await audioPlayer.play?.();
await audioPlayer.play?.()
}
if (videoElRef.current && videoUri) {
// Lecture vidéo HTML5 (muet pour éviter les policies)
videoElRef.current.muted = true;
videoElRef.current.play().catch(() => { });
videoElRef.current.muted = true
videoElRef.current.play().catch(() => {})
}
} catch { }
};
start();
} catch {}
}
start()
return () => {
playbackEndedRef.current = false;
void stopPlayback();
};
}, [audioPlayer, songUrl, stopPlayback, videoUri]);
playbackEndedRef.current = false
void stopPlayback()
}
}, [audioPlayer, songUrl, stopPlayback, videoUri])
// Boucle de progression + éventuelle sync de la vidéo si fournie
useEffect(() => {
const id = setInterval(() => {
try {
const durS = toSeconds(audioPlayer?.duration); // secondes
const posS = toSeconds(audioPlayer?.currentTime); // secondes
const durS = toSeconds(audioPlayer?.duration) // secondes
const posS = toSeconds(audioPlayer?.currentTime) // secondes
setProgress({
posS,
durS,
playing: !!audioPlayer?.playing,
});
})
// Sync vidéo si on a une source vidéo
if (
videoElRef.current &&
videoUri &&
!Number.isNaN(videoElRef.current.currentTime)
) {
const v = Number(videoElRef.current.currentTime || 0);
const drift = Math.abs(v - posS);
if (videoElRef.current && videoUri && !Number.isNaN(videoElRef.current.currentTime)) {
const v = Number(videoElRef.current.currentTime || 0)
const drift = Math.abs(v - posS)
if (drift > 0.35) {
videoElRef.current.currentTime = Math.max(0, posS);
videoElRef.current.currentTime = Math.max(0, posS)
}
}
} catch { }
}, 250);
return () => clearInterval(id);
}, [audioPlayer, videoUri]);
} catch {}
}, 250)
return () => clearInterval(id)
}, [audioPlayer, videoUri])
// Slider: ratio 0..1
const sliderProgress = useMemo(() => {
return progress.durS > 0 ? Math.min(1, progress.posS / progress.durS) : 0;
}, [progress]);
const pendingSeekRef = useRef(null);
return progress.durS > 0 ? Math.min(1, progress.posS / progress.durS) : 0
}, [progress])
const pendingSeekRef = useRef(null)
useEffect(() => {
const duration = progress?.durS || 0;
if (!duration) return;
const position = progress?.posS || 0;
const duration = progress?.durS || 0
if (!duration) return
const position = progress?.posS || 0
if (playbackEndedRef.current && duration - position > 1) {
playbackEndedRef.current = false;
return;
playbackEndedRef.current = false
return
}
const remaining = Math.max(0, duration - position);
const remaining = Math.max(0, duration - position)
if (remaining <= 0.35 && !playbackEndedRef.current) {
playbackEndedRef.current = true;
playbackEndedRef.current = true
void (async () => {
await stopPlayback();
await stopPlayback()
try {
if (audioPlayer) await audioPlayer.seekTo?.(0);
} catch { }
if (audioPlayer) await audioPlayer.seekTo?.(0)
} catch {}
try {
if (videoElRef.current) {
videoElRef.current.currentTime = 0;
videoElRef.current.currentTime = 0
}
} catch { }
})();
} catch {}
})()
}
}, [progress, stopPlayback, audioPlayer]);
}, [progress, stopPlayback, audioPlayer])
const onSeek = useCallback(
async (ratio) => {
const durS = Number(progress.durS || 0);
const target = durS > 0 ? durS * ratio : 0; // secondes
const durS = Number(progress.durS || 0)
const target = durS > 0 ? durS * ratio : 0 // secondes
const promise = (async () => {
try {
if (audioPlayer && durS > 0) {
await audioPlayer.seekTo?.(Math.max(0, target));
await audioPlayer.seekTo?.(Math.max(0, target))
}
if (videoElRef.current && videoUri) {
videoElRef.current.currentTime = Math.max(0, target);
videoElRef.current.currentTime = Math.max(0, target)
}
} catch { }
})();
pendingSeekRef.current = promise;
await promise;
} catch {}
})()
pendingSeekRef.current = promise
await promise
},
[audioPlayer, progress.durS, videoUri],
);
[audioPlayer, progress.durS, videoUri]
)
const waitForPendingSeek = useCallback(async () => {
const promise = pendingSeekRef.current;
pendingSeekRef.current = null;
if (!promise) return;
const promise = pendingSeekRef.current
pendingSeekRef.current = null
if (!promise) return
try {
await promise;
} catch { }
}, []);
await promise
} catch {}
}, [])
const wasPlayingRef = useRef(false);
const isSeekingRef = useRef(false);
const wasPlayingRef = useRef(false)
const isSeekingRef = useRef(false)
const onSeekStart = useCallback(async () => {
try {
if (isSeekingRef.current) return;
isSeekingRef.current = true;
wasPlayingRef.current = !!audioPlayer?.playing;
pendingSeekRef.current = null;
playbackEndedRef.current = false;
if (audioPlayer?.playing) await audioPlayer.pause?.();
if (isSeekingRef.current) return
isSeekingRef.current = true
wasPlayingRef.current = !!audioPlayer?.playing
pendingSeekRef.current = null
playbackEndedRef.current = false
if (audioPlayer?.playing) await audioPlayer.pause?.()
if (videoElRef.current && !videoElRef.current.paused) {
videoElRef.current.pause();
videoElRef.current.pause()
}
} catch { }
}, [audioPlayer]);
} catch {}
}, [audioPlayer])
const onSeekEnd = useCallback(async () => {
try {
if (!isSeekingRef.current) return;
await waitForPendingSeek();
isSeekingRef.current = false;
if (!isSeekingRef.current) return
await waitForPendingSeek()
isSeekingRef.current = false
if (wasPlayingRef.current) {
if (audioPlayer) await audioPlayer.resume?.();
if (videoElRef.current && videoUri)
videoElRef.current.play().catch(() => { });
if (audioPlayer) await audioPlayer.resume?.()
if (videoElRef.current && videoUri) videoElRef.current.play().catch(() => {})
}
} catch { }
}, [audioPlayer, videoUri, waitForPendingSeek]);
} catch {}
}, [audioPlayer, videoUri, waitForPendingSeek])
const handleTogglePlayback = useCallback(async () => {
try {
const duration = Number(progress?.durS || 0);
const position = Number(progress?.posS || 0);
const isAtEnd = duration > 0 && duration - position < 0.35;
const isCurrentlyPlaying = !!audioPlayer?.playing;
const duration = Number(progress?.durS || 0)
const position = Number(progress?.posS || 0)
const isAtEnd = duration > 0 && duration - position < 0.35
const isCurrentlyPlaying = !!audioPlayer?.playing
if (isCurrentlyPlaying) {
playbackEndedRef.current = false;
await stopPlayback();
return;
playbackEndedRef.current = false
await stopPlayback()
return
}
if (isAtEnd) {
if (audioPlayer) await audioPlayer.seekTo?.(0);
if (audioPlayer) await audioPlayer.seekTo?.(0)
if (videoElRef.current && videoUri) {
videoElRef.current.currentTime = 0;
videoElRef.current.currentTime = 0
}
}
playbackEndedRef.current = false;
playbackEndedRef.current = false
if (songUrl && audioPlayer) {
await audioPlayer.play?.();
await audioPlayer.play?.()
}
if (videoElRef.current && videoUri) {
videoElRef.current.muted = true;
videoElRef.current.play().catch(() => { });
videoElRef.current.muted = true
videoElRef.current.play().catch(() => {})
}
} catch { }
}, [audioPlayer, songUrl, stopPlayback, progress, videoUri]);
} catch {}
}, [audioPlayer, songUrl, stopPlayback, progress, videoUri])
return (
<Page
containerStyle={{
alignItems: "none",
alignItems: 'none',
}}
backgroundColor={Palette.grayMid}
headerType="NONE"
@@ -274,7 +255,7 @@ const RecordedPlayback = ({ route }) => {
/>
<View
style={{
width: "100%",
width: '100%',
}}
>
{/* Bloc vidéo optionnel si jamais tu as un videoUri sur web */}
@@ -282,14 +263,14 @@ const RecordedPlayback = ({ route }) => {
<Pressable
onPress={handleTogglePlayback}
style={{
width: isWeb ? WEB_PREVIEW_WIDTH : "80%",
maxWidth: "100%",
width: isWeb ? WEB_PREVIEW_WIDTH : '80%',
maxWidth: '100%',
aspectRatio: 9 / 16,
borderRadius: 12,
backgroundColor: "#00000066",
overflow: "hidden",
alignSelf: "center",
position: "relative",
backgroundColor: '#00000066',
overflow: 'hidden',
alignSelf: 'center',
position: 'relative',
}}
>
<video
@@ -298,10 +279,10 @@ const RecordedPlayback = ({ route }) => {
muted
playsInline
style={{
width: "100%",
height: "100%",
objectFit: "cover",
backgroundColor: "black",
width: '100%',
height: '100%',
objectFit: 'cover',
backgroundColor: 'black',
}}
controls={false}
/>
@@ -309,18 +290,18 @@ const RecordedPlayback = ({ route }) => {
<View
pointerEvents="none"
style={{
position: "absolute",
left: "50%",
top: "50%",
position: 'absolute',
left: '50%',
top: '50%',
transform: [{ translateX: -36 }, { translateY: -36 }],
width: 72,
height: 72,
borderRadius: 36,
alignItems: "center",
justifyContent: "center",
backgroundColor: "rgba(10, 5, 24, 0.75)",
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(10, 5, 24, 0.75)',
borderWidth: 1,
borderColor: "#F94697",
borderColor: '#F94697',
}}
>
<Image source={icons.play} style={{ width: 26, height: 26 }} />
@@ -331,15 +312,15 @@ const RecordedPlayback = ({ route }) => {
// Placeholder quand pas de vidéo sur web
<View
style={{
width: isWeb ? WEB_PREVIEW_WIDTH : "80%",
maxWidth: "100%",
width: isWeb ? WEB_PREVIEW_WIDTH : '80%',
maxWidth: '100%',
aspectRatio: 9 / 16,
borderRadius: 12,
backgroundColor: "#00000066",
alignItems: "center",
justifyContent: "center",
overflow: "hidden",
alignSelf: "center",
backgroundColor: '#00000066',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
alignSelf: 'center',
}}
>
<Text style={{ color: Palette.white, opacity: 0.8 }}>
@@ -350,14 +331,14 @@ const RecordedPlayback = ({ route }) => {
<View
style={{
width: isWeb ? WEB_PREVIEW_WIDTH : "80%",
maxWidth: "100%",
alignSelf: "center",
width: isWeb ? WEB_PREVIEW_WIDTH : '80%',
maxWidth: '100%',
alignSelf: 'center',
marginTop: 6,
alignItems: "center",
alignItems: 'center',
}}
>
<View style={{ width: "100%" }}>
<View style={{ width: '100%' }}>
<Slider
value={fmtSeconds(progress.posS)} // mm:ss (seconds)
maxValue={fmtSeconds(progress.durS)} // mm:ss (seconds)
@@ -373,9 +354,9 @@ const RecordedPlayback = ({ route }) => {
<View
style={{
width: isWeb ? WEB_PREVIEW_WIDTH : "80%",
maxWidth: "100%",
alignSelf: "center",
width: isWeb ? WEB_PREVIEW_WIDTH : '80%',
maxWidth: '100%',
alignSelf: 'center',
gap: 12,
marginTop: 50,
}}
@@ -383,26 +364,26 @@ const RecordedPlayback = ({ route }) => {
<GradientButton
title="Je valide"
onPress={() => {
shouldPreserveBlobRef.current = true;
shouldPreserveBlobRef.current = true
navigate(Routes.PlaybackDownload, {
action: "playback",
action: 'playback',
uri: videoUri || null, // peut être null sur web
project,
});
})
}}
/>
<BorderGradientButton
title="Recommencer"
onPress={async () => {
try {
await stopPlayback();
} catch { }
navigate(Routes.RecordPlayback, { project });
await stopPlayback()
} catch {}
navigate(Routes.RecordPlayback, { project })
}}
/>
</View>
</Page>
);
};
)
}
export default RecordedPlayback;
export default RecordedPlayback
+20 -28
View File
@@ -1,54 +1,46 @@
import { View, Text, Image } from "react-native";
import React from "react";
import MusicLandHeader from "../../components/MusicLandHeader";
import Page from "../../layouts/Page";
import { background, icons, img } from "../../assets";
import { goBack, navigate } from "../../navigation/NavigationService";
import Slider from "../../components/Slider";
import GradientButton from "../../components/GradientButton";
import BorderGradientButton from "../../components/BorderGradientButton";
import { gutters } from "../../styles";
import { Routes } from "../../navigation";
import { View, Text, Image } from 'react-native'
import React from 'react'
import MusicLandHeader from '../../components/MusicLandHeader'
import Page from '../../layouts/Page'
import { background, icons, img } from '../../assets'
import { goBack, navigate } from '../../navigation/NavigationService'
import Slider from '../../components/Slider'
import GradientButton from '../../components/GradientButton'
import BorderGradientButton from '../../components/BorderGradientButton'
import { gutters } from '../../styles'
import { Routes } from '../../navigation'
const VideoFinalize = () => {
return (
<Page backgroundImg={background.playbackBG2} headerType="NONE">
<MusicLandHeader progress={19} onPressBack={goBack} />
<View
style={{ flex: 1, paddingTop: 12, gap: 14, paddingBottom: gutters * 2 }}
>
<View style={{ flex: 1, paddingTop: 12, gap: 14, paddingBottom: gutters * 2 }}>
<View style={{ flex: 1, gap: 22 }}>
<Image
source={img.placeholder3}
style={{
width: "80%",
width: '80%',
flex: 1,
alignSelf: "center",
alignSelf: 'center',
borderRadius: 16,
}}
/>
<Slider value="0:00" maxValue="2:00" />
</View>
<View
style={{ width: "80%", alignSelf: "center", marginTop: 4, gap: 12 }}
>
<BorderGradientButton
icon={icons.stars}
title="Regénérer"
onPress={goBack}
/>
<View style={{ width: '80%', alignSelf: 'center', marginTop: 4, gap: 12 }}>
<BorderGradientButton icon={icons.stars} title="Regénérer" onPress={goBack} />
<GradientButton
title="Valider"
onPress={() =>
navigate(Routes.PlaybackDownload, {
action: "playback",
action: 'playback',
})
}
/>
</View>
</View>
</Page>
);
};
)
}
export default VideoFinalize;
export default VideoFinalize