loading messages
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -14,6 +14,9 @@ export default {
|
||||
title: "",
|
||||
},
|
||||
|
||||
_isLoading: false,
|
||||
_loadingMessage: null,
|
||||
|
||||
_config: {
|
||||
colors: {
|
||||
primary: Palette.primary,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
const loaderMessages = {
|
||||
globalDefault: "",
|
||||
profilePhotoUploadWeb: "",
|
||||
photoCoverGenerationWeb: "",
|
||||
pouchReadyGenerationWeb: "",
|
||||
};
|
||||
|
||||
export default loaderMessages;
|
||||
@@ -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;
|
||||
@@ -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 (
|
||||
<>
|
||||
@@ -30,17 +32,35 @@ export default ({ children }) => {
|
||||
isDesktopWeb
|
||||
? { position: "fixed" }
|
||||
: isMobileWeb
|
||||
? { position: "fixed" }
|
||||
: { position: "absolute" },
|
||||
? { position: "fixed" }
|
||||
: { 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>
|
||||
);
|
||||
};
|
||||
|
||||
+150
-26
@@ -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 }}>
|
||||
@@ -286,30 +349,59 @@ const Profile = () => {
|
||||
</PressableScale>
|
||||
)}
|
||||
|
||||
<View style={{ alignItems: "center" }}>
|
||||
<ExpoImage
|
||||
source={
|
||||
(
|
||||
isSelf
|
||||
? currentUserData?.profilePictureURL
|
||||
: userData?.profilePictureURL
|
||||
)
|
||||
? {
|
||||
uri: isSelf
|
||||
? currentUserData?.profilePictureURL
|
||||
: userData?.profilePictureURL,
|
||||
}
|
||||
: img.profile
|
||||
}
|
||||
cachePolicy="memory-disk"
|
||||
priority="high"
|
||||
contentFit="cover"
|
||||
transition={100}
|
||||
style={{
|
||||
...size({ size: 108 }),
|
||||
borderRadius: 100,
|
||||
}}
|
||||
/>
|
||||
<View style={{ alignItems: "center" }}>
|
||||
<View style={styles.avatarWrapper}>
|
||||
<ExpoImage
|
||||
source={
|
||||
(
|
||||
isSelf
|
||||
? currentUserData?.profilePictureURL
|
||||
: userData?.profilePictureURL
|
||||
)
|
||||
? {
|
||||
uri: isSelf
|
||||
? currentUserData?.profilePictureURL
|
||||
: userData?.profilePictureURL,
|
||||
}
|
||||
: img.profile
|
||||
}
|
||||
cachePolicy="memory-disk"
|
||||
priority="high"
|
||||
contentFit="cover"
|
||||
transition={100}
|
||||
style={{
|
||||
...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
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 }}>
|
||||
Génération en cours.
|
||||
</Text>
|
||||
{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>
|
||||
|
||||
@@ -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 }}>
|
||||
Génération en cours.
|
||||
</Text>
|
||||
{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>
|
||||
|
||||
Reference in New Issue
Block a user