Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context Rework #298

Merged
merged 6 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 29 additions & 44 deletions client/app/contexts/LocationContext.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,19 @@
import { LOCATION_REFRESH_RATE } from "@env";
import * as Location from "expo-location";
import React, { createContext, useContext, useEffect, useState } from "react";
import { getLocation, checkLocationPermission } from "@app/services/LocationService";
import { LocationContextProps, LocationType } from "@app/types/Location";

interface LocationContextProps {
longitude: number;
latitude: number;
isLocationEnabled: boolean;
}

interface LocationType {
longitude: number;
latitude: number;
}

// LocationContext Creation
const LocationContext = createContext<LocationContextProps | null>(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,
}: {
Expand All @@ -37,43 +26,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 (
<LocationContext.Provider
Expand Down
57 changes: 10 additions & 47 deletions client/app/contexts/SettingsContext.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<string>("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 (
Expand Down
61 changes: 19 additions & 42 deletions client/app/contexts/SocketContext.tsx
Original file line number Diff line number Diff line change
@@ -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<Socket | null>(null);

Expand All @@ -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;

Expand All @@ -62,31 +51,19 @@ 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 (
<SocketContext.Provider value={socket}>{children}</SocketContext.Provider>
<SocketContext.Provider value={socket}>
{children}
</SocketContext.Provider>
);
};
17 changes: 3 additions & 14 deletions client/app/contexts/UserContext.tsx
Original file line number Diff line number Diff line change
@@ -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<UserType | null>(null);

Expand All @@ -10,17 +9,7 @@ export const useUser = () => {
};

export const UserProvider = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<UserType>({
displayName: "DefaultDisplayName",
userIcon: {
imagePath: "DefaultImagePath",
colorHex: "#fff",
},
});

useEffect(() => {
user.displayName = generateName()
}, [])
const [user, setUser] = useState<UserType>(initializeUser);

return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};
23 changes: 23 additions & 0 deletions client/app/services/LocationService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as Location from "expo-location";

// Location Service to Handle Location Fetching
export const getLocation = async (): Promise<Location.LocationObject | null> => {
try {
return await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.Balanced,
});
} catch (error) {
console.error("Error fetching location:", error);
return null;
}
};

// Permission Service to Handle Location Permissions
export const checkLocationPermission = async (): Promise<boolean> => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== "granted") {
console.log("Permission to access location was denied");
return false;
}
return true;
};
41 changes: 41 additions & 0 deletions client/app/services/SocketService.ts
Original file line number Diff line number Diff line change
@@ -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<Socket> => {
const socketIo = io(`http://${EXPO_IP}:8080`, {
auth: {
token,
},
});

socketIo.connect();
return socketIo;
};


export const getToken = async (): Promise<string | null> => {
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);
}
);
};
13 changes: 13 additions & 0 deletions client/app/services/UserService.ts
Original file line number Diff line number Diff line change
@@ -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",
},
};
};
Loading
Loading