loading messages

This commit is contained in:
2025-10-06 11:27:02 +02:00
parent 35e8a0f6dc
commit fd416b5b05
9 changed files with 336 additions and 40 deletions
@@ -0,0 +1,20 @@
import React from "react";
import "./rotationTestStyle.css";
// Web-only rotating gradient border container
// Props:
// - active: show rotating gradient when true (default)
// - style, className: optional DOM styles/classes
const RotationBorder = ({ children, style, className, active = true }) => {
const classes = ["box", active ? "a" : null, className]
.filter(Boolean)
.join(" ");
return (
<div className={classes} style={style}>
{children}
</div>
);
};
export default RotationBorder;
@@ -0,0 +1,42 @@
.box {
--border-angle: 0deg;
border-radius: 12px;
width: 100%;
height: 260px;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0px 2px 4px hsl(0 0% 0% / 25%);
animation: border-angle-rotate 5s infinite linear;
border: 2px solid transparent;
position: relative;
&.a {
background: linear-gradient(#11012f, #11012f) padding-box,
conic-gradient(
from var(--border-angle),
oklch(0.6659 0.2211 304.21),
oklch(0.6659 0.2211 304.21 / 0%),
oklch(0.6659 0.2211 304.21),
oklch(0.6659 0.2211 304.21 / 0%),
oklch(0.6659 0.2211 304.21)
)
border-box;
}
}
@keyframes border-angle-rotate {
from {
--border-angle: 0deg;
}
to {
--border-angle: 360deg;
}
}
@property --border-angle {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}
+3
View File
@@ -14,6 +14,9 @@ export default {
title: "", title: "",
}, },
_isLoading: false,
_loadingMessage: null,
_config: { _config: {
colors: { colors: {
primary: Palette.primary, primary: Palette.primary,
+8
View File
@@ -0,0 +1,8 @@
const loaderMessages = {
globalDefault: "",
profilePhotoUploadWeb: "",
photoCoverGenerationWeb: "",
pouchReadyGenerationWeb: "",
};
export default loaderMessages;
+27
View File
@@ -0,0 +1,27 @@
import { useCallback } from "react";
import { useGlobal } from "reactn";
const useGlobalLoading = () => {
const [, setIsLoading] = useGlobal("_isLoading");
const [, setLoadingMessage] = useGlobal("_loadingMessage");
const setLoading = useCallback(
async (loading, { message } = {}) => {
if (loading) {
await setLoadingMessage(message ?? null);
await setIsLoading(true);
} else {
await setIsLoading(false);
await setLoadingMessage(null);
}
},
[setIsLoading, setLoadingMessage]
);
return {
setLoading,
setLoadingMessage,
};
};
export default useGlobalLoading;
+28 -8
View File
@@ -1,13 +1,15 @@
import { ActivityIndicator, Text, View } from "react-native";
import React, { useGlobal } from "reactn"; import React, { useGlobal } from "reactn";
import { ActivityIndicator, View } from "react-native";
import { Palette } from "../styles";
import useLayoutType from "../hooks/useLayoutType"; import useLayoutType from "../hooks/useLayoutType";
import { Palette } from "../styles";
import { FONT_FAMILY } from "../styles/Fonts";
export default ({ children }) => { export default ({ children }) => {
const [isGlobalLoading] = useGlobal("_isLoading"); const [isGlobalLoading] = useGlobal("_isLoading");
const [loadingMessage] = useGlobal("_loadingMessage");
const { isDesktopWeb = false, isMobileWeb = false } = useLayoutType(); const { isDesktopWeb = false, isMobileWeb = false } = useLayoutType();
const isWeb = isDesktopWeb || isMobileWeb;
return ( return (
<> <>
@@ -30,17 +32,35 @@ export default ({ children }) => {
isDesktopWeb isDesktopWeb
? { position: "fixed" } ? { position: "fixed" }
: isMobileWeb : isMobileWeb
? { position: "fixed" } ? { position: "fixed" }
: { position: "absolute" }, : { position: "absolute" },
]} ]}
> >
<LoaderIndicator /> <LoaderIndicator
isWeb={isWeb}
message={isWeb ? loadingMessage : null}
/>
</View> </View>
)} )}
</> </>
); );
}; };
export const LoaderIndicator = () => { export const LoaderIndicator = ({ isWeb = false, message = null }) => {
return <ActivityIndicator size={"large"} color={Palette.primary} />; return (
<View style={{ alignItems: "center", gap: 10 }}>
<ActivityIndicator size={"large"} color={Palette.primary} />
{isWeb && message ? (
<Text
style={{
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
textAlign: "center",
}}
>
{message}
</Text>
) : null}
</View>
);
}; };
+150 -26
View File
@@ -1,8 +1,10 @@
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 { Image as ExpoImage } from "expo-image";
import * as ImagePicker from "expo-image-picker";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import { import {
ActivityIndicator,
FlatList, FlatList,
Image, Image,
Pressable, Pressable,
@@ -18,13 +20,19 @@ import { background, icons, img } 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 { projectsRef, usersRef } from "../../config/firebase"; import firebase, {
projectsRef,
serverTimestamp,
usersRef,
} from "../../config/firebase";
import loaderMessages from "../../config/loaderMessages";
import { uploadFileToFirebase } from "../../helpers/uploadToFirebase";
import useDataFromRef from "../../hooks/useDataFromRef"; import useDataFromRef from "../../hooks/useDataFromRef";
import useLayoutType from "../../hooks/useLayoutType"; import useLayoutType from "../../hooks/useLayoutType";
import useNavigateToMusicDetails from "../../hooks/useNavigateToMusicDetails"; import useNavigateToMusicDetails from "../../hooks/useNavigateToMusicDetails";
import Page from "../../layouts/Page"; import Page from "../../layouts/Page";
import { Routes } from "../../navigation"; import { Routes } from "../../navigation";
import { navigate, push } from "../../navigation/NavigationService"; import { push } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider"; import { useUser } from "../../providers/UserDataProvider";
import { Palette } from "../../styles"; import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts"; import { FONT_FAMILY } from "../../styles/Fonts";
@@ -54,6 +62,8 @@ const Profile = () => {
const [menuPosition, setMenuPosition] = useState(null); const [menuPosition, setMenuPosition] = useState(null);
const [showMenu, setShowMenu] = useState(false); const [showMenu, setShowMenu] = useState(false);
const [selectedProjectId, setSelectedProjectId] = useState(null); const [selectedProjectId, setSelectedProjectId] = useState(null);
const [updatingPhoto, setUpdatingPhoto] = useState(false);
const [webUploadMessage, setWebUploadMessage] = useState("");
const navigateToMusicDetails = useNavigateToMusicDetails(); const navigateToMusicDetails = useNavigateToMusicDetails();
const onPressMenu = (item) => { const onPressMenu = (item) => {
setSelected(item); setSelected(item);
@@ -114,6 +124,59 @@ const Profile = () => {
} }
}; };
const onChangeProfilePicture = async (message = "") => {
if (!isSelf || updatingPhoto) return;
try {
setUpdatingPhoto(true);
setWebUploadMessage(isWeb && message ? message : "");
const uid = currentUID;
if (!uid) return;
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 4],
quality: 1,
});
const pickedUri = result?.assets?.[0]?.uri || null;
if (!pickedUri) return;
const { resultURI = null } = await uploadFileToFirebase({
uri: pickedUri,
path: `users/${uid}/profilePicture.png`,
});
if (!resultURI) {
throw new Error("Téléversement de l'image impossible");
}
await usersRef.doc(uid).set(
{
profilePictureURL: resultURI,
updatedAt: serverTimestamp(),
},
{ merge: true }
);
const authUser = firebase.auth().currentUser;
if (authUser) {
await authUser.updateProfile({ photoURL: resultURI });
}
setTooltip({ text: "Photo de profil mise à jour", type: "success" });
} catch (e) {
setTooltip({
text: e?.message || "Erreur changement photo de profil",
type: "error",
});
} finally {
setUpdatingPhoto(false);
setWebUploadMessage("");
}
};
// Composant factorisé pour afficher la liste de musiques // Composant factorisé pour afficher la liste de musiques
const MusicListSection = ({ data, emptyText }) => ( const MusicListSection = ({ data, emptyText }) => (
<ScrollView style={{ flex: 1, marginBottom: 70 }}> <ScrollView style={{ flex: 1, marginBottom: 70 }}>
@@ -286,30 +349,59 @@ const Profile = () => {
</PressableScale> </PressableScale>
)} )}
<View style={{ alignItems: "center" }}> <View style={{ alignItems: "center" }}>
<ExpoImage <View style={styles.avatarWrapper}>
source={ <ExpoImage
( source={
isSelf (
? currentUserData?.profilePictureURL isSelf
: userData?.profilePictureURL ? currentUserData?.profilePictureURL
) : userData?.profilePictureURL
? { )
uri: isSelf ? {
? currentUserData?.profilePictureURL uri: isSelf
: userData?.profilePictureURL, ? currentUserData?.profilePictureURL
} : userData?.profilePictureURL,
: img.profile }
} : img.profile
cachePolicy="memory-disk" }
priority="high" cachePolicy="memory-disk"
contentFit="cover" priority="high"
transition={100} contentFit="cover"
style={{ transition={100}
...size({ size: 108 }), style={{
borderRadius: 100, ...size({ size: 108 }),
}} borderRadius: 100,
/> }}
/>
{isSelf && (
<Pressable
style={styles.editAvatarButton}
onPress={() =>
onChangeProfilePicture(
loaderMessages.profilePhotoUploadWeb
)
}
disabled={updatingPhoto}
hitSlop={10}
>
<View style={styles.editAvatarBackground}>
{updatingPhoto ? (
<ActivityIndicator size="small" color={Palette.white} />
) : (
<Image
source={icons.edit}
style={styles.editAvatarIcon}
resizeMode="contain"
/>
)}
</View>
</Pressable>
)}
</View>
{isWeb && updatingPhoto && webUploadMessage ? (
<Text style={styles.webLoaderText}>{webUploadMessage}</Text>
) : null}
<Text <Text
style={{ style={{
fontSize: 22, fontSize: 22,
@@ -498,4 +590,36 @@ const styles = StyleSheet.create({
paddingHorizontal: 10, paddingHorizontal: 10,
...Style.containerCenter, ...Style.containerCenter,
}, },
avatarWrapper: {
position: "relative",
...size({ size: 108 }),
alignItems: "center",
justifyContent: "center",
},
editAvatarButton: {
position: "absolute",
bottom: 4,
right: 4,
},
editAvatarBackground: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: Palette.transparentBlack,
borderWidth: 1,
borderColor: Palette.white,
...Style.containerCenter,
},
editAvatarIcon: {
...size({ size: 16 }),
tintColor: Palette.white,
},
webLoaderText: {
marginTop: 8,
color: Palette.white,
fontSize: 12,
fontFamily: FONT_FAMILY.InterRegular,
textAlign: "center",
opacity: 0.85,
},
}); });
+29 -3
View File
@@ -8,6 +8,7 @@ import BorderGradientButton from "../../components/BorderGradientButton";
import GradientButton from "../../components/GradientButton"; import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader"; import MusicLandHeader from "../../components/MusicLandHeader";
import firebase, { tasksRef } from "../../config/firebase"; import firebase, { tasksRef } from "../../config/firebase";
import loaderMessages from "../../config/loaderMessages";
import { uploadFileToFirebase } from "../../helpers/uploadToFirebase"; import { uploadFileToFirebase } from "../../helpers/uploadToFirebase";
import { isWeb } from "../../hooks/useLayoutType"; import { isWeb } from "../../hooks/useLayoutType";
import Page from "../../layouts/Page"; import Page from "../../layouts/Page";
@@ -22,6 +23,9 @@ const PhotoCover = () => {
useUserData(); useUserData();
const { setIsLoading } = useMinuit(); const { setIsLoading } = useMinuit();
const isGenerating = selectedProject?.coverStatus === "GENERATING"; const isGenerating = selectedProject?.coverStatus === "GENERATING";
const coverGenerationMessage = isWeb
? loaderMessages.photoCoverGenerationWeb
: "";
const onChooseForegroundImage = async () => { const onChooseForegroundImage = async () => {
try { try {
@@ -102,9 +106,31 @@ const PhotoCover = () => {
{isGenerating && ( {isGenerating && (
<View style={{ alignItems: "center", gap: 8 }}> <View style={{ alignItems: "center", gap: 8 }}>
<ActivityIndicator color={Palette.white} /> <ActivityIndicator color={Palette.white} />
<Text style={{ color: Palette.white, marginTop: 6 }}> {isWeb ? (
Génération en cours. coverGenerationMessage ? (
</Text> <Text
style={{
color: Palette.white,
marginTop: 6,
textAlign: "center",
opacity: 0.9,
}}
>
{coverGenerationMessage}
</Text>
) : null
) : (
<Text
style={{
color: Palette.white,
marginTop: 6,
textAlign: "center",
opacity: 0.9,
}}
>
Génération en cours.
</Text>
)}
</View> </View>
)} )}
</View> </View>
+29 -3
View File
@@ -8,6 +8,7 @@ import BorderGradientButton from "../../components/BorderGradientButton";
import GradientButton from "../../components/GradientButton"; import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader"; import MusicLandHeader from "../../components/MusicLandHeader";
import firebase, { tasksRef } from "../../config/firebase"; import firebase, { tasksRef } from "../../config/firebase";
import loaderMessages from "../../config/loaderMessages";
import { isWeb } from "../../hooks/useLayoutType"; import { isWeb } from "../../hooks/useLayoutType";
import Page from "../../layouts/Page"; import Page from "../../layouts/Page";
import { Routes } from "../../navigation"; import { Routes } from "../../navigation";
@@ -21,6 +22,9 @@ const PouchReady = () => {
const { setIsLoading } = useMinuit(); const { setIsLoading } = useMinuit();
const isGenerating = selectedProject?.coverStatus === "GENERATING"; const isGenerating = selectedProject?.coverStatus === "GENERATING";
const isFocused = useIsFocused(); const isFocused = useIsFocused();
const coverBackgroundMessage = isWeb
? loaderMessages.pouchReadyGenerationWeb
: "";
const generateCover = useCallback(async () => { const generateCover = useCallback(async () => {
if (!selectedProjectId) return; if (!selectedProjectId) return;
@@ -126,9 +130,31 @@ const PouchReady = () => {
{isGenerating && ( {isGenerating && (
<View style={{ alignItems: "center", gap: 8 }}> <View style={{ alignItems: "center", gap: 8 }}>
<ActivityIndicator color={Palette.white} /> <ActivityIndicator color={Palette.white} />
<Text style={{ color: Palette.white, marginTop: 6 }}> {isWeb ? (
Génération en cours. coverBackgroundMessage ? (
</Text> <Text
style={{
color: Palette.white,
marginTop: 6,
textAlign: "center",
opacity: 0.9,
}}
>
{coverBackgroundMessage}
</Text>
) : null
) : (
<Text
style={{
color: Palette.white,
marginTop: 6,
textAlign: "center",
opacity: 0.9,
}}
>
Génération en cours.
</Text>
)}
</View> </View>
)} )}
</View> </View>