modal transition

This commit is contained in:
2025-10-23 16:07:52 +02:00
parent bd26a2fb29
commit 7501b818c6
11 changed files with 227 additions and 162 deletions
+65
View File
@@ -0,0 +1,65 @@
import { Image } from "expo-image";
import { memo } from "react";
import { Platform, StyleSheet, View } from "react-native";
import { Palette } from "../styles";
const DEFAULT_INTENSITY = Platform.select({ ios: 22, default: 14 });
const ProfilePicture = ({
uri = null,
size = 48,
style,
blurIntensity = DEFAULT_INTENSITY,
blurTint = "dark",
imageProps = {},
transitionDuration = 120,
cachePolicy = "memory-disk",
contentFit = "cover",
accessibilityLabel,
...rest
}) => {
const dimensionStyle = {
width: size,
height: size,
borderRadius: size / 2,
};
return (
<View
accessible
accessibilityRole="image"
accessibilityLabel={accessibilityLabel}
style={[styles.container, dimensionStyle, style]}
{...rest}
>
{uri ? (
<Image
source={{ uri: uri }}
contentFit={contentFit}
cachePolicy={cachePolicy}
transition={transitionDuration}
style={StyleSheet.absoluteFill}
{...imageProps}
/>
) : (
<>
<View style={[styles.fallback, dimensionStyle]} />
</>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
overflow: "hidden",
backgroundColor: "transparent",
},
fallback: {
backgroundColor: Palette.glass,
borderColor: Palette.ultraLightWhite,
borderWidth: 2,
},
});
export default memo(ProfilePicture);
@@ -6,7 +6,6 @@ import {
BottomSheetTextInput, BottomSheetTextInput,
} from '@gorhom/bottom-sheet'; } from '@gorhom/bottom-sheet';
import { BlurView } from 'expo-blur'; import { BlurView } from 'expo-blur';
import { Image as ExpoImage } from 'expo-image';
import React, { import React, {
useCallback, useCallback,
useEffect, useEffect,
@@ -30,7 +29,7 @@ import { useUser } from '../../providers/UserDataProvider';
import { getArtistDisplayName } from '../../utils/artistName'; import { getArtistDisplayName } from '../../utils/artistName';
import { Palette } from '../../styles'; import { Palette } from '../../styles';
import { FONT_FAMILY } from '../../styles/Fonts'; import { FONT_FAMILY } from '../../styles/Fonts';
import { size } from '../../styles/Style'; import ProfilePicture from '../ProfilePicture';
let externalOpen; let externalOpen;
let externalClose; let externalClose;
@@ -273,24 +272,7 @@ const CommentsBottomSheet = () => {
<View <View
key={c.id} key={c.id}
style={{ flexDirection: 'row', gap: 12, marginBottom: 14 }}> style={{ flexDirection: 'row', gap: 12, marginBottom: 14 }}>
{c?.profilePicture ? ( <ProfilePicture uri={c?.profilePicture || null} size={42} />
<ExpoImage
source={{ uri: c.profilePicture }}
cachePolicy='memory-disk'
priority='high'
contentFit='cover'
transition={100}
style={{ ...size({ size: 42 }), borderRadius: 100 }}
/>
) : (
<View
style={{
...size({ size: 42 }),
borderRadius: 100,
backgroundColor: '#FFFFFF22',
}}
/>
)}
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
<View <View
style={{ style={{
+34 -4
View File
@@ -21,6 +21,7 @@ import useDataFromRef from "../../hooks/useDataFromRef";
import { isWeb } from "../../hooks/useLayoutType.js"; import { isWeb } from "../../hooks/useLayoutType.js";
import Page from "../../layouts/Page"; import Page from "../../layouts/Page";
import { navigate } from "../../navigation/NavigationService"; import { navigate } from "../../navigation/NavigationService";
import { Routes } from "../../navigation/Routes";
import { useUser } from "../../providers/UserDataProvider"; import { useUser } from "../../providers/UserDataProvider";
import { Palette } from "../../styles"; import { Palette } from "../../styles";
import { import {
@@ -217,14 +218,43 @@ const Home = ({ navigation, route }) => {
}, [handleDeleteProject, menuProject?.id]); }, [handleDeleteProject, menuProject?.id]);
useEffect(() => { useEffect(() => {
if (route?.params?.showLyricsCongrats) { if (!route?.params?.showLyricsCongrats) {
return;
}
const beatmakerStage = currentProject
? getStageAction("beatmaker", currentProject)
: null;
Alert( Alert(
"Céline", "Céline",
"Bravo ! Vous venez de terminer de créer les paroles de votre musique ! Maintenant vous pouvez passer à la prochaine étape : le Studio avec Malik. Dans cette étape vous allez pouvoir donner vie à votre chanson !" "Bravo ! Vous venez de terminer de créer les paroles de votre musique ! Maintenant vous pouvez passer à la prochaine étape : le Studio avec Malik. Dans cette étape vous allez pouvoir donner vie à votre chanson !",
[
{
text: "Retour à l'accueil",
style: "cancel",
},
{
text: "Continuer avec Malik",
onPress: () => {
if (!currentProject?.id) {
return;
}
if (selectedProject?.id !== currentProject.id) {
selectProject(currentProject.id);
}
const targetRoute = beatmakerStage?.route || Routes.Compose;
navigate(targetRoute, beatmakerStage?.params);
},
},
]
); );
navigation?.setParams?.({ showLyricsCongrats: false }); navigation?.setParams?.({ showLyricsCongrats: false });
} }, [
}, [navigation, route?.params?.showLyricsCongrats]); currentProject,
navigation,
selectProject,
selectedProject?.id,
route?.params?.showLyricsCongrats,
]);
const activeStage = const activeStage =
stageStates[activeStageIndex] || stageStates[preferredStageIndex]; stageStates[activeStageIndex] || stageStates[preferredStageIndex];
@@ -1,14 +1,12 @@
import { Image as ExpoImage } from "expo-image";
import React, { useState } from "react"; import React, { useState } from "react";
import { Pressable, ScrollView, Text, View } from "react-native"; import { Pressable, ScrollView, Text, View } from "react-native";
import { img } from "../../../assets";
import MoreMenu from "../../../components/MoreMenu"; import MoreMenu from "../../../components/MoreMenu";
import ProfilePicture from "../../../components/ProfilePicture";
import useNavigateToMusicDetails from "../../../hooks/useNavigateToMusicDetails"; import useNavigateToMusicDetails from "../../../hooks/useNavigateToMusicDetails";
import { Routes } from "../../../navigation"; import { Routes } from "../../../navigation";
import { navigate } from "../../../navigation/NavigationService"; import { navigate } from "../../../navigation/NavigationService";
import { Palette, Style } from "../../../styles"; import { Palette, Style } from "../../../styles";
import { FONT_FAMILY } from "../../../styles/Fonts"; import { FONT_FAMILY } from "../../../styles/Fonts";
import { size } from "../../../styles/Style";
import { getArtistDisplayName } from "../../../utils/artistName"; import { getArtistDisplayName } from "../../../utils/artistName";
import CreateLyricsHeader from "../../Writing/components/CreateLyricsHeader"; import CreateLyricsHeader from "../../Writing/components/CreateLyricsHeader";
import MusicCard from "./MusicCard"; import MusicCard from "./MusicCard";
@@ -190,27 +188,15 @@ const SearchResultsList = ({
key={user.id} key={user.id}
onPress={() => handleProfilePress(user.id)} onPress={() => handleProfilePress(user.id)}
> >
<ExpoImage <ProfilePicture
source={ uri={
user?.pictureUrl || user?.pictureUrl ||
user?.profilePictureURL || user?.profilePictureURL ||
user?.photoURL user?.photoURL ||
? { null
uri:
user?.pictureUrl ||
user?.profilePictureURL ||
user?.photoURL,
} }
: img.profile size={60}
} imageProps={{ priority: "high" }}
cachePolicy="memory-disk"
priority="high"
contentFit="cover"
transition={100}
style={{
...size({ size: 60 }),
borderRadius: 100,
}}
/> />
<Text <Text
style={{ style={{
+5 -16
View File
@@ -14,6 +14,7 @@ import { responsiveHeight } from "react-native-responsive-dimensions";
import { icons, img } from "../../assets"; import { icons, img } from "../../assets";
import { openComments } from "../../components/bottomsheets/CommentsBottomSheet"; import { openComments } from "../../components/bottomsheets/CommentsBottomSheet";
import KaraokeLyrics from "../../components/KaraokeLyrics"; import KaraokeLyrics from "../../components/KaraokeLyrics";
import ProfilePicture from "../../components/ProfilePicture";
import { import {
arrayRemove, arrayRemove,
arrayUnion, arrayUnion,
@@ -213,23 +214,11 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
navigate(Routes.SingerProfile, { userId: item?.userId }); navigate(Routes.SingerProfile, { userId: item?.userId });
}} }}
> >
{owner?.profilePictureURL ? ( <ProfilePicture
<Image uri={owner?.profilePictureURL || null}
source={{ uri: owner.profilePictureURL }} size={45}
style={{ imageProps={{ priority: "high" }}
...size({ size: 45 }),
borderRadius: 100,
}}
/> />
) : (
<Image
source={img.profile}
style={{
...size({ size: 45 }),
borderRadius: 100,
}}
/>
)}
</Pressable> </Pressable>
{owner?.id && currentUID && owner.id !== currentUID && ( {owner?.id && currentUID && owner.id !== currentUID && (
<Pressable <Pressable
@@ -1,5 +1,4 @@
import { BlurView } from "expo-blur"; import { BlurView } from "expo-blur";
import { Image as ExpoImage } from "expo-image";
import moment from "moment"; import moment from "moment";
import "moment/locale/fr"; import "moment/locale/fr";
import React, { import React, {
@@ -30,7 +29,7 @@ import { useUser } from "../../../providers/UserDataProvider";
import { getArtistDisplayName } from "../../../utils/artistName"; import { getArtistDisplayName } from "../../../utils/artistName";
import { Palette } from "../../../styles"; import { Palette } from "../../../styles";
import { FONT_FAMILY } from "../../../styles/Fonts"; import { FONT_FAMILY } from "../../../styles/Fonts";
import { size } from "../../../styles/Style"; import ProfilePicture from "../../../components/ProfilePicture";
moment.locale("fr"); moment.locale("fr");
@@ -198,18 +197,11 @@ const CommentsPanel = ({
index > 0 && styles.commentRowSpacing, index > 0 && styles.commentRowSpacing,
]} ]}
> >
{c?.profilePicture ? ( <ProfilePicture
<ExpoImage uri={c?.profilePicture || null}
source={{ uri: c.profilePicture }} size={48}
cachePolicy="memory-disk"
priority="high"
contentFit="cover"
transition={100}
style={styles.commentAvatar} style={styles.commentAvatar}
/> />
) : (
<View style={styles.commentAvatarFallback} />
)}
<View style={styles.commentBubble}> <View style={styles.commentBubble}>
<View style={styles.commentHeader}> <View style={styles.commentHeader}>
<Text style={styles.commentAuthor}> <Text style={styles.commentAuthor}>
@@ -315,14 +307,6 @@ const styles = StyleSheet.create({
marginTop: 16, marginTop: 16,
}, },
commentAvatar: { commentAvatar: {
...size({ size: 48 }),
borderRadius: 100,
marginRight: 12,
},
commentAvatarFallback: {
...size({ size: 48 }),
borderRadius: 100,
backgroundColor: "#FFFFFF33",
marginRight: 12, marginRight: 12,
}, },
commentBubble: { commentBubble: {
+6 -19
View File
@@ -1,12 +1,12 @@
import { BlurView } from "expo-blur"; import { BlurView } from "expo-blur";
import { Image as ExpoImage } from "expo-image";
import * as ImagePicker from "expo-image-picker"; import * as ImagePicker from "expo-image-picker";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Platform, Pressable, Text, View } from "react-native"; import { Platform, Pressable, Text, View } from "react-native";
import { responsiveHeight } from "react-native-responsive-dimensions"; import { responsiveHeight } from "react-native-responsive-dimensions";
import { useGlobal } from "reactn"; import { useGlobal } from "reactn";
import { background, img } from "../../assets"; import { background } from "../../assets";
import GradientButton from "../../components/GradientButton"; import GradientButton from "../../components/GradientButton";
import ProfilePicture from "../../components/ProfilePicture";
import firebase, { serverTimestamp, usersRef } from "../../config/firebase"; import firebase, { serverTimestamp, usersRef } from "../../config/firebase";
import { uploadFileToFirebase } from "../../helpers/uploadToFirebase"; import { uploadFileToFirebase } from "../../helpers/uploadToFirebase";
import { isWeb } from "../../hooks/useLayoutType"; import { isWeb } from "../../hooks/useLayoutType";
@@ -15,7 +15,6 @@ import { goBack } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider"; import { useUser } from "../../providers/UserDataProvider";
import { gutters, Palette } from "../../styles"; import { gutters, Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts"; import { FONT_FAMILY } from "../../styles/Fonts";
import { size } from "../../styles/Style";
import EditInput from "./components/EditInput"; import EditInput from "./components/EditInput";
const EditProfile = () => { const EditProfile = () => {
@@ -147,22 +146,10 @@ const EditProfile = () => {
// } // }
> >
<Pressable style={{ alignItems: "center" }} onPress={onChangePicture}> <Pressable style={{ alignItems: "center" }} onPress={onChangePicture}>
<ExpoImage <ProfilePicture
source={ uri={currentUserData?.profilePictureURL || null}
currentUserData?.profilePictureURL size={108}
? { imageProps={{ priority: "high" }}
uri: currentUserData?.profilePictureURL,
}
: img.profile
}
cachePolicy="memory-disk"
priority="high"
contentFit="cover"
transition={100}
style={{
...size({ size: 108 }),
borderRadius: 100,
}}
/> />
<Text <Text
style={{ style={{
+6 -14
View File
@@ -1,11 +1,9 @@
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { Pressable, Text, View } from "react-native"; import { Pressable, Text, View } from "react-native";
import { Image as ExpoImage } from "expo-image";
import Page from "../../layouts/Page"; import Page from "../../layouts/Page";
import { background, img } from "../../assets"; import { background } from "../../assets";
import { Palette, Style } from "../../styles"; import { Palette, Style } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts"; import { FONT_FAMILY } from "../../styles/Fonts";
import { size } from "../../styles/Style";
import { useUser } from "../../providers/UserDataProvider"; import { useUser } from "../../providers/UserDataProvider";
import { usersRef } from "../../config/firebase"; import { usersRef } from "../../config/firebase";
import useDataFromArrayId from "react-native-minuit/src/hooks/useDataFromArrayId"; import useDataFromArrayId from "react-native-minuit/src/hooks/useDataFromArrayId";
@@ -14,6 +12,7 @@ import { Routes } from "../../navigation";
import { useRoute } from "@react-navigation/native"; import { useRoute } from "@react-navigation/native";
import { navigate } from "../../navigation/NavigationService"; import { navigate } from "../../navigation/NavigationService";
import { getArtistDisplayName } from "../../utils/artistName"; import { getArtistDisplayName } from "../../utils/artistName";
import ProfilePicture from "../../components/ProfilePicture";
const Follows = () => { const Follows = () => {
const { params } = useRoute(); const { params } = useRoute();
@@ -92,17 +91,10 @@ const Follows = () => {
style={{ gap: 14, ...Style.containerRow }} style={{ gap: 14, ...Style.containerRow }}
onPress={() => navigate(Routes.SingerProfile, { userId: user?.id })} onPress={() => navigate(Routes.SingerProfile, { userId: user?.id })}
> >
<ExpoImage <ProfilePicture
source={ uri={user?.profilePictureURL || null}
user?.profilePictureURL size={60}
? { uri: user.profilePictureURL } imageProps={{ priority: "high" }}
: img.profile
}
cachePolicy="memory-disk"
priority="high"
contentFit="cover"
transition={100}
style={{ ...size({ size: 60 }), borderRadius: 100 }}
/> />
<Text <Text
style={{ style={{
+8 -22
View File
@@ -1,6 +1,5 @@
import { useRoute } from "@react-navigation/native"; import { useRoute } from "@react-navigation/native";
import { BlurView } from "expo-blur"; import { BlurView } from "expo-blur";
import { Image as ExpoImage } from "expo-image";
import * as ImagePicker from "expo-image-picker"; import * as ImagePicker from "expo-image-picker";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import { import {
@@ -16,10 +15,11 @@ import {
import { SheetManager } from "react-native-actions-sheet"; import { SheetManager } from "react-native-actions-sheet";
import { responsiveHeight } from "react-native-responsive-dimensions"; import { responsiveHeight } from "react-native-responsive-dimensions";
import { useGlobal } from "reactn"; import { useGlobal } from "reactn";
import { background, icons, img } from "../../assets"; import { background, icons } from "../../assets";
import BorderGradient from "../../components/BorderGradient/BorderGradient"; import BorderGradient from "../../components/BorderGradient/BorderGradient";
import MoreMenu from "../../components/MoreMenu"; import MoreMenu from "../../components/MoreMenu";
import PressableScale from "../../components/PressableScale"; import PressableScale from "../../components/PressableScale";
import ProfilePicture from "../../components/ProfilePicture";
import firebase, { import firebase, {
projectsRef, projectsRef,
serverTimestamp, serverTimestamp,
@@ -361,28 +361,14 @@ const Profile = () => {
<View style={{ alignItems: "center" }}> <View style={{ alignItems: "center" }}>
<View style={styles.avatarWrapper}> <View style={styles.avatarWrapper}>
<ExpoImage <ProfilePicture
source={ uri={
(
isSelf isSelf
? currentUserData?.profilePictureURL ? currentUserData?.profilePictureURL || null
: userData?.profilePictureURL : userData?.profilePictureURL || null
)
? {
uri: isSelf
? currentUserData?.profilePictureURL
: userData?.profilePictureURL,
} }
: img.profile size={108}
} imageProps={{ priority: "high" }}
cachePolicy="memory-disk"
priority="high"
contentFit="cover"
transition={100}
style={{
...size({ size: 108 }),
borderRadius: 100,
}}
/> />
{isSelf && ( {isSelf && (
<Pressable <Pressable
+36 -2
View File
@@ -23,6 +23,7 @@ import {
sanitizeStructureList, sanitizeStructureList,
segmentRequiresLyrics, segmentRequiresLyrics,
} from "../../utils/songStructure"; } from "../../utils/songStructure";
import { getStageAction } from "../../utils/projectStages";
import CustomInput from "./components/CustomInput"; import CustomInput from "./components/CustomInput";
const Lyrics = ({ navigation }) => { const Lyrics = ({ navigation }) => {
@@ -264,17 +265,49 @@ const Lyrics = ({ navigation }) => {
if (!isSame) { if (!isSame) {
updateData.musicUrls = firebase.firestore.FieldValue.delete(); updateData.musicUrls = firebase.firestore.FieldValue.delete();
updateData.musicStatus = firebase.firestore.FieldValue.delete(); updateData.musicStatus = firebase.firestore.FieldValue.delete();
}
await projectsRef await projectsRef
.doc(selectedProjectId) .doc(selectedProjectId)
.set(updateData, { merge: true }); .set(updateData, { merge: true });
const projectForStage = {
...selectedProject,
title: titleTrimmed,
lyrics: normalizedNewLyrics,
config: sanitize(projectConfig),
selections: sanitize(projectSelections),
hasLyrics: true,
};
if (!isSame) {
projectForStage.musicUrls = undefined;
projectForStage.musicStatus = undefined;
} }
const beatmakerStage = getStageAction("beatmaker", projectForStage);
await setIsLoading(false);
alert(
"Céline",
"Bravo ! Vous venez de terminer de créer les paroles de votre musique ! Maintenant vous pouvez passer à la prochaine étape : le Studio avec Malik. Dans cette étape vous allez pouvoir donner vie à votre chanson !",
[
{
text: "Retour à l'accueil",
style: "cancel",
onPress: () =>
navigate(Routes.BottomTab, { navigate(Routes.BottomTab, {
screen: Routes.HomeStack, screen: Routes.HomeStack,
params: { params: {
screen: Routes.Home, screen: Routes.Home,
params: { showLyricsCongrats: true },
}, },
}); }),
},
{
text: "Continuer avec Malik",
onPress: () => {
const targetRoute = beatmakerStage?.route || Routes.Compose;
navigate(targetRoute, beatmakerStage?.params);
},
},
]
);
return;
} catch (e) { } catch (e) {
console.log(e); console.log(e);
alertMessage("Erreur", "Échec de l'enregistrement dans le projet."); alertMessage("Erreur", "Échec de l'enregistrement dans le projet.");
@@ -293,6 +326,7 @@ const Lyrics = ({ navigation }) => {
selectedProjectId, selectedProjectId,
projectHasLyrics, projectHasLyrics,
projectLyrics, projectLyrics,
selectedProject,
]); ]);
const typeOccurrences = {}; const typeOccurrences = {};
+34 -4
View File
@@ -3,6 +3,7 @@ import React, { useCallback, useMemo, useState } from "react";
import { Pressable, Text, View } from "react-native"; import { Pressable, Text, View } from "react-native";
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js"; import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
import { background } from "../../assets"; import { background } from "../../assets";
import alert from "../../components/Alert";
import GradientButton from "../../components/GradientButton"; import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader"; import MusicLandHeader from "../../components/MusicLandHeader";
import { isWeb } from "../../hooks/useLayoutType"; import { isWeb } from "../../hooks/useLayoutType";
@@ -11,6 +12,7 @@ import { Routes } from "../../navigation";
import { goBack, navigate } from "../../navigation/NavigationService"; import { goBack, navigate } from "../../navigation/NavigationService";
import { useUserData } from "../../providers/UserDataProvider"; import { useUserData } from "../../providers/UserDataProvider";
import { gutters, Palette, Style } from "../../styles"; import { gutters, Palette, Style } from "../../styles";
import { getStageAction } from "../../utils/projectStages";
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader"; import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
const ValidateCover = () => { const ValidateCover = () => {
@@ -95,18 +97,46 @@ const ValidateCover = () => {
const existingCover = selectedProject?.cover || {}; const existingCover = selectedProject?.cover || {};
const finalUrl = const finalUrl =
selectedOption.finalUrl || selectedOption.generatedUrl || null; selectedOption.finalUrl || selectedOption.generatedUrl || null;
await updateProjectData({ const nextCoverData = {
cover: {
...existingCover, ...existingCover,
options: coverOptions, options: coverOptions,
selectedOptionId: selectedOption.id, selectedOptionId: selectedOption.id,
result: finalUrl, result: finalUrl,
generatedBackground: generatedBackground:
selectedOption.generatedUrl || selectedOption.finalUrl || null, selectedOption.generatedUrl || selectedOption.finalUrl || null,
}, };
await updateProjectData({
cover: nextCoverData,
coverUrl: finalUrl, coverUrl: finalUrl,
}); });
navigate(Routes.Home); const projectForStage = {
...selectedProject,
cover: nextCoverData,
coverUrl: finalUrl,
};
const playbackStage = getStageAction("director", projectForStage);
await setIsLoading(false);
alert(
"Malik",
"Super ! Ta pochette est validée. Tu peux retourner à l'accueil ou continuer avec John pour produire ton playback.",
[
{
text: "Retour à l'accueil",
style: "cancel",
onPress: () => navigate(Routes.Home),
},
{
text: "Continuer avec John",
onPress: () => {
const targetRoute = playbackStage?.route || Routes.Playback;
const params =
playbackStage?.params || { project: projectForStage };
navigate(targetRoute, params);
},
},
]
);
return;
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} finally { } finally {