feat: tickets
This commit is contained in:
@@ -165,7 +165,7 @@ const GlobalAudioPlayer = () => {
|
||||
}
|
||||
const project =
|
||||
currentTrack?.metadata?.project &&
|
||||
typeof currentTrack.metadata.project === "object"
|
||||
typeof currentTrack.metadata.project === "object"
|
||||
? currentTrack.metadata.project
|
||||
: null;
|
||||
navigateToMusicDetails({
|
||||
|
||||
@@ -42,6 +42,7 @@ const HIDDEN_ROUTE_NAMES = new Set([
|
||||
Routes.PlaybackExample,
|
||||
Routes.PlaybackOnboarding,
|
||||
Routes.Playback,
|
||||
Routes.PlaybackGuide,
|
||||
Routes.RecordPlayback,
|
||||
Routes.RecordedPlayback,
|
||||
Routes.ChooseDecor,
|
||||
|
||||
+288
-272
@@ -1,6 +1,12 @@
|
||||
import { BlurView } from "expo-blur";
|
||||
import { Image as ExpoImage } from "expo-image";
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
@@ -9,6 +15,8 @@ import {
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
} from "react-native";
|
||||
import { background, icons } from "../../assets";
|
||||
import alert from "../../components/Alert";
|
||||
@@ -220,10 +228,7 @@ const PouchReady = () => {
|
||||
const start = coverProgressStartRef.current;
|
||||
if (!start) return;
|
||||
const elapsed = Date.now() - start.getTime();
|
||||
const ratio = Math.max(
|
||||
0,
|
||||
Math.min(1, elapsed / COVER_FAKE_DURATION_MS),
|
||||
);
|
||||
const ratio = Math.max(0, Math.min(1, elapsed / COVER_FAKE_DURATION_MS));
|
||||
const next = COVER_PROGRESS_MAX * ratio;
|
||||
setCoverProgress((prev) => {
|
||||
if (prev >= COVER_PROGRESS_MAX) return COVER_PROGRESS_MAX;
|
||||
@@ -310,45 +315,82 @@ const PouchReady = () => {
|
||||
return (
|
||||
<Page backgroundImg={background.studioBG2} headerType="NONE">
|
||||
<MusicLandHeader onPressBack={goBack} progress={72} />
|
||||
<View style={{ flex: 1, marginTop: 0 }}>
|
||||
<CreateLyricsHeader
|
||||
title={
|
||||
selectedProject?.cover?.backgroundGenerated
|
||||
? "Ta pochette est prête!"
|
||||
: "Génération de la pochette"
|
||||
}
|
||||
/>
|
||||
<View style={{ flex: 1, ...Style.containerCenter }}>
|
||||
<View style={{ width: isWeb ? "80%" : "100%", gap: 24 }}>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
flexDirection: isWeb ? "row" : "column",
|
||||
gap: 16,
|
||||
justifyContent: "center",
|
||||
flexWrap: "wrap",
|
||||
}}
|
||||
>
|
||||
{displayOptions.length > 0 ? (
|
||||
displayOptions.map((option, index) => {
|
||||
const optionUri =
|
||||
option?.finalUrl || option?.generatedUrl || "";
|
||||
if (!optionUri) return null;
|
||||
if (hasGeneratedOptions) {
|
||||
const isSelected = option?.id === selectedOption?.id;
|
||||
const cardWidthStyle = isWeb
|
||||
? styles.coverOptionCardWeb
|
||||
: styles.coverOptionCardMobile;
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<View style={{ flex: 1, marginTop: 0 }}>
|
||||
<CreateLyricsHeader
|
||||
title={
|
||||
selectedProject?.cover?.backgroundGenerated
|
||||
? "Ta pochette est prête!"
|
||||
: "Génération de la pochette"
|
||||
}
|
||||
/>
|
||||
<View style={{ flex: 1, ...Style.containerCenter }}>
|
||||
<View style={{ width: isWeb ? "80%" : "80%", gap: 24 }}>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
flexDirection: isWeb ? "row" : "column",
|
||||
gap: 16,
|
||||
justifyContent: "center",
|
||||
flexWrap: "wrap",
|
||||
}}
|
||||
>
|
||||
{displayOptions.length > 0 ? (
|
||||
displayOptions.map((option, index) => {
|
||||
const optionUri =
|
||||
option?.finalUrl || option?.generatedUrl || "";
|
||||
if (!optionUri) return null;
|
||||
if (hasGeneratedOptions) {
|
||||
const isSelected = option?.id === selectedOption?.id;
|
||||
const cardWidthStyle = isWeb
|
||||
? styles.coverOptionCardWeb
|
||||
: styles.coverOptionCardMobile;
|
||||
return (
|
||||
<Pressable
|
||||
key={option?.id || index}
|
||||
onPress={() => handleSelectOption(option)}
|
||||
disabled={isSelecting}
|
||||
style={[
|
||||
styles.coverOptionCard,
|
||||
cardWidthStyle,
|
||||
isSelected ? styles.coverOptionCardSelected : null,
|
||||
isSelecting ? styles.coverOptionCardDisabled : null,
|
||||
]}
|
||||
>
|
||||
<ExpoImage
|
||||
source={{ uri: optionUri }}
|
||||
cachePolicy="memory-disk"
|
||||
priority="high"
|
||||
contentFit="cover"
|
||||
transition={120}
|
||||
style={styles.coverOptionImage}
|
||||
/>
|
||||
<View style={styles.coverOptionBadge}>
|
||||
<Text style={styles.coverOptionBadgeLabel}>
|
||||
{`Option ${index + 1}`}
|
||||
</Text>
|
||||
</View>
|
||||
{isSelected && (
|
||||
<View style={styles.coverOptionSelectedBadge}>
|
||||
<Text style={styles.coverOptionSelectedLabel}>
|
||||
Sélectionnée
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Pressable
|
||||
<View
|
||||
key={option?.id || index}
|
||||
onPress={() => handleSelectOption(option)}
|
||||
disabled={isSelecting}
|
||||
style={[
|
||||
styles.coverOptionCard,
|
||||
cardWidthStyle,
|
||||
isSelected ? styles.coverOptionCardSelected : null,
|
||||
isSelecting ? styles.coverOptionCardDisabled : null,
|
||||
styles.coverPreviewCard,
|
||||
isWeb
|
||||
? styles.coverPreviewCardWeb
|
||||
: styles.coverPreviewCardMobile,
|
||||
]}
|
||||
>
|
||||
<ExpoImage
|
||||
@@ -357,62 +399,41 @@ const PouchReady = () => {
|
||||
priority="high"
|
||||
contentFit="cover"
|
||||
transition={120}
|
||||
style={styles.coverOptionImage}
|
||||
style={styles.coverPreviewImage}
|
||||
/>
|
||||
<View style={styles.coverOptionBadge}>
|
||||
<Text style={styles.coverOptionBadgeLabel}>
|
||||
{`Option ${index + 1}`}
|
||||
</Text>
|
||||
</View>
|
||||
{isSelected && (
|
||||
<View style={styles.coverOptionSelectedBadge}>
|
||||
<Text style={styles.coverOptionSelectedLabel}>
|
||||
Sélectionnée
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View
|
||||
key={option?.id || index}
|
||||
style={[
|
||||
styles.coverPreviewCard,
|
||||
isWeb
|
||||
? styles.coverPreviewCardWeb
|
||||
: styles.coverPreviewCardMobile,
|
||||
]}
|
||||
>
|
||||
<ExpoImage
|
||||
source={{ uri: optionUri }}
|
||||
cachePolicy="memory-disk"
|
||||
priority="high"
|
||||
contentFit="cover"
|
||||
transition={120}
|
||||
style={styles.coverPreviewImage}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<>
|
||||
{isGenerating && (
|
||||
<View
|
||||
style={{
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
width: isWeb ? 300 : "100%",
|
||||
height: 300,
|
||||
borderRadius: 20,
|
||||
backgroundColor: "#00000040",
|
||||
alignSelf: "center",
|
||||
...Style.containerCenter,
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator color={Palette.white} />
|
||||
{isWeb ? (
|
||||
coverBackgroundMessage ? (
|
||||
})
|
||||
) : (
|
||||
<>
|
||||
{isGenerating && (
|
||||
<View
|
||||
style={{
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
width: isWeb ? 300 : "100%",
|
||||
height: 300,
|
||||
borderRadius: 20,
|
||||
backgroundColor: "#00000040",
|
||||
alignSelf: "center",
|
||||
...Style.containerCenter,
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator color={Palette.white} />
|
||||
{isWeb ? (
|
||||
coverBackgroundMessage ? (
|
||||
<Text
|
||||
style={{
|
||||
color: Palette.white,
|
||||
marginTop: 6,
|
||||
textAlign: "center",
|
||||
opacity: 0.9,
|
||||
}}
|
||||
>
|
||||
{coverBackgroundMessage}
|
||||
</Text>
|
||||
) : null
|
||||
) : (
|
||||
<Text
|
||||
style={{
|
||||
color: Palette.white,
|
||||
@@ -421,201 +442,196 @@ const PouchReady = () => {
|
||||
opacity: 0.9,
|
||||
}}
|
||||
>
|
||||
{coverBackgroundMessage}
|
||||
Génération en cours.
|
||||
</Text>
|
||||
)}
|
||||
<View style={{ alignItems: "center", gap: 10 }}>
|
||||
<ProgressBar gradient progress={coverProgressValue} />
|
||||
<Text
|
||||
style={{
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 13,
|
||||
}}
|
||||
>
|
||||
{coverProgressValue}%
|
||||
</Text>
|
||||
) : null
|
||||
) : (
|
||||
<Text
|
||||
style={{
|
||||
color: Palette.white,
|
||||
marginTop: 6,
|
||||
textAlign: "center",
|
||||
opacity: 0.9,
|
||||
}}
|
||||
>
|
||||
Génération en cours.
|
||||
</Text>
|
||||
)}
|
||||
<View style={{ alignItems: "center", gap: 10 }}>
|
||||
<ProgressBar gradient progress={coverProgressValue} />
|
||||
<Text
|
||||
style={{
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
fontSize: 13,
|
||||
}}
|
||||
>
|
||||
{coverProgressValue}%
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
// </View>
|
||||
)}
|
||||
</View>
|
||||
{shouldShowStylePanel && (
|
||||
<BlurView
|
||||
tint="dark"
|
||||
intensity={isWeb ? 40 : 30}
|
||||
style={[styles.stylePanel, isWeb && styles.stylePanelWeb]}
|
||||
>
|
||||
<View style={styles.stylePanelHeader}>
|
||||
<Text style={styles.panelTitle}>Style de la pochette</Text>
|
||||
<Text style={styles.panelSubtitle}>
|
||||
Choisis une ambiance ou écris ton propre brief créatif.
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.modeContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.modeCard,
|
||||
styleMode === "preset" ? styles.modeCardActive : null,
|
||||
]}
|
||||
>
|
||||
<AppCheckbox
|
||||
label="Choisir un style prédéfini"
|
||||
selected={styleMode === "preset"}
|
||||
onPress={() => {
|
||||
setStyleMode("preset");
|
||||
setIsPresetDropdownOpen(false);
|
||||
if (!selectedPresetStyle) {
|
||||
setSelectedPresetStyle(COVER_STYLE_PRESETS[0]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
{styleMode === "preset" && (
|
||||
<View style={styles.dropdownArea}>
|
||||
<Pressable
|
||||
style={styles.dropdownTrigger}
|
||||
onPress={() => setIsPresetDropdownOpen((prev) => !prev)}
|
||||
>
|
||||
<Text
|
||||
style={styles.dropdownTriggerLabel}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{selectedPresetStyle}
|
||||
</Text>
|
||||
<Image
|
||||
source={icons.chevronDown}
|
||||
style={[
|
||||
styles.dropdownArrow,
|
||||
isPresetDropdownOpen
|
||||
? styles.dropdownArrowOpen
|
||||
: null,
|
||||
]}
|
||||
/>
|
||||
</Pressable>
|
||||
{isPresetDropdownOpen && (
|
||||
<View style={styles.dropdownList}>
|
||||
{COVER_STYLE_PRESETS.map((styleOption, index) => {
|
||||
const isActive =
|
||||
selectedPresetStyle === styleOption;
|
||||
const isLast =
|
||||
index === COVER_STYLE_PRESETS.length - 1;
|
||||
return (
|
||||
<Pressable
|
||||
key={styleOption}
|
||||
style={[
|
||||
styles.dropdownOption,
|
||||
!isLast ? styles.dropdownOptionDivider : null,
|
||||
isActive ? styles.dropdownOptionActive : null,
|
||||
]}
|
||||
onPress={() => {
|
||||
setSelectedPresetStyle(styleOption);
|
||||
setIsPresetDropdownOpen(false);
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.dropdownOptionLabel,
|
||||
isActive
|
||||
? styles.dropdownOptionLabelActive
|
||||
: null,
|
||||
]}
|
||||
>
|
||||
{styleOption}
|
||||
</Text>
|
||||
{isActive && (
|
||||
<Image
|
||||
source={icons.check}
|
||||
style={styles.dropdownOptionIcon}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.modeColumn}>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
// </View>
|
||||
)}
|
||||
</View>
|
||||
{shouldShowStylePanel && (
|
||||
<BlurView
|
||||
tint="dark"
|
||||
intensity={isWeb ? 40 : 30}
|
||||
style={[styles.stylePanel, isWeb && styles.stylePanelWeb]}
|
||||
>
|
||||
<View style={styles.stylePanelHeader}>
|
||||
<Text style={styles.panelTitle}>Style de la pochette</Text>
|
||||
<Text style={styles.panelSubtitle}>
|
||||
Choisis une ambiance ou écris ton propre brief créatif.
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.modeContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.modeCard,
|
||||
styleMode === "custom" ? styles.modeCardActive : null,
|
||||
styleMode === "preset" ? styles.modeCardActive : null,
|
||||
]}
|
||||
>
|
||||
<AppCheckbox
|
||||
label="Style personnalisé"
|
||||
selected={styleMode === "custom"}
|
||||
label="Choisir un style prédéfini"
|
||||
selected={styleMode === "preset"}
|
||||
onPress={() => {
|
||||
setStyleMode("custom");
|
||||
setStyleMode("preset");
|
||||
setIsPresetDropdownOpen(false);
|
||||
if (!selectedPresetStyle) {
|
||||
setSelectedPresetStyle(COVER_STYLE_PRESETS[0]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
{styleMode === "custom" && (
|
||||
<View style={styles.customInputWrapper}>
|
||||
<TextInput
|
||||
placeholder="Exemple : Collage rétro futuriste lumineux"
|
||||
placeholderTextColor={Palette.grayMid}
|
||||
value={customStyle}
|
||||
onChangeText={setCustomStyle}
|
||||
style={styles.customInput}
|
||||
multiline={false}
|
||||
maxLength={120}
|
||||
textAlignVertical="center"
|
||||
autoCapitalize="sentences"
|
||||
autoCorrect
|
||||
selectionColor={Palette.primary}
|
||||
/>
|
||||
{styleMode === "preset" && (
|
||||
<View style={styles.dropdownArea}>
|
||||
<Pressable
|
||||
style={styles.dropdownTrigger}
|
||||
onPress={() =>
|
||||
setIsPresetDropdownOpen((prev) => !prev)
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={styles.dropdownTriggerLabel}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{selectedPresetStyle}
|
||||
</Text>
|
||||
<Image
|
||||
source={icons.chevronDown}
|
||||
style={[
|
||||
styles.dropdownArrow,
|
||||
isPresetDropdownOpen
|
||||
? styles.dropdownArrowOpen
|
||||
: null,
|
||||
]}
|
||||
/>
|
||||
</Pressable>
|
||||
{isPresetDropdownOpen && (
|
||||
<View style={styles.dropdownList}>
|
||||
{COVER_STYLE_PRESETS.map((styleOption, index) => {
|
||||
const isActive =
|
||||
selectedPresetStyle === styleOption;
|
||||
const isLast =
|
||||
index === COVER_STYLE_PRESETS.length - 1;
|
||||
return (
|
||||
<Pressable
|
||||
key={styleOption}
|
||||
style={[
|
||||
styles.dropdownOption,
|
||||
!isLast
|
||||
? styles.dropdownOptionDivider
|
||||
: null,
|
||||
isActive
|
||||
? styles.dropdownOptionActive
|
||||
: null,
|
||||
]}
|
||||
onPress={() => {
|
||||
setSelectedPresetStyle(styleOption);
|
||||
setIsPresetDropdownOpen(false);
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.dropdownOptionLabel,
|
||||
isActive
|
||||
? styles.dropdownOptionLabelActive
|
||||
: null,
|
||||
]}
|
||||
>
|
||||
{styleOption}
|
||||
</Text>
|
||||
{isActive && (
|
||||
<Image
|
||||
source={icons.check}
|
||||
style={styles.dropdownOptionIcon}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.modeColumn}>
|
||||
<View
|
||||
style={[
|
||||
styles.modeCard,
|
||||
styleMode === "custom" ? styles.modeCardActive : null,
|
||||
]}
|
||||
>
|
||||
<AppCheckbox
|
||||
label="Style personnalisé"
|
||||
selected={styleMode === "custom"}
|
||||
onPress={() => {
|
||||
setStyleMode("custom");
|
||||
setIsPresetDropdownOpen(false);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
{styleMode === "custom" && (
|
||||
<View style={styles.customInputWrapper}>
|
||||
<TextInput
|
||||
placeholder="Exemple : Collage rétro futuriste lumineux"
|
||||
placeholderTextColor={Palette.grayMid}
|
||||
value={customStyle}
|
||||
onChangeText={setCustomStyle}
|
||||
style={styles.customInput}
|
||||
multiline={false}
|
||||
maxLength={120}
|
||||
textAlignVertical="center"
|
||||
autoCapitalize="sentences"
|
||||
autoCorrect
|
||||
selectionColor={Palette.primary}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</BlurView>
|
||||
)}
|
||||
</BlurView>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
paddingBottom: gutters * 2,
|
||||
width: isWeb ? "80%" : "100%",
|
||||
alignSelf: "center",
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
{!hasGeneratedOptions && (
|
||||
<BorderGradientButton
|
||||
title={
|
||||
isGenerating ? "Génération en cours..." : "Générer la pochette"
|
||||
}
|
||||
icon={icons.stars}
|
||||
onPress={requestCoverGeneration}
|
||||
disabled={isGenerating}
|
||||
/>
|
||||
)}
|
||||
{hasGeneratedOptions && (
|
||||
<GradientButton
|
||||
title={"Valider la pochette"}
|
||||
onPress={onValidatePicture}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
paddingBottom: gutters * 2,
|
||||
width: isWeb ? "80%" : "100%",
|
||||
alignSelf: "center",
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
{!hasGeneratedOptions && (
|
||||
<BorderGradientButton
|
||||
title={
|
||||
isGenerating ? "Génération en cours..." : "Générer la pochette"
|
||||
}
|
||||
icon={icons.stars}
|
||||
onPress={requestCoverGeneration}
|
||||
disabled={isGenerating}
|
||||
/>
|
||||
)}
|
||||
{hasGeneratedOptions && (
|
||||
<GradientButton
|
||||
title={"Valider la pochette"}
|
||||
onPress={onValidatePicture}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user