Added pfp images

This commit is contained in:
Milan Paunovic
2024-10-21 04:19:19 +02:00
parent 64ee7b38ac
commit a8eb2ff48b
9 changed files with 185 additions and 58 deletions

View File

@ -174,7 +174,7 @@ const MyGroup = () => {
padding-10 padding-10
> >
<Avatar <Avatar
source={{uri: "https://via.placeholder.com/60"}} source={{uri: member?.pfp ?? undefined}}
size={40} size={40}
backgroundColor={Colors.grey60} backgroundColor={Colors.grey60}
/> />
@ -213,7 +213,7 @@ const MyGroup = () => {
padding-10 padding-10
> >
<Avatar <Avatar
source={{uri: "https://via.placeholder.com/60"}} source={{uri: member?.pfp ?? undefined}}
size={40} size={40}
backgroundColor={Colors.grey60} backgroundColor={Colors.grey60}
/> />

View File

@ -1,30 +1,34 @@
import {Colors, Picker, Text, TextField, View} from "react-native-ui-lib";
import React, {useEffect, useRef, useState} from "react"; import React, {useEffect, useRef, useState} from "react";
import {ImageBackground, StyleSheet} from "react-native"; import {StyleSheet, TouchableOpacity} from "react-native";
import {ScrollView} from "react-native-gesture-handler"; import {ScrollView} from "react-native-gesture-handler";
import * as ImagePicker from "expo-image-picker";
import {Colors, Image, Picker, Text, TextField, View} from "react-native-ui-lib";
import Ionicons from "@expo/vector-icons/Ionicons";
import * as tz from "tzdata";
import * as Localization from "expo-localization";
import debounce from "debounce";
import {useAuthContext} from "@/contexts/AuthContext"; import {useAuthContext} from "@/contexts/AuthContext";
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData"; import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
import Ionicons from "@expo/vector-icons/Ionicons"; import {useChangeProfilePicture} from "@/hooks/firebase/useChangeProfilePicture";
import * as tz from 'tzdata';
import * as Localization from 'expo-localization';
import debounce from "debounce";
const MyProfile = () => { const MyProfile = () => {
const {user, profileData} = useAuthContext(); const {user, profileData} = useAuthContext();
const [timeZone, setTimeZone] = useState<string>(
const [timeZone, setTimeZone] = useState<string>(profileData?.timeZone! ?? Localization.getCalendars()[0].timeZone); profileData?.timeZone! ?? Localization.getCalendars()[0].timeZone
);
const [lastName, setLastName] = useState<string>(profileData?.lastName || ""); const [lastName, setLastName] = useState<string>(profileData?.lastName || "");
const [firstName, setFirstName] = useState<string>( const [firstName, setFirstName] = useState<string>(
profileData?.firstName || "" profileData?.firstName || ""
); );
const [profileImage, setProfileImage] = useState<string | ImagePicker.ImagePickerAsset | null>(profileData?.pfp || null);
const {mutateAsync: updateUserData} = useUpdateUserData(); const {mutateAsync: updateUserData} = useUpdateUserData();
const {mutateAsync: changeProfilePicture} = useChangeProfilePicture();
const isFirstRender = useRef(true); const isFirstRender = useRef(true);
const handleUpdateUserData = async () => { const handleUpdateUserData = async () => {
await updateUserData({newUserData: {firstName, lastName, timeZone}}); await updateUserData({newUserData: {firstName, lastName, timeZone}});
} };
const debouncedUserDataUpdate = debounce(handleUpdateUserData, 500); const debouncedUserDataUpdate = debounce(handleUpdateUserData, 500);
@ -34,22 +38,68 @@ const MyProfile = () => {
return; return;
} }
debouncedUserDataUpdate(); debouncedUserDataUpdate();
}, [timeZone, lastName, firstName]); }, [timeZone, lastName, firstName, profileImage]);
useEffect(() => {
if (profileData) {
setFirstName(profileData.firstName || "");
setLastName(profileData.lastName || "");
// setProfileImage(profileData.pfp || null);
setTimeZone(profileData.timeZone || Localization.getCalendars()[0].timeZone!);
}
}, [profileData]);
const pickImage = async () => {
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!permissionResult.granted) {
alert("Permission to access camera roll is required!");
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 1,
});
if (!result.canceled) {
setProfileImage(result.assets[0].uri);
changeProfilePicture(result.assets[0])
}
};
const handleClearImage = async () => {
await updateUserData({newUserData: {pfp: null}});
setProfileImage(null)
}
const pfpUri = profileImage && typeof profileImage === 'object' && 'uri' in profileImage ? profileImage.uri : profileImage;
return ( return (
<ScrollView style={{paddingBottom: 100, flex: 1}}> <ScrollView style={{paddingBottom: 100, flex: 1}}>
<View style={styles.card}> <View style={styles.card}>
<Text style={styles.subTit}>Your Profile</Text> <Text style={styles.subTit}>Your Profile</Text>
<View row spread paddingH-15 centerV marginV-15> <View row spread paddingH-15 centerV marginV-15>
<ImageBackground <TouchableOpacity onPress={pickImage}>
style={styles.pfp} <Image
source={require("../../../../assets/images/profile-picture.png")} key={pfpUri}
/> style={styles.pfp}
source={pfpUri ? {uri: pfpUri} : null}
/>
</TouchableOpacity>
<Text style={styles.photoSet} color="#50be0c"> <TouchableOpacity onPress={pickImage}>
Change Photo <Text style={styles.photoSet} color="#50be0c" onPress={pickImage}>
</Text> {profileData?.pfp ? "Change" : "Add"} Photo
<Text style={styles.photoSet}>Remove Photo</Text> </Text>
</TouchableOpacity>
{profileData?.pfp && (
<TouchableOpacity onPress={handleClearImage}>
<Text style={styles.photoSet}>Remove Photo</Text>
</TouchableOpacity>
)}
</View> </View>
<View paddingH-15> <View paddingH-15>
<Text text80 marginT-10 marginB-7 style={styles.label}> <Text text80 marginT-10 marginB-7 style={styles.label}>
@ -94,24 +144,27 @@ const MyProfile = () => {
<Text style={styles.jakarta12}>Time Zone</Text> <Text style={styles.jakarta12}>Time Zone</Text>
<View style={styles.viewPicker}> <View style={styles.viewPicker}>
<Picker <Picker
// editable={!isLoading}
value={timeZone} value={timeZone}
onChange={(item) => { onChange={(item) => setTimeZone(item as string)}
setTimeZone(item as string)
}}
showSearch showSearch
floatingPlaceholder floatingPlaceholder
style={styles.inViewPicker} style={styles.inViewPicker}
trailingAccessory={ trailingAccessory={
<View style={{ <View
justifyContent: "center", style={{
alignItems: "center", justifyContent: "center",
height: "100%", alignItems: "center",
marginTop: -38, height: "100%",
paddingRight: 15 marginTop: -38,
}}> paddingRight: 15,
<Ionicons name={"chevron-down"} style={{alignSelf: "center"}} size={20} }}
color={"#000000"}/> >
<Ionicons
name={"chevron-down"}
style={{alignSelf: "center"}}
size={20}
color={"#000000"}
/>
</View> </View>
} }
> >
@ -123,9 +176,11 @@ const MyProfile = () => {
); );
}; };
const timeZoneItems = Object.keys(tz.zones).sort().map((zone) => ( const timeZoneItems = Object.keys(tz.zones)
<Picker.Item key={zone} label={zone.replace("/", " / ").replace("_", " ")} value={zone}/> .sort()
)); .map((zone) => (
<Picker.Item key={zone} label={zone.replace("/", " / ").replace("_", " ")} value={zone}/>
));
const styles = StyleSheet.create({ const styles = StyleSheet.create({
card: { card: {
@ -139,7 +194,7 @@ const styles = StyleSheet.create({
pfp: { pfp: {
aspectRatio: 1, aspectRatio: 1,
width: 65.54, width: 65.54,
backgroundColor: "green", backgroundColor: "gray",
borderRadius: 20, borderRadius: 20,
}, },
txtBox: { txtBox: {
@ -150,7 +205,7 @@ const styles = StyleSheet.create({
padding: 15, padding: 15,
height: 45, height: 45,
fontFamily: "PlusJakartaSans_500Medium", fontFamily: "PlusJakartaSans_500Medium",
fontSize: 13 fontSize: 13,
}, },
subTit: { subTit: {
fontFamily: "Manrope_500Medium", fontFamily: "Manrope_500Medium",
@ -159,11 +214,11 @@ const styles = StyleSheet.create({
label: { label: {
fontFamily: "PlusJakartaSans_500Medium", fontFamily: "PlusJakartaSans_500Medium",
fontSize: 12, fontSize: 12,
color: "#a1a1a1" color: "#a1a1a1",
}, },
photoSet: { photoSet: {
fontFamily: "PlusJakartaSans_500Medium", fontFamily: "PlusJakartaSans_500Medium",
fontSize: 13.07 fontSize: 13.07,
}, },
jakarta12: { jakarta12: {
paddingVertical: 10, paddingVertical: 10,
@ -171,18 +226,6 @@ const styles = StyleSheet.create({
fontSize: 12, fontSize: 12,
color: "#a1a1a1", color: "#a1a1a1",
}, },
picker: {
borderRadius: 50,
paddingVertical: 12,
paddingHorizontal: 16,
backgroundColor: Colors.grey80,
marginBottom: 16,
borderColor: Colors.grey50,
borderWidth: 1,
marginTop: -20,
height: 40,
zIndex: 10,
},
viewPicker: { viewPicker: {
borderRadius: 50, borderRadius: 50,
backgroundColor: Colors.grey80, backgroundColor: Colors.grey80,
@ -204,4 +247,4 @@ const styles = StyleSheet.create({
}, },
}); });
export default MyProfile; export default MyProfile;

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import {ImageBackground, StyleSheet} from "react-native"; import {StyleSheet} from "react-native";
import {Text, TouchableOpacity, View} from "react-native-ui-lib"; import {Image, Text, TouchableOpacity, View} from "react-native-ui-lib";
import RemoveAssigneeBtn from "./RemoveAssigneeBtn"; import RemoveAssigneeBtn from "./RemoveAssigneeBtn";
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers"; import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
@ -26,7 +26,7 @@ const AssigneesDisplay = ({selectedAttendees, setSelectedAttendees}: {
<TouchableOpacity key={member.uid} style={styles.assigneeWrapper} <TouchableOpacity key={member.uid} style={styles.assigneeWrapper}
onPress={() => removeAttendee(member.uid!)}> onPress={() => removeAttendee(member.uid!)}>
{member?.pfp ? ( {member?.pfp ? (
<ImageBackground <Image
source={{uri: member?.pfp}} source={{uri: member?.pfp}}
style={styles.image} style={styles.image}
children={<RemoveAssigneeBtn/>} children={<RemoveAssigneeBtn/>}

View File

@ -17,7 +17,7 @@ export interface UserProfile {
password: string; password: string;
familyId?: string; familyId?: string;
uid?: string; uid?: string;
pfp?: string; pfp?: string | null;
eventColor?: string | null; eventColor?: string | null;
timeZone?: string | null; timeZone?: string | null;
firstDayOfWeek?: string | null; firstDayOfWeek?: string | null;

View File

@ -0,0 +1,57 @@
import { useMutation, useQueryClient } from "react-query";
import firestore from "@react-native-firebase/firestore";
import storage from "@react-native-firebase/storage";
import { useAuthContext } from "@/contexts/AuthContext";
import * as ImagePicker from "expo-image-picker";
import { Platform } from "react-native";
export const useChangeProfilePicture = () => {
const queryClient = useQueryClient();
const { user, refreshProfileData } = useAuthContext();
return useMutation({
mutationKey: ["changeProfilePicture"],
mutationFn: async (profilePicture: ImagePicker.ImagePickerAsset) => {
if (!profilePicture?.uri) {
throw new Error("No image selected");
}
let imageUri = profilePicture.uri;
console.log("Selected image URI:", imageUri);
if (Platform.OS === 'ios' && !imageUri.startsWith('file://')) {
imageUri = `file://${imageUri}`;
console.log("Updated image URI for iOS:", imageUri);
}
const fileName = `profilePictures/${new Date().getTime()}_profile.jpg`;
console.log("Firebase Storage file path:", fileName);
try {
const reference = storage().ref(fileName);
console.log('Uploading image to Firebase Storage...');
await reference.putFile(imageUri);
console.log('Image uploaded successfully!');
const downloadURL = await reference.getDownloadURL();
console.log("Download URL:", downloadURL);
await firestore()
.collection("Profiles")
.doc(user?.uid)
.update({ pfp: downloadURL });
} catch (e) {
console.error("Error uploading profile picture:", e.message);
throw e;
}
},
onSuccess: () => {
// Invalidate queries to refresh profile data
queryClient.invalidateQueries("Profiles");
refreshProfileData();
},
});
};

View File

@ -1311,6 +1311,9 @@ PODS:
- Firebase/Functions (10.29.0): - Firebase/Functions (10.29.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseFunctions (~> 10.29.0) - FirebaseFunctions (~> 10.29.0)
- Firebase/Storage (10.29.0):
- Firebase/CoreOnly
- FirebaseStorage (~> 10.29.0)
- FirebaseAppCheckInterop (10.29.0) - FirebaseAppCheckInterop (10.29.0)
- FirebaseAuth (10.29.0): - FirebaseAuth (10.29.0):
- FirebaseAppCheckInterop (~> 10.17) - FirebaseAppCheckInterop (~> 10.17)
@ -1382,6 +1385,13 @@ PODS:
- nanopb (< 2.30911.0, >= 2.30908.0) - nanopb (< 2.30911.0, >= 2.30908.0)
- PromisesSwift (~> 2.1) - PromisesSwift (~> 2.1)
- FirebaseSharedSwift (10.29.0) - FirebaseSharedSwift (10.29.0)
- FirebaseStorage (10.29.0):
- FirebaseAppCheckInterop (~> 10.0)
- FirebaseAuthInterop (~> 10.25)
- FirebaseCore (~> 10.0)
- FirebaseCoreExtension (~> 10.0)
- GoogleUtilities/Environment (~> 7.12)
- GTMSessionFetcher/Core (< 4.0, >= 2.1)
- fmt (9.1.0) - fmt (9.1.0)
- glog (0.3.5) - glog (0.3.5)
- GoogleDataTransport (9.4.1): - GoogleDataTransport (9.4.1):
@ -2719,6 +2729,10 @@ PODS:
- Firebase/Functions (= 10.29.0) - Firebase/Functions (= 10.29.0)
- React-Core - React-Core
- RNFBApp - RNFBApp
- RNFBStorage (21.0.0):
- Firebase/Storage (= 10.29.0)
- React-Core
- RNFBApp
- RNGestureHandler (2.16.2): - RNGestureHandler (2.16.2):
- DoubleConversion - DoubleConversion
- glog - glog
@ -2897,6 +2911,7 @@ DEPENDENCIES:
- "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)" - "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)"
- "RNFBFirestore (from `../node_modules/@react-native-firebase/firestore`)" - "RNFBFirestore (from `../node_modules/@react-native-firebase/firestore`)"
- "RNFBFunctions (from `../node_modules/@react-native-firebase/functions`)" - "RNFBFunctions (from `../node_modules/@react-native-firebase/functions`)"
- "RNFBStorage (from `../node_modules/@react-native-firebase/storage`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNReanimated (from `../node_modules/react-native-reanimated`) - RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`) - RNScreens (from `../node_modules/react-native-screens`)
@ -2924,6 +2939,7 @@ SPEC REPOS:
- FirebaseRemoteConfigInterop - FirebaseRemoteConfigInterop
- FirebaseSessions - FirebaseSessions
- FirebaseSharedSwift - FirebaseSharedSwift
- FirebaseStorage
- GoogleDataTransport - GoogleDataTransport
- GoogleUtilities - GoogleUtilities
- "gRPC-C++" - "gRPC-C++"
@ -3138,6 +3154,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-firebase/firestore" :path: "../node_modules/@react-native-firebase/firestore"
RNFBFunctions: RNFBFunctions:
:path: "../node_modules/@react-native-firebase/functions" :path: "../node_modules/@react-native-firebase/functions"
RNFBStorage:
:path: "../node_modules/@react-native-firebase/storage"
RNGestureHandler: RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler" :path: "../node_modules/react-native-gesture-handler"
RNReanimated: RNReanimated:
@ -3210,6 +3228,7 @@ SPEC CHECKSUMS:
FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d
FirebaseSessions: dbd14adac65ce996228652c1fc3a3f576bdf3ecc FirebaseSessions: dbd14adac65ce996228652c1fc3a3f576bdf3ecc
FirebaseSharedSwift: 20530f495084b8d840f78a100d8c5ee613375f6e FirebaseSharedSwift: 20530f495084b8d840f78a100d8c5ee613375f6e
FirebaseStorage: 436c30aa46f2177ba152f268fe4452118b8a4856
fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120
glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
@ -3283,6 +3302,7 @@ SPEC CHECKSUMS:
RNFBCrashlytics: f465771d96a2eaf9f6104b30abb002cfe78fc0be RNFBCrashlytics: f465771d96a2eaf9f6104b30abb002cfe78fc0be
RNFBFirestore: e47cdde04ea3d9e73e58e037e1aa1d0b1141c316 RNFBFirestore: e47cdde04ea3d9e73e58e037e1aa1d0b1141c316
RNFBFunctions: 738cc9e2177d060d29b5d143ef2f9ed0eda4bb1f RNFBFunctions: 738cc9e2177d060d29b5d143ef2f9ed0eda4bb1f
RNFBStorage: 2dab66f3fcc51de3acd838c72c0ff081e61a0960
RNGestureHandler: 20a4307fd21cbff339abfcfa68192f3f0a6a518b RNGestureHandler: 20a4307fd21cbff339abfcfa68192f3f0a6a518b
RNReanimated: d51431fd3597a8f8320319dce8e42cee82a5445f RNReanimated: d51431fd3597a8f8320319dce8e42cee82a5445f
RNScreens: 30249f9331c3b00ae7cb7922e11f58b3ed369c07 RNScreens: 30249f9331c3b00ae7cb7922e11f58b3ed369c07

View File

@ -113,6 +113,7 @@
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
</array> </array>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>SplashScreen</string> <string>SplashScreen</string>

View File

@ -38,6 +38,7 @@
"@react-native-firebase/crashlytics": "^20.3.0", "@react-native-firebase/crashlytics": "^20.3.0",
"@react-native-firebase/firestore": "^20.4.0", "@react-native-firebase/firestore": "^20.4.0",
"@react-native-firebase/functions": "^20.4.0", "@react-native-firebase/functions": "^20.4.0",
"@react-native-firebase/storage": "^21.0.0",
"@react-navigation/drawer": "^6.7.2", "@react-navigation/drawer": "^6.7.2",
"@react-navigation/native": "^6.0.2", "@react-navigation/native": "^6.0.2",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",

View File

@ -2425,6 +2425,11 @@
resolved "https://registry.npmjs.org/@react-native-firebase/functions/-/functions-20.4.0.tgz" resolved "https://registry.npmjs.org/@react-native-firebase/functions/-/functions-20.4.0.tgz"
integrity sha512-g4kAWZboTE9cTdT7KT6k1haHDmEBA36bPCvrh2MJ2RACo2JxotB2MIOEPZ5U/cT94eIAlgI5YtxQQGQfC+VcBQ== integrity sha512-g4kAWZboTE9cTdT7KT6k1haHDmEBA36bPCvrh2MJ2RACo2JxotB2MIOEPZ5U/cT94eIAlgI5YtxQQGQfC+VcBQ==
"@react-native-firebase/storage@^21.0.0":
version "21.0.0"
resolved "https://registry.yarnpkg.com/@react-native-firebase/storage/-/storage-21.0.0.tgz#0905fd67c74629d947f176bfb988d7cc4d85e244"
integrity sha512-meft5Pu0nI7zxhpnP49ko9Uw8GaIy9hXGJfa/fCFrpf2vA9OXdTr3CvgloH/b9DpbkwQGcGTshRqltuttXI67w==
"@react-native/assets-registry@0.74.85": "@react-native/assets-registry@0.74.85":
version "0.74.85" version "0.74.85"
resolved "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.85.tgz" resolved "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.85.tgz"