feat: tickets

This commit is contained in:
2025-12-08 14:13:39 +01:00
parent 8c6cc50dcb
commit b0cf4a20ab
3 changed files with 290 additions and 273 deletions
+1 -1
View File
@@ -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({
+1
View File
@@ -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
View File
@@ -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>
);
};