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: "",
},
_isLoading: false,
_loadingMessage: null,
_config: {
colors: {
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;
+26 -6
View File
@@ -1,13 +1,15 @@
import { ActivityIndicator, Text, View } from "react-native";
import React, { useGlobal } from "reactn";
import { ActivityIndicator, View } from "react-native";
import { Palette } from "../styles";
import useLayoutType from "../hooks/useLayoutType";
import { Palette } from "../styles";
import { FONT_FAMILY } from "../styles/Fonts";
export default ({ children }) => {
const [isGlobalLoading] = useGlobal("_isLoading");
const [loadingMessage] = useGlobal("_loadingMessage");
const { isDesktopWeb = false, isMobileWeb = false } = useLayoutType();
const isWeb = isDesktopWeb || isMobileWeb;
return (
<>
@@ -34,13 +36,31 @@ export default ({ children }) => {
: { position: "absolute" },
]}
>
<LoaderIndicator />
<LoaderIndicator
isWeb={isWeb}
message={isWeb ? loadingMessage : null}
/>
</View>
)}
</>
);
};
export const LoaderIndicator = () => {
return <ActivityIndicator size={"large"} color={Palette.primary} />;
export const LoaderIndicator = ({ isWeb = false, message = null }) => {
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>
);
};
+126 -2
View File
@@ -1,8 +1,10 @@
import { useRoute } from "@react-navigation/native";
import { BlurView } from "expo-blur";
import { Image as ExpoImage } from "expo-image";
import * as ImagePicker from "expo-image-picker";
import React, { useEffect, useMemo, useState } from "react";
import {
ActivityIndicator,
FlatList,
Image,
Pressable,
@@ -18,13 +20,19 @@ import { background, icons, img } from "../../assets";
import BorderGradient from "../../components/BorderGradient/BorderGradient";
import MoreMenu from "../../components/MoreMenu";
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 useLayoutType from "../../hooks/useLayoutType";
import useNavigateToMusicDetails from "../../hooks/useNavigateToMusicDetails";
import Page from "../../layouts/Page";
import { Routes } from "../../navigation";
import { navigate, push } from "../../navigation/NavigationService";
import { push } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
@@ -54,6 +62,8 @@ const Profile = () => {
const [menuPosition, setMenuPosition] = useState(null);
const [showMenu, setShowMenu] = useState(false);
const [selectedProjectId, setSelectedProjectId] = useState(null);
const [updatingPhoto, setUpdatingPhoto] = useState(false);
const [webUploadMessage, setWebUploadMessage] = useState("");
const navigateToMusicDetails = useNavigateToMusicDetails();
const onPressMenu = (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
const MusicListSection = ({ data, emptyText }) => (
<ScrollView style={{ flex: 1, marginBottom: 70 }}>
@@ -287,6 +350,7 @@ const Profile = () => {
)}
<View style={{ alignItems: "center" }}>
<View style={styles.avatarWrapper}>
<ExpoImage
source={
(
@@ -310,6 +374,34 @@ const Profile = () => {
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
style={{
fontSize: 22,
@@ -498,4 +590,36 @@ const styles = StyleSheet.create({
paddingHorizontal: 10,
...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,
},
});
+27 -1
View File
@@ -8,6 +8,7 @@ import BorderGradientButton from "../../components/BorderGradientButton";
import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader";
import firebase, { tasksRef } from "../../config/firebase";
import loaderMessages from "../../config/loaderMessages";
import { uploadFileToFirebase } from "../../helpers/uploadToFirebase";
import { isWeb } from "../../hooks/useLayoutType";
import Page from "../../layouts/Page";
@@ -22,6 +23,9 @@ const PhotoCover = () => {
useUserData();
const { setIsLoading } = useMinuit();
const isGenerating = selectedProject?.coverStatus === "GENERATING";
const coverGenerationMessage = isWeb
? loaderMessages.photoCoverGenerationWeb
: "";
const onChooseForegroundImage = async () => {
try {
@@ -102,9 +106,31 @@ const PhotoCover = () => {
{isGenerating && (
<View style={{ alignItems: "center", gap: 8 }}>
<ActivityIndicator color={Palette.white} />
<Text style={{ color: Palette.white, marginTop: 6 }}>
{isWeb ? (
coverGenerationMessage ? (
<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>
+27 -1
View File
@@ -8,6 +8,7 @@ import BorderGradientButton from "../../components/BorderGradientButton";
import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader";
import firebase, { tasksRef } from "../../config/firebase";
import loaderMessages from "../../config/loaderMessages";
import { isWeb } from "../../hooks/useLayoutType";
import Page from "../../layouts/Page";
import { Routes } from "../../navigation";
@@ -21,6 +22,9 @@ const PouchReady = () => {
const { setIsLoading } = useMinuit();
const isGenerating = selectedProject?.coverStatus === "GENERATING";
const isFocused = useIsFocused();
const coverBackgroundMessage = isWeb
? loaderMessages.pouchReadyGenerationWeb
: "";
const generateCover = useCallback(async () => {
if (!selectedProjectId) return;
@@ -126,9 +130,31 @@ const PouchReady = () => {
{isGenerating && (
<View style={{ alignItems: "center", gap: 8 }}>
<ActivityIndicator color={Palette.white} />
<Text style={{ color: Palette.white, marginTop: 6 }}>
{isWeb ? (
coverBackgroundMessage ? (
<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>