diff --git a/api.txt b/api.txt new file mode 100644 index 000000000..6a47ade23 --- /dev/null +++ b/api.txt @@ -0,0 +1,67 @@ +Websocket API Protocol + +=== Objects === + +""" +ObjectName { + requiredProp: type + optionalProp: type? +} +""" + +Location { + lat: float, + lon: float, + geohash: string? (generated if not provided) +} + +Message { + author: string, + content: { + text?: string, + attachment?: string, + }, + location: Location, + replyTo?: string, + reactions: { + [key: string]: int, + } +} + +UserProfile { + displayName: string, + profilePicture: int, +} + +=== + +=== Client -> Server Methods === + +""" +methodName( + arguments / inputs... +) ack -> ackResponse +""" + +// Must be called at least once before calling any other methods +updateLocation( + location: Location + ack: func? +) ack -> "success" + +sendMessage( + message: Message + ack: func? +) ack -> "success" + +getNearbyUsers( + callback: func(nearbyUserUids: { [uid: string]: UserProfile }) -> _, +) callback -> map of nearby user uids to user profiles + +// Call after the client has already updated their profile document in firestore +notifyUpdateProfile( + ack: func? +) ack -> "success" + + +=== \ No newline at end of file diff --git a/client/app.json b/client/app.json index 69e66da8b..cc2c81b15 100644 --- a/client/app.json +++ b/client/app.json @@ -12,9 +12,7 @@ "resizeMode": "contain", "backgroundColor": "34D1BF" }, - "assetBundlePatterns": [ - "**/*" - ], + "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true }, @@ -27,6 +25,7 @@ "web": { "favicon": "./assets/favicon.png" }, + "newArchEnabled": true, "plugins": [ "expo-router", [ diff --git a/client/app/components/auth/AuthButtons.tsx b/client/app/components/auth/AuthButtons.tsx index 6336e7702..b6515be75 100644 --- a/client/app/components/auth/AuthButtons.tsx +++ b/client/app/components/auth/AuthButtons.tsx @@ -10,56 +10,6 @@ import { ImageSourcePropType, } from "react-native"; -// Migrated to SettingsScree.tsx - -// import { appSignOut } from "../../services/AuthStore"; -// interface SignOutButtonProps {} -// export const SignOutButton: React.FC = () => { -// const [loading, setLoading] = useState(false); - -// const handleSignOut = async () => { -// Alert.alert( -// "Confirm Sign Out", -// "Are you sure you want to sign out?", -// [ -// { -// text: "Cancel", -// style: "cancel", -// }, -// { -// text: "Sign Out", -// onPress: async () => { -// setLoading(true); -// const response = await appSignOut(); -// setLoading(false); - -// if (response?.user === null) { -// console.log("Sign out successful"); -// } else if (response?.error) { -// console.log(response.error); -// Alert.alert( -// "Sign Out Failed", -// "An error occurred during sign out. Please try again.", -// ); -// } -// }, -// }, -// ], -// { cancelable: false }, -// ); -// }; - -// return ( -// -// Sign Out -// - -// ); -// }; - export const ExternalAuthButton: React.FC<{ onPress?: () => void; companyName: string; diff --git a/client/app/components/chat/ChatScreenFooter.tsx b/client/app/components/chat/ChatScreenFooter.tsx index c96420f44..3495949e9 100644 --- a/client/app/components/chat/ChatScreenFooter.tsx +++ b/client/app/components/chat/ChatScreenFooter.tsx @@ -26,12 +26,12 @@ export const ChatScreenFooter: React.FC = ({ return ( - + {/* - + */} = ({ messages }) => { +const MessageChannel: React.FC = ({ + nearbyUsers, + messages, +}) => { const reverseMessages = [...messages].reverse(); return ( @@ -13,14 +16,18 @@ const MessageChannel: React.FC = ({ messages }) => { width: "100%", }} data={reverseMessages} - keyExtractor={(item) => item.msgId} - renderItem={({ item }) => ( - - )} + renderItem={({ item }) => { + const user = nearbyUsers[item.author]; + // console.log(nearbyUsers); + if (user === undefined) return null; + return ( + + ); + }} inverted // This will render items from the bottom onLayout={() => {}} // This will make sure the list is scrolled to the bottom on first render /> diff --git a/client/app/components/chat/NearbyHeader.tsx b/client/app/components/chat/NearbyHeader.tsx index 93086af07..0bc4b11e7 100644 --- a/client/app/components/chat/NearbyHeader.tsx +++ b/client/app/components/chat/NearbyHeader.tsx @@ -1,3 +1,4 @@ +import { UserProfile } from "@app/types/User"; import React from "react"; import { View, @@ -9,18 +10,25 @@ import { } from "react-native"; import { ChevronLeft } from "react-native-feather"; -export const NearbyHeader: React.FC = () => { +interface NearbyHeaderProps { + onClick: () => void; + nearbyUsers: { [uid: string]: UserProfile }; +} + +export const NearbyHeader: React.FC = ({ onClick, nearbyUsers }) => { return ( - Nearby - - - {5} - + + + + + {Object.keys(nearbyUsers).length} + + ); }; @@ -38,9 +46,10 @@ const styles = StyleSheet.create({ shadowOpacity: 0.3, shadowRadius: 2, paddingVertical: 15, - paddingRight: 25, - paddingLeft: 10, + paddingRight: "5%", + paddingLeft: "10%", gap: 10, + zIndex: 1, }, nearbyText: { fontFamily: "Quicksand", diff --git a/client/app/components/chat/NearbyUserDrawer.tsx b/client/app/components/chat/NearbyUserDrawer.tsx new file mode 100644 index 000000000..99e5b3603 --- /dev/null +++ b/client/app/components/chat/NearbyUserDrawer.tsx @@ -0,0 +1,121 @@ +import React, { useState } from "react"; +import { View, Text, StyleSheet, FlatList, Dimensions, Image } from "react-native"; +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + Easing, +} from "react-native-reanimated"; +import NearbyHeader from "./NearbyHeader"; +import { Pressable } from "react-native"; +import { UserProfile } from "@app/types/User"; + +const SCREEN_WIDTH = Dimensions.get("window").width; + +interface NearbyUserDrawerProps { + nearbyUsers: { [uid: string]: UserProfile }; +} + +const NearbyUserDrawer: React.FC = ({ nearbyUsers }) => { + const [isOpen, setIsOpen] = useState(false); + const translateX = useSharedValue(SCREEN_WIDTH); + + const toggleDrawer = () => { + translateX.value = isOpen + ? withTiming(SCREEN_WIDTH, { + duration: 300, // Duration in milliseconds + easing: Easing.out(Easing.ease), // Smooth easing out + }) + : withTiming(0, { + duration: 300, + easing: Easing.out(Easing.ease), // Smooth easing out + }); + setIsOpen(!isOpen); + }; + + const animatedStyle = useAnimatedStyle(() => ({ + transform: [{ translateX: translateX.value }], + })); + + return ( + + + {/* Overlay and Drawer */} + {isOpen && } + + Nearby Users + item[0]} + renderItem={({ item }) => { + return ( + + + {item[1].displayName} + + ); + }} + /> + + + ); +}; + +const styles = StyleSheet.create({ + container: { + display: "flex", + height: Dimensions.get("screen").height, + flexDirection: "column", + alignItems: "center", + position: "absolute", + }, + overlay: { + position: "absolute", + top: 0, + left: 0, + width: "100%", + height: "100%", + backgroundColor: "rgba(0, 0, 0, 0.15)", + zIndex: 1, + }, + drawer: { + position: "absolute", + right: 0, + top: 0, + width: SCREEN_WIDTH * 0.6, + height: "100%", + backgroundColor: "#fff", + borderLeftWidth: 1, + borderLeftColor: "#ddd", + padding: 30, + shadowColor: "#000", + shadowOffset: { width: -2, height: 0 }, + shadowOpacity: 0.3, + shadowRadius: 4, + zIndex: 2, + gap: 20, + }, + headerText: { + fontFamily: "Quicksand", + fontSize: 24, + fontWeight: "bold", + }, + name: { + fontSize: 18, + marginVertical: 10, + }, +}); + +export default NearbyUserDrawer; diff --git a/client/app/components/common/CustomInputs.tsx b/client/app/components/common/CustomInputs.tsx index 10d804581..0f44c8425 100644 --- a/client/app/components/common/CustomInputs.tsx +++ b/client/app/components/common/CustomInputs.tsx @@ -25,8 +25,6 @@ export const WelcomeEmailInput: React.FC = ({ ); }; -// Maybe will put LogInEmailInput & LogInPasswordInput two together into a single component - export const EmailInput: React.FC = ({ value, onChangeText, @@ -43,6 +41,20 @@ export const EmailInput: React.FC = ({ ); }; +export const DisplayNameInput: React.FC< + ChatInputProps & { invalid: boolean } +> = ({ value, onChangeText, invalid }) => { + return ( + + ); +}; + export const PasswordInput: React.FC = ({ value, onChangeText, diff --git a/client/app/configs/firebaseConfig.ts b/client/app/configs/firebaseConfig.ts index e8308ecbe..aa0e28ff6 100644 --- a/client/app/configs/firebaseConfig.ts +++ b/client/app/configs/firebaseConfig.ts @@ -18,7 +18,7 @@ const firebaseConfig = { let app; let auth: Auth; -// Checks if auth and app have already been initilized as Firebase will throw an error if we try to initialize twice! +// Checks if auth and app have already been initialized as Firebase will throw an error if we try to initialize twice! if (!getApps().length) { try { app = initializeApp(firebaseConfig); diff --git a/client/app/contexts/LocationContext.tsx b/client/app/contexts/LocationContext.tsx index 00d7659f1..1897d92de 100644 --- a/client/app/contexts/LocationContext.tsx +++ b/client/app/contexts/LocationContext.tsx @@ -13,6 +13,16 @@ export const useLocation = () => { return useContext(LocationContext); }; +var setLocationCallback: React.Dispatch> | undefined = undefined; +export const forceRefreshLocation = async () => { + if (setLocationCallback === undefined) return; + const locationData = await getLocation(); + if (locationData && locationData.coords) { + const { latitude, longitude } = locationData.coords; + setLocationCallback({ lat: latitude, lon: longitude }); + } +} + // LocationProvider Component to Provide Location Context export const LocationProvider = ({ children, @@ -20,10 +30,11 @@ export const LocationProvider = ({ children: React.ReactNode; }) => { const [location, setLocation] = useState({ - latitude: 99999, // Impossible starting value - longitude: 99999, + lat: 99999, // Impossible starting value + lon: 99999, }); const [isLocationEnabled, setIsLocationEnabled] = useState(false); + setLocationCallback = undefined; useEffect(() => { let interval: NodeJS.Timeout; @@ -33,16 +44,17 @@ export const LocationProvider = ({ if (!hasPermission) return; setIsLocationEnabled(true); + setLocationCallback = setLocation; // Set up the interval once after permission is granted interval = setInterval(async () => { - const locationData = await getLocation(); + const locationData = await getLocation(); if (locationData && locationData.coords) { const { latitude, longitude } = locationData.coords; - if (latitude !== location.latitude || longitude !== location.longitude) { - setLocation({ latitude, longitude }); + if (latitude !== location.lat || longitude !== location.lon) { + setLocation({ lat: latitude, lon: longitude }); } else { - console.log("Location has not changed"); + //console.log("Location has not changed"); } } }, Number(LOCATION_REFRESH_RATE)); @@ -57,14 +69,14 @@ export const LocationProvider = ({ console.log("[LOG]: Cleaning up location useEffect"); } }; - }, []); + }, [location.lat, location.lon]); return ( {children} diff --git a/client/app/contexts/NearbyUserContext.tsx b/client/app/contexts/NearbyUserContext.tsx new file mode 100644 index 000000000..777e00c2a --- /dev/null +++ b/client/app/contexts/NearbyUserContext.tsx @@ -0,0 +1,39 @@ +import React, { createContext, useContext, useState } from "react"; +import { UserProfile } from "../types/User"; +import { getNearbyUsers } from "@app/services/SocketService"; +import { Socket } from "socket.io-client"; + +const NearbyUsersContext = createContext<{ [uid: string]: UserProfile }>({}); + +export const useNearbyUsers = () => { + return useContext(NearbyUsersContext); +}; + +var setNearbyUsersCallback: + | React.Dispatch< + React.SetStateAction<{ + [uid: string]: UserProfile; + }> + > + | undefined = undefined; +export const refreshNearbyUsers = async (socket: Socket) => { + if (setNearbyUsersCallback === undefined) return; + const nearbyUsers = await getNearbyUsers(socket); + // console.log(nearbyUsers); + setNearbyUsersCallback(nearbyUsers); +}; + +export const NearbyUsersProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [user, setNearbyUsers] = useState<{ [uid: string]: UserProfile }>({}); + setNearbyUsersCallback = setNearbyUsers; + + return ( + + {children} + + ); +}; diff --git a/client/app/contexts/SocketContext.tsx b/client/app/contexts/SocketContext.tsx index fd39e575a..b7b5f1d04 100644 --- a/client/app/contexts/SocketContext.tsx +++ b/client/app/contexts/SocketContext.tsx @@ -1,7 +1,7 @@ import React, { createContext, useContext, useEffect, useState } from "react"; import { Socket } from "socket.io-client"; -import { useLocation } from "./LocationContext"; -import { initializeSocket, getToken, sendLocationUpdate } from "@app/services/SocketService"; +import { forceRefreshLocation, useLocation } from "./LocationContext"; +import { initializeSocket, getToken, updateLocation } from "@app/services/SocketService"; const SocketContext = createContext(null); @@ -33,13 +33,14 @@ export const SocketProvider = ({ children }: { children: React.ReactNode }) => { socket?.disconnect(); console.log("[LOG]: Cleaning up initializeSocket useEffect"); }; - }, [mounted]); + }, [mounted, socket]); useEffect(() => { if (!socket) return; socket.on("connect", () => { console.log("Connected to server"); + forceRefreshLocation(); }); return () => { @@ -54,12 +55,12 @@ export const SocketProvider = ({ children }: { children: React.ReactNode }) => { if ( socket && locationContext && - locationContext?.latitude !== 9999 && - locationContext?.longitude !== 9999 + locationContext?.lat !== 99999 && + locationContext?.lon !== 99999 ) { - sendLocationUpdate(socket, locationContext.latitude, locationContext.longitude); + updateLocation(socket, { lat: locationContext.lat, lon: locationContext.lon }); } - }, [locationContext?.latitude, locationContext?.longitude, socket]); + }, [locationContext, socket]); return ( diff --git a/client/app/contexts/UserContext.tsx b/client/app/contexts/UserContext.tsx deleted file mode 100644 index 26bef57e0..000000000 --- a/client/app/contexts/UserContext.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React, { createContext, useContext, useState } from "react"; -import { UserType } from "../types/User"; -import { initializeUser } from "@app/services/UserService"; - -const UserContext = createContext(null); - -export const useUser = () => { - return useContext(UserContext); -}; - -export const UserProvider = ({ children }: { children: React.ReactNode }) => { - const [user, setUser] = useState(initializeUser); - - return {children}; -}; diff --git a/client/app/index.js b/client/app/index.js index 3e0dc8607..f55f6536f 100644 --- a/client/app/index.js +++ b/client/app/index.js @@ -16,9 +16,7 @@ const App = () => { if (!initialized) return Loading...; - return ( - isLoggedin ? : - ); + return isLoggedin ? : ; }; export default App; diff --git a/client/app/navigation/AppNavigator.tsx b/client/app/navigation/AppNavigator.tsx index d3fc43b5a..f9da0f321 100644 --- a/client/app/navigation/AppNavigator.tsx +++ b/client/app/navigation/AppNavigator.tsx @@ -4,7 +4,7 @@ import * as React from "react"; import { LocationProvider } from "../contexts/LocationContext"; import { SocketProvider } from "../contexts/SocketContext"; -import { UserProvider } from "../contexts/UserContext"; +import { NearbyUsersProvider } from "../contexts/NearbyUserContext"; import ChatScreen from "../screens/chat/ChatScreen"; import SettingsScreen from "../screens/settings/SettingsScreen"; import { Home } from "react-native-feather"; @@ -16,7 +16,7 @@ const AppNavigator = () => { return ( - + { /> - + ); diff --git a/client/app/navigation/AuthNavigator.tsx b/client/app/navigation/AuthNavigator.tsx index 159a687f0..e53b0f703 100644 --- a/client/app/navigation/AuthNavigator.tsx +++ b/client/app/navigation/AuthNavigator.tsx @@ -6,6 +6,7 @@ import SignUpScreen from "../screens/auth/SignUpScreen"; import WelcomeScreen from "../screens/auth/WelcomeScreen"; import EmailVerificationScreen from "../screens/auth/EmailVerificationScreen"; import { NavigationContainer } from "@react-navigation/native"; +import DisplayNameScreen from "@app/screens/auth/DisplayNameScreen"; const Stack = createStackNavigator(); @@ -23,6 +24,7 @@ const AuthNavigator = () => { name="Email Verification" component={EmailVerificationScreen} /> + {/* */} ); diff --git a/client/app/screens/auth/DisplayNameScreen.tsx b/client/app/screens/auth/DisplayNameScreen.tsx new file mode 100644 index 000000000..351df6233 --- /dev/null +++ b/client/app/screens/auth/DisplayNameScreen.tsx @@ -0,0 +1,131 @@ +import React, { useState, useRef } from "react"; +import { + View, + Text, + StyleSheet, + Dimensions, + TouchableWithoutFeedback, + Keyboard, + TextInput, + TouchableOpacity, +} from "react-native"; +import LargeTextButton from "../../components/auth/LargeTextButton"; +import { DisplayNameInput } from "@app/components/common/CustomInputs"; + +const DisplayNameScreen = ({ navigation }: any) => { + const [input, setInput] = useState(""); + const [errorMessage, setErrorMessage] = useState(""); + const hiddenInputRef = useRef(null); + + const handlePress = () => { + hiddenInputRef.current?.focus(); + }; + + const handleChangeText = (text: string) => { + setInput(text); + setErrorMessage(""); + }; + + const handleSubmit = () => { + if (!input || input.length == 0) { + setErrorMessage("Please enter a display name"); + return; + } + navigation.navigate("Home"); + }; + + return ( + + + + Just one more step! + + Pick a display name other people will see + + + + + + + + + + {errorMessage ? ( + {errorMessage} + ) : null} + + + ); +}; + +const styles = StyleSheet.create({ + main_container: { + display: "flex", + height: "100%", + width: "100%", + justifyContent: "flex-start", + alignItems: "center", + paddingHorizontal: Dimensions.get("window").width * 0.11, + paddingVertical: Dimensions.get("window").height * 0.01, + backgroundColor: "white", + gap: Dimensions.get("window").height * 0.029, + }, + input_container: { + display: "flex", + width: "100%", + justifyContent: "center", + alignItems: "center", + }, + boxContainer: { + flexDirection: "row", + justifyContent: "space-between", + width: "80%", + }, + box: { + width: 40, + height: 50, + borderWidth: 1, + borderColor: "#000", + justifyContent: "center", + alignItems: "center", + borderRadius: 5, + }, + boxText: { + fontSize: 24, + fontWeight: "bold", + }, + button_container: { + display: "flex", + justifyContent: "space-around", + alignItems: "center", + width: "100%", + }, + header_container: { + display: "flex", + justifyContent: "center", + alignItems: "flex-start", + width: "100%", + marginBottom: Dimensions.get("window").height * 0.019, + marginTop: Dimensions.get("window").height * 0.17, + }, + header_text: { + fontFamily: "Quicksand-Bold", + fontSize: 37, + marginBottom: Dimensions.get("window").height * 0.01, + }, + subheader_text: { + fontFamily: "Quicksand-Medium", + fontSize: 20, + }, + errorText: { + color: "red", + marginTop: 10, + fontSize: 13, + }, +}); + +export default DisplayNameScreen; diff --git a/client/app/screens/auth/SignUpScreen.tsx b/client/app/screens/auth/SignUpScreen.tsx index 5828434f0..20669d79e 100644 --- a/client/app/screens/auth/SignUpScreen.tsx +++ b/client/app/screens/auth/SignUpScreen.tsx @@ -60,6 +60,7 @@ const SignUpScreen = ({ navigation }: any) => { const handleGoogleSignUp = async () => { console.log("Google Sign Up"); + navigation.navigate("Display Name"); }; const handleFacebookSignIn = async () => { diff --git a/client/app/screens/chat/ChatScreen.tsx b/client/app/screens/chat/ChatScreen.tsx index 7616b7925..578334db3 100644 --- a/client/app/screens/chat/ChatScreen.tsx +++ b/client/app/screens/chat/ChatScreen.tsx @@ -16,9 +16,13 @@ import { useSocket } from "../../contexts/SocketContext"; import { AuthStore } from "../../services/AuthStore"; import { Message } from "../../types/Message"; import { useState, useEffect } from "react"; -import { useUser } from "@app/contexts/UserContext"; -import NearbyHeader from "@app/components/chat/NearbyHeader"; import React from "react"; +import NearbyUserDrawer from "@app/components/chat/NearbyUserDrawer"; +import { sendMessage } from "@app/services/SocketService"; +import { + refreshNearbyUsers, + useNearbyUsers, +} from "@app/contexts/NearbyUserContext"; const ChatScreen = () => { const settings = useSettings(); @@ -26,7 +30,7 @@ const ChatScreen = () => { const keyboardBehavior = Platform.OS === "ios" ? "padding" : undefined; const socket = useSocket(); const location = useLocation(); - const user = useUser(); + const nearbyUsers = useNearbyUsers(); const userAuth = AuthStore.useState(); // Note: To prevent complexity, all user information is grabbed from different contexts and services. If we wanted most information inside of UserContext, we would have to import contexts within contexts and have state change as certain things mount, which could cause errors that are difficult to pinpoint. @@ -37,9 +41,16 @@ const ChatScreen = () => { useEffect(() => { if (socket === null) return; // This line might need to be changed - const handleMessage = (data: any, ack?: any) => { - console.log("Message received from server:", data); - setMessages((prevMessages) => [...prevMessages, data]); + const handleMessage = async (message: Message, ack?: any) => { + console.log("Message received from server:", message); + console.log(nearbyUsers); + if (message.author! in nearbyUsers) { + console.log( + `${message.author} not in nearby users map. Requesting a new map of nearby users...`, + ); + await refreshNearbyUsers(socket); + } + setMessages((prevMessages) => [...prevMessages, message]); if (ack) console.log("Server acknowledged message:", ack); }; @@ -48,35 +59,28 @@ const ChatScreen = () => { return () => { socket.off("message", handleMessage); }; - }, [messages, socket]); + }, [messages, nearbyUsers, socket]); // For when the user sends a message (fired by the send button) const onHandleSubmit = () => { - if (messageContent.trim() !== "") { - const newMessage: Message = { - author: { - uid: String(userAuth.userAuthInfo?.uid), - displayName: String(user?.displayName), - }, - msgId: Crypto.randomUUID(), - msgContent: messageContent.trim(), - timestamp: Date.now(), - lastUpdated: Date.now(), - location: { - lat: Number(location?.latitude), - lon: Number(location?.longitude), - }, - isReply: false, - replyTo: "", - reactions: {}, - }; - - if (socket !== null) { - socket.emit("message", newMessage); - } + if (messageContent.trim() === "") return; + if (socket === null) return; + + const newMessage: Message = { + author: String(userAuth.userAuthInfo?.uid), + //msgId: Crypto.randomUUID(), + timestamp: -1, // timestamp will be overridden by socket server + content: { text: messageContent.trim() }, + location: { + lat: Number(location?.lat), + lon: Number(location?.lon), + }, + replyTo: undefined, + reactions: {}, + }; + sendMessage(socket, newMessage); - setMessageContent(""); - } + setMessageContent(""); }; return ( @@ -91,9 +95,9 @@ const ChatScreen = () => { Platform.OS === "ios" ? screenHeight * 0.055 : 0 }> - + - + { + const socket = useSocket(); const [data, setData] = useState({ displayName: "Display Name", profilePicIndex: 0, // index for icons array @@ -61,14 +46,13 @@ const SettingsScreen: React.FC = () => { deleteMessages: false, }); - const[profileVisible, setProfileVisible] = useState(false); - const[inputVisible, setInputVisible] = useState({ + const [profileVisible, setProfileVisible] = useState(false); + const [inputVisible, setInputVisible] = useState({ displayName: false, profileColor: false, }); - - const iconStyle = [styles.icon, {backgroundColor: data.profileColor}] + const iconStyle = [styles.icon, { backgroundColor: data.profileColor }]; const icons = [ require("../../../assets/icons/user/face_01.png"), @@ -111,83 +95,132 @@ const SettingsScreen: React.FC = () => { }, }, ], - { cancelable: false } + { cancelable: false }, ); }; + const handleProfilePic = async (index: number) => { + if (socket == null) return; + setData({ + ...data, + ["profilePicIndex"]: index, + }); + const profile: UserProfile = { + displayName: data.displayName, + profilePicture: data.profilePicIndex, + }; + await updateActiveUserProfile(socket, profile); + }; + return ( - {/* User Settings Menu */} setProfileVisible(false)} - > - + animationType="fade" + transparent={true} + visible={profileVisible} + onRequestClose={() => setProfileVisible(false)}> setProfileVisible(false)}> - - - - - Hi {data.displayName}! - - setProfileVisible(false)}> - + + + + + Hi {data.displayName}! + + + setProfileVisible(false)}> + - - + /> + + - setInputVisible({...inputVisible, ["displayName"]: value})} - outputSetter={(output: string) => setData({...data, ["displayName"]: output})} - /> - + setInputVisible({ + ...inputVisible, + ["displayName"]: value, + }) + } + outputSetter={(output: string) => + setData({ ...data, ["displayName"]: output }) + } + /> + setInputVisible({...inputVisible, ["profileColor"]: value})} - outputSetter={(output: string) => setData({...data, ["profileColor"]: output})} - /> + visibleSetter={(value: boolean) => + setInputVisible({ + ...inputVisible, + ["profileColor"]: value, + }) + } + outputSetter={(output: string) => + setData({ ...data, ["profileColor"]: output }) + } + /> - {/* User Settings */} - - - Edit Profile - - -