From 7805f31ce5eb580b5138171c334964d6b9a85a6c Mon Sep 17 00:00:00 2001 From: kalebE36 Date: Mon, 30 Sep 2024 23:07:11 -0400 Subject: [PATCH 1/4] fixed Location context --- client/app/contexts/LocationContext.tsx | 65 +++++++++++------------- client/app/services/LocationService.ts | 13 +++++ client/app/services/PermissionService.ts | 11 ++++ 3 files changed, 54 insertions(+), 35 deletions(-) create mode 100644 client/app/services/LocationService.ts create mode 100644 client/app/services/PermissionService.ts diff --git a/client/app/contexts/LocationContext.tsx b/client/app/contexts/LocationContext.tsx index 4b1f38e7..eb80f923 100644 --- a/client/app/contexts/LocationContext.tsx +++ b/client/app/contexts/LocationContext.tsx @@ -1,7 +1,9 @@ import { LOCATION_REFRESH_RATE } from "@env"; -import * as Location from "expo-location"; import React, { createContext, useContext, useEffect, useState } from "react"; +import { getLocation } from "@app/services/LocationService"; +import { checkLocationPermission } from "@app/services/PermissionService"; +// LocationContext Interfaces interface LocationContextProps { longitude: number; latitude: number; @@ -13,18 +15,15 @@ interface LocationType { latitude: number; } +// LocationContext Creation const LocationContext = createContext(null); -const getLocation = async () => { - return await Location.getCurrentPositionAsync({ - accuracy: Location.Accuracy.Balanced, - }); // Change accuracy while testing. Could become .env variable. -}; - +// Custom Hook for Consuming Location Context export const useLocation = () => { return useContext(LocationContext); }; +// LocationProvider Component to Provide Location Context export const LocationProvider = ({ children, }: { @@ -37,43 +36,39 @@ export const LocationProvider = ({ const [isLocationEnabled, setIsLocationEnabled] = useState(false); useEffect(() => { - // TODO: Refactor this useEffect into a different file (service?) outside of the context, as it is not part of the purpose of a context. - (async () => { - // Request location permissions, if not granted, return - const { status } = await Location.requestForegroundPermissionsAsync(); - if (status !== "granted") { - console.log("Permission to access location was denied"); - return; - } + let interval: NodeJS.Timeout; + + const startLocationTracking = async () => { + const hasPermission = await checkLocationPermission(); // Use permission service + if (!hasPermission) return; setIsLocationEnabled(true); - const interval = setInterval(async () => { - // FIXME: This loop does not stop after refreshing app. Must completely close out and restart app when LOCATION_REFRESH_RATE is changed. - try { - const locationData = await getLocation(); - if ( - locationData.coords.latitude !== location.latitude || - locationData.coords.longitude !== location.longitude - ) { - setLocation({ - latitude: locationData.coords.latitude, - longitude: locationData.coords.longitude, - }); + // Set up the interval once after permission is granted + interval = setInterval(async () => { + const locationData = await getLocation(); + if (locationData && locationData.coords) { + const { latitude, longitude } = locationData.coords; + if (latitude !== location.latitude || longitude !== location.longitude) { + setLocation({ latitude, longitude }); } else { console.log("Location has not changed"); } - } catch (error) { - console.error("Error fetching location:", error); } - }, Number(LOCATION_REFRESH_RATE)); // Fetch location every 3 seconds + }, Number(LOCATION_REFRESH_RATE)); + }; + + startLocationTracking(); - // Cleanup function to clear interval when component unmounts - return () => clearInterval(interval); - })(); + // Cleanup function to clear interval when component unmounts + return () => { + if (interval) { + clearInterval(interval); + console.log("[LOG]: Cleaning up location useEffect"); + } + }; + }, []); - return () => console.log("[LOG]: Cleaning up location useEffect"); - }, []); return ( => { + try { + return await Location.getCurrentPositionAsync({ + accuracy: Location.Accuracy.Balanced, + }); + } catch (error) { + console.error("Error fetching location:", error); + return null; + } +}; diff --git a/client/app/services/PermissionService.ts b/client/app/services/PermissionService.ts new file mode 100644 index 00000000..9cab26be --- /dev/null +++ b/client/app/services/PermissionService.ts @@ -0,0 +1,11 @@ +import * as Location from "expo-location"; + +// Permission Service to Handle Location Permissions +export const checkLocationPermission = async (): Promise => { + const { status } = await Location.requestForegroundPermissionsAsync(); + if (status !== "granted") { + console.log("Permission to access location was denied"); + return false; + } + return true; +}; From 008efa185c46dfc295ded5563eb02a3022151499 Mon Sep 17 00:00:00 2001 From: kalebE36 Date: Wed, 2 Oct 2024 13:08:52 -0400 Subject: [PATCH 2/4] adding seperate services/utils to more modularize LocationContext, SettingsContext, SocketContext --- client/app/contexts/LocationContext.tsx | 11 +---- client/app/contexts/SettingsContext.tsx | 57 +++++-------------------- client/app/contexts/SocketContext.tsx | 57 +++++++------------------ client/app/services/SocketService.ts | 41 ++++++++++++++++++ client/app/types/Location.ts | 10 +++++ client/app/utils/storageUtils.ts | 26 +++++++++++ 6 files changed, 104 insertions(+), 98 deletions(-) create mode 100644 client/app/services/SocketService.ts create mode 100644 client/app/types/Location.ts create mode 100644 client/app/utils/storageUtils.ts diff --git a/client/app/contexts/LocationContext.tsx b/client/app/contexts/LocationContext.tsx index eb80f923..e20429c2 100644 --- a/client/app/contexts/LocationContext.tsx +++ b/client/app/contexts/LocationContext.tsx @@ -2,18 +2,9 @@ import { LOCATION_REFRESH_RATE } from "@env"; import React, { createContext, useContext, useEffect, useState } from "react"; import { getLocation } from "@app/services/LocationService"; import { checkLocationPermission } from "@app/services/PermissionService"; +import { LocationContextProps, LocationType } from "@app/types/Location"; -// LocationContext Interfaces -interface LocationContextProps { - longitude: number; - latitude: number; - isLocationEnabled: boolean; -} -interface LocationType { - longitude: number; - latitude: number; -} // LocationContext Creation const LocationContext = createContext(null); diff --git a/client/app/contexts/SettingsContext.tsx b/client/app/contexts/SettingsContext.tsx index 9e4043aa..7dc37d9b 100644 --- a/client/app/contexts/SettingsContext.tsx +++ b/client/app/contexts/SettingsContext.tsx @@ -1,5 +1,5 @@ -import AsyncStorage from "@react-native-async-storage/async-storage"; import React, { useState, useContext, createContext, useEffect } from "react"; +import { loadTheme, saveTheme } from "@app/utils/storageUtils"; // Import utilities type Settings = { theme: string; @@ -12,65 +12,28 @@ export const useSettings = () => { return useContext(SettingsContext); }; -const loadSettings = async () => { - try { - const themeSetting = await AsyncStorage.getItem("theme"); - if (themeSetting !== null) { - return { - theme: themeSetting, - }; - } else { - await AsyncStorage.setItem("theme", "light"); - return { - theme: "light", - }; - } - } catch (err) { - console.error(err); - } -}; - export const SettingsProvider = ({ children, }: { children: React.ReactNode; }) => { - const [theme, setTheme] = useState("light"); + const [theme, setTheme] = useState("light"); // Initial settings load useEffect(() => { - const loadInitialSettings = async () => { - const settings = await loadSettings(); - if (settings) { - setTheme(settings.theme); - } + const initializeTheme = async () => { + const savedTheme = await loadTheme(); + setTheme(savedTheme); }; - loadInitialSettings(); + initializeTheme(); }, []); - // Setting toggler - const reloadSettings = async () => { - const settings = await loadSettings(); - if (settings) { - setTheme(settings.theme); - } - }; - + // Toggle theme and update AsyncStorage const toggleTheme = async () => { - console.log("Toggling theme"); - try { - const settings = await loadSettings(); - if (settings && settings.theme === "light") { - await AsyncStorage.setItem("theme", "dark"); - } else { - await AsyncStorage.setItem("theme", "light"); - } - } catch (err) { - console.error(err); - } - - await reloadSettings(); + const newTheme = theme === "light" ? "dark" : "light"; + setTheme(newTheme); + await saveTheme(newTheme); // Save the new theme }; return ( diff --git a/client/app/contexts/SocketContext.tsx b/client/app/contexts/SocketContext.tsx index f5c9f5b4..fdcee483 100644 --- a/client/app/contexts/SocketContext.tsx +++ b/client/app/contexts/SocketContext.tsx @@ -1,9 +1,8 @@ -import { EXPO_IP } from "@env"; import React, { createContext, useContext, useEffect, useState } from "react"; -import { io, Socket } from "socket.io-client"; - +import { Socket } from "socket.io-client"; import { useLocation } from "./LocationContext"; -import { AuthStore } from "../services/AuthStore"; +import { initializeSocket, getToken, sendLocationUpdate } from "@app/services/SocketService"; + const SocketContext = createContext(null); @@ -17,35 +16,25 @@ export const SocketProvider = ({ children }: { children: React.ReactNode }) => { const locationContext = useLocation(); useEffect(() => { - const getToken = async () => { - const token = await AuthStore.getRawState().userAuthInfo?.getIdToken(); - console.log("Token:", token); - return token; - }; - - const initializeSocket = async () => { + const initialize = async () => { const token = await getToken(); - const socketIo = io(`http://${EXPO_IP}:8080`, { - auth: { - token, - }, - }); - - socketIo.connect(); - setSocket(socketIo); - setMounted(true); + if (token) { + const socketIo = await initializeSocket(token); + setSocket(socketIo); + setMounted(true); + } }; if (!mounted) { - initializeSocket(); + initialize(); } + return () => { socket?.disconnect(); - console.log("[LOG]: Cleaning up intializeSocket useEffect"); + console.log("[LOG]: Cleaning up initializeSocket useEffect"); }; - }, []); + }, [mounted]); - // Listen to the socket state and run once the socket is set! useEffect(() => { if (!socket) return; @@ -62,29 +51,15 @@ export const SocketProvider = ({ children }: { children: React.ReactNode }) => { }, [socket]); useEffect(() => { - // TODO: Refactor this useEffect into a different file (service?) outside of the context, as it is not part of the purpose of a context. if ( socket && + locationContext && locationContext?.latitude !== 9999 && locationContext?.longitude !== 9999 ) { - console.log( - "Sending location update to server:", - locationContext?.latitude, - locationContext?.longitude, - ); - socket.emit( - "updateLocation", - { - lat: locationContext?.latitude, - lon: locationContext?.longitude, - }, - (ack: string) => { - console.log("Location update ack:", ack); - }, - ); + sendLocationUpdate(socket, locationContext.latitude, locationContext.longitude); } - }, [locationContext?.latitude, locationContext?.longitude]); + }, [locationContext?.latitude, locationContext?.longitude, socket]); return ( {children} diff --git a/client/app/services/SocketService.ts b/client/app/services/SocketService.ts new file mode 100644 index 00000000..60bc4606 --- /dev/null +++ b/client/app/services/SocketService.ts @@ -0,0 +1,41 @@ +import { io, Socket } from "socket.io-client"; +import { AuthStore } from "../services/AuthStore"; +import { EXPO_IP } from "@env"; + +export const initializeSocket = async (token: string): Promise => { + const socketIo = io(`http://${EXPO_IP}:8080`, { + auth: { + token, + }, + }); + + socketIo.connect(); + return socketIo; +}; + + +export const getToken = async (): Promise => { + const token = await AuthStore.getRawState().userAuthInfo?.getIdToken(); + console.log("Token:", token); + return token ?? null; +}; + + + +export const sendLocationUpdate = ( + socket: Socket, + latitude: number, + longitude: number +) => { + console.log("Sending location update to server:", latitude, longitude); + socket.emit( + "updateLocation", + { + lat: latitude, + lon: longitude, + }, + (ack: string) => { + console.log("Location update ack:", ack); + } + ); +}; \ No newline at end of file diff --git a/client/app/types/Location.ts b/client/app/types/Location.ts new file mode 100644 index 00000000..fb384602 --- /dev/null +++ b/client/app/types/Location.ts @@ -0,0 +1,10 @@ +export interface LocationContextProps { + longitude: number; + latitude: number; + isLocationEnabled: boolean; + } + +export interface LocationType { + longitude: number; + latitude: number; + } \ No newline at end of file diff --git a/client/app/utils/storageUtils.ts b/client/app/utils/storageUtils.ts new file mode 100644 index 00000000..6c8e62a3 --- /dev/null +++ b/client/app/utils/storageUtils.ts @@ -0,0 +1,26 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; + +// Utility to load theme from AsyncStorage +export const loadTheme = async (): Promise => { + try { + const themeSetting = await AsyncStorage.getItem("theme"); + if (themeSetting !== null) { + return themeSetting; + } else { + await AsyncStorage.setItem("theme", "light"); + return "light"; + } + } catch (error) { + console.error("Failed to load theme", error); + return "light"; + } +}; + +// Utility to save theme to AsyncStorage +export const saveTheme = async (theme: string): Promise => { + try { + await AsyncStorage.setItem("theme", theme); + } catch (error) { + console.error("Failed to save theme", error); + } +}; From c51a166df8fb4fec546af26ab44963975e62e292 Mon Sep 17 00:00:00 2001 From: kalebE36 Date: Wed, 2 Oct 2024 13:18:37 -0400 Subject: [PATCH 3/4] finished SocetContext Modularization, moved functions from SocketContext into SocketService then called in context --- client/app/contexts/SocketContext.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/app/contexts/SocketContext.tsx b/client/app/contexts/SocketContext.tsx index fdcee483..fd39e575 100644 --- a/client/app/contexts/SocketContext.tsx +++ b/client/app/contexts/SocketContext.tsx @@ -62,6 +62,8 @@ export const SocketProvider = ({ children }: { children: React.ReactNode }) => { }, [locationContext?.latitude, locationContext?.longitude, socket]); return ( - {children} + + {children} + ); }; From 4878316f7feddfdd715cf9d3cb4b970356d4c113 Mon Sep 17 00:00:00 2001 From: kalebE36 Date: Wed, 2 Oct 2024 13:36:27 -0400 Subject: [PATCH 4/4] redid the UserContext, and I merged permission service into Location service to have the Location Context importing from one file --- client/app/contexts/LocationContext.tsx | 3 +-- client/app/contexts/UserContext.tsx | 17 +++-------------- client/app/services/LocationService.ts | 10 ++++++++++ client/app/services/PermissionService.ts | 11 ----------- client/app/services/UserService.ts | 13 +++++++++++++ 5 files changed, 27 insertions(+), 27 deletions(-) delete mode 100644 client/app/services/PermissionService.ts create mode 100644 client/app/services/UserService.ts diff --git a/client/app/contexts/LocationContext.tsx b/client/app/contexts/LocationContext.tsx index e20429c2..00d7659f 100644 --- a/client/app/contexts/LocationContext.tsx +++ b/client/app/contexts/LocationContext.tsx @@ -1,7 +1,6 @@ import { LOCATION_REFRESH_RATE } from "@env"; import React, { createContext, useContext, useEffect, useState } from "react"; -import { getLocation } from "@app/services/LocationService"; -import { checkLocationPermission } from "@app/services/PermissionService"; +import { getLocation, checkLocationPermission } from "@app/services/LocationService"; import { LocationContextProps, LocationType } from "@app/types/Location"; diff --git a/client/app/contexts/UserContext.tsx b/client/app/contexts/UserContext.tsx index 9dcaf92b..26bef57e 100644 --- a/client/app/contexts/UserContext.tsx +++ b/client/app/contexts/UserContext.tsx @@ -1,7 +1,6 @@ -import React, { createContext, useContext, useEffect, useState } from "react"; - +import React, { createContext, useContext, useState } from "react"; import { UserType } from "../types/User"; -import { generateName } from "@app/utils/scripts"; +import { initializeUser } from "@app/services/UserService"; const UserContext = createContext(null); @@ -10,17 +9,7 @@ export const useUser = () => { }; export const UserProvider = ({ children }: { children: React.ReactNode }) => { - const [user, setUser] = useState({ - displayName: "DefaultDisplayName", - userIcon: { - imagePath: "DefaultImagePath", - colorHex: "#fff", - }, - }); - - useEffect(() => { - user.displayName = generateName() - }, []) + const [user, setUser] = useState(initializeUser); return {children}; }; diff --git a/client/app/services/LocationService.ts b/client/app/services/LocationService.ts index 868a0a36..be23242f 100644 --- a/client/app/services/LocationService.ts +++ b/client/app/services/LocationService.ts @@ -11,3 +11,13 @@ export const getLocation = async (): Promise => return null; } }; + +// Permission Service to Handle Location Permissions +export const checkLocationPermission = async (): Promise => { + const { status } = await Location.requestForegroundPermissionsAsync(); + if (status !== "granted") { + console.log("Permission to access location was denied"); + return false; + } + return true; +}; diff --git a/client/app/services/PermissionService.ts b/client/app/services/PermissionService.ts deleted file mode 100644 index 9cab26be..00000000 --- a/client/app/services/PermissionService.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as Location from "expo-location"; - -// Permission Service to Handle Location Permissions -export const checkLocationPermission = async (): Promise => { - const { status } = await Location.requestForegroundPermissionsAsync(); - if (status !== "granted") { - console.log("Permission to access location was denied"); - return false; - } - return true; -}; diff --git a/client/app/services/UserService.ts b/client/app/services/UserService.ts new file mode 100644 index 00000000..4976d8c7 --- /dev/null +++ b/client/app/services/UserService.ts @@ -0,0 +1,13 @@ +import { UserType } from "../types/User"; +import { generateName } from "@app/utils/scripts"; + +// Function to initialize default user +export const initializeUser = (): UserType => { + return { + displayName: generateName(), + userIcon: { + imagePath: "DefaultImagePath", + colorHex: "#fff", + }, + }; +};