Compare commits

..

No commits in common. "be53dc63c166a5cc5cb051da429978659edc445e" and "e891e4def9a80c280b9d6507f6f5e37c315c4810" have entirely different histories.

6 changed files with 126 additions and 281 deletions

View File

@ -1,11 +1,9 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Platform, View, TouchableOpacity, StyleSheet } from "react-native"; import { Platform } from "react-native";
import { Button, TextInput, Dialog, Portal, Avatar, useTheme, Text } from "react-native-paper"; import { Button, TextInput, Dialog, Portal, Avatar, useTheme } from "react-native-paper";
import { Asset } from 'expo-asset'; import { Asset } from 'expo-asset';
import * as FileSystem from 'expo-file-system'; import * as FileSystem from 'expo-file-system';
import * as ImagePicker from "expo-image-picker"; import * as ImagePicker from "expo-image-picker";
import { themes } from '@/app/themes';
import { featureFlags } from '@/featureFlags';
interface ProfileScreenProps { interface ProfileScreenProps {
visible: boolean; visible: boolean;
@ -15,26 +13,21 @@ interface ProfileScreenProps {
image: string; image: string;
setImage: (image: string) => void; setImage: (image: string) => void;
setChanged: (dataChanged: boolean) => void; setChanged: (dataChanged: boolean) => void;
setTheme: (theme: string) => void;
currentTheme: string;
onClose: () => void; onClose: () => void;
} }
const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, image, setImage, setChanged, currentTheme, setTheme, onClose }) => { const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, image, setImage, setChanged, onClose }) => {
const theme = useTheme(); const { colors } = useTheme();
const isNameEmpty = !name.trim(); const isNameEmpty = !name.trim();
const themeColors = ['red', 'blue', 'yellow', 'green', 'orange', 'purple'];
// Track the initial values when the component first mounts // Track the initial values when the component first mounts
const [initialName, setInitialName] = useState(name); const [initialName, setInitialName] = useState(name);
const [initialImage, setInitialImage] = useState(image); const [initialImage, setInitialImage] = useState(image);
const [initialTheme, setInitialTheme] = useState(currentTheme);
useEffect(() => { useEffect(() => {
if (visible) { if (visible) {
setInitialName(name); // Store initial name when the profile opens setInitialName(name); // Store initial name when the profile opens
setInitialImage(image); setInitialImage(image); // Store initial image when the profile opens
setInitialTheme(currentTheme)// Store initial image when the profile opens
} }
}, [visible]); // Reset when the dialog is opened }, [visible]); // Reset when the dialog is opened
@ -85,7 +78,7 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, i
const handleSave = () => { const handleSave = () => {
// Check if the name or image has changed // Check if the name or image has changed
const hasChanged = name !== initialName || image !== initialImage || currentTheme !== initialTheme; const hasChanged = name !== initialName || image !== initialImage;
if (hasChanged) { if (hasChanged) {
setChanged(true); setChanged(true);
} }
@ -101,8 +94,8 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, i
onClose(); onClose();
} }
}} }}
style={{ backgroundColor: theme.colors.background }}> style={{ backgroundColor: colors.background }}>
<Dialog.Title style={{ color: theme.colors.onBackground, textAlign: 'center' }}>Edit Your Profile</Dialog.Title> <Dialog.Title style={{ color: colors.onBackground, textAlign: 'center' }}>Edit Your Profile</Dialog.Title>
<Dialog.Content> <Dialog.Content>
<Avatar.Image <Avatar.Image
size={100} size={100}
@ -112,8 +105,8 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, i
<Button <Button
onPress={pickImage} onPress={pickImage}
mode="contained" mode="contained"
style={{ backgroundColor: theme.colors.primary, marginBottom: 10 }} style={{ backgroundColor: colors.primary, marginBottom: 10 }}
labelStyle={{ color: theme.colors.onPrimary }} labelStyle={{ color: colors.onPrimary }}
> >
Change Profile Picture Change Profile Picture
</Button> </Button>
@ -127,26 +120,10 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, i
console.log("Name change"); console.log("Name change");
} }
}} }}
style={{ marginBottom: 15, backgroundColor: theme.colors.surface }} style={{ marginBottom: 15, backgroundColor: colors.surface }}
placeholderTextColor={theme.colors.onSurface} placeholderTextColor={colors.onSurface}
theme={{ colors: { text: theme.colors.onSurface } }} theme={{ colors: { text: colors.onSurface } }}
/> />
{featureFlags.enableThemeSelection && (
<>
<Text style={{ color: theme.colors.onBackground, fontSize: 18, textAlign: 'center' }}>Choose Theme</Text>
<View style={styles.themeContainer}>
{themeColors.map((userTheme) => (
<TouchableOpacity
key={userTheme}
style={[styles.themeButton, { backgroundColor: themes[userTheme as keyof typeof themes]['light'].colors.primary }]}
onPress={() => {setTheme(userTheme); console.log("Changing Theme: ", userTheme)}}
>
<View style={[styles.halfCircle, { backgroundColor: themes[userTheme as keyof typeof themes]['dark'].colors.primary }]} />
</TouchableOpacity>
))}
</View>
</>
)}
</Dialog.Content> </Dialog.Content>
<Dialog.Actions> <Dialog.Actions>
<Button <Button
@ -154,39 +131,17 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, i
mode="contained" mode="contained"
disabled={isNameEmpty} // Disable if name is empty disabled={isNameEmpty} // Disable if name is empty
style={{ style={{
backgroundColor: theme.colors.primary, backgroundColor: isNameEmpty ? colors.tertiary : colors.secondary, // Dim the button
opacity: isNameEmpty ? 0.5 : 1, // Visually dim the button opacity: isNameEmpty ? 0.5 : 1, // Visually dim the button
}} }}
labelStyle={{ color: theme.colors.onPrimary }}>Save</Button> labelStyle={{ color: colors.onPrimary }}
>
Save
</Button>
</Dialog.Actions> </Dialog.Actions>
</Dialog> </Dialog>
</Portal> </Portal>
); );
}; };
const styles = StyleSheet.create({
themeContainer: {
flexDirection: 'row',
justifyContent: 'space-around',
marginTop: 10,
},
themeButton: {
width: 50,
height: 50,
borderRadius: 25,
justifyContent: 'center',
alignItems: 'flex-start',
overflow: 'hidden',
borderWidth: 1,
borderColor: 'black',
},
halfCircle: {
width: '50%',
height: '100%',
position: 'absolute',
bottom: 0,
},
});
export default ProfileScreen; export default ProfileScreen;

View File

@ -1,9 +1,8 @@
import React, { useEffect, useState, useRef } from "react"; import React, { useEffect, useState, useRef } from "react";
import useWebSocket from "react-use-websocket"; import useWebSocket from "react-use-websocket";
import axios from "axios"; import axios from "axios";
import {Animated, Easing, ImageBackground, StyleSheet, useColorScheme, View} from "react-native"; import { Animated, Easing, ImageBackground, StyleSheet, View } from "react-native";
import { Avatar, List, Button, useTheme, } from "react-native-paper"; import { Avatar, List, Button, useTheme, } from "react-native-paper";
import { themes } from "@/app/themes";
export const API_URL = process.env.EXPO_PUBLIC_API_URL; export const API_URL = process.env.EXPO_PUBLIC_API_URL;
export const WS_URL = process.env.EXPO_PUBLIC_WS_URL; export const WS_URL = process.env.EXPO_PUBLIC_WS_URL;
@ -13,7 +12,6 @@ interface Message {
Name: string; Name: string;
Image: string; Image: string;
Status: string; Status: string;
Theme: string;
Timestamp: string; Timestamp: string;
} }
@ -71,15 +69,13 @@ interface StatusProps {
image: string; image: string;
currentStatus: string; currentStatus: string;
setStatus: (currentStatus: string) => void; setStatus: (currentStatus: string) => void;
currentTheme: string;
isProfileActive: boolean; isProfileActive: boolean;
} }
const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, setStatus, currentTheme, isProfileActive }) => { const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, setStatus, isProfileActive }) => {
//console.log("WebSocket URL: ", WS_URL); //console.log("WebSocket URL: ", WS_URL);
//console.log("API URL: ", API_URL); //console.log("API URL: ", API_URL);
const theme = useTheme(); const theme = useTheme();
const colorScheme = useColorScheme();
const [messages, setMessages] = useState<Message[]>([]); const [messages, setMessages] = useState<Message[]>([]);
const { lastMessage } = useWebSocket(WS_URL + "/ws", { const { lastMessage } = useWebSocket(WS_URL + "/ws", {
shouldReconnect: () => true, shouldReconnect: () => true,
@ -150,7 +146,6 @@ const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, set
Name: name, Name: name,
Image: image, Image: image,
Status: "none", Status: "none",
Theme: currentTheme,
Timestamp: new Date().toISOString() Timestamp: new Date().toISOString()
}; };
await axios.post(API_URL + "/set", message); await axios.post(API_URL + "/set", message);
@ -165,7 +160,6 @@ const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, set
Name: name, Name: name,
Image: image, Image: image,
Status: status, Status: status,
Theme: currentTheme,
Timestamp: new Date().toISOString() Timestamp: new Date().toISOString()
}; };
await axios.post(API_URL + "/set", message); await axios.post(API_URL + "/set", message);
@ -219,14 +213,13 @@ const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, set
{messages.filter(msg => msg.Status === "On the Way") {messages.filter(msg => msg.Status === "On the Way")
.sort((a, b) => new Date(a.Timestamp).getTime() - new Date(b.Timestamp).getTime()) .sort((a, b) => new Date(a.Timestamp).getTime() - new Date(b.Timestamp).getTime())
.map(item => ( .map(item => (
<View key={item.Id} style={[styles.card, <View key={item.Id} style={[styles.card, { backgroundColor: theme.colors.primaryContainer }]}>
{ backgroundColor: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.primaryContainer }]}>
<List.Item <List.Item
key={item.Id} key={item.Id}
title={item.Name} title={item.Name}
titleStyle={{ color: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.onSurface }} titleStyle={{ color: theme.colors.onSurface }}
description={new Date(item.Timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: true })} description={new Date(item.Timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: true })}
descriptionStyle={{ color: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.onSurface }} descriptionStyle={{ color: theme.colors.onSurface }}
left={(props) => <Avatar.Image {...props} size={40} source={{ uri: `data:image/png;base64,${item.Image}` }} />} left={(props) => <Avatar.Image {...props} size={40} source={{ uri: `data:image/png;base64,${item.Image}` }} />}
/> />
</View> </View>
@ -239,14 +232,13 @@ const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, set
{messages.filter(msg => msg.Status === "Arrived") {messages.filter(msg => msg.Status === "Arrived")
.sort((a, b) => new Date(a.Timestamp).getTime() - new Date(b.Timestamp).getTime()) .sort((a, b) => new Date(a.Timestamp).getTime() - new Date(b.Timestamp).getTime())
.map(item => ( .map(item => (
<View key={item.Id} style={[styles.card, <View key={item.Id} style={[styles.card, { backgroundColor: theme.colors.primaryContainer }]}>
{ backgroundColor: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.primaryContainer }]}>
<List.Item <List.Item
key={item.Id} key={item.Id}
title={item.Name} title={item.Name}
titleStyle={{ color: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.onSurface }} titleStyle={{ color: theme.colors.onSurface }}
description={new Date(item.Timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: true })} description={new Date(item.Timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: true })}
descriptionStyle={{ color: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.onSurface }} descriptionStyle={{ color: theme.colors.onSurface }}
left={(props) => <Avatar.Image {...props} size={40} source={{ uri: `data:image/png;base64,${item.Image}` }} />} left={(props) => <Avatar.Image {...props} size={40} source={{ uri: `data:image/png;base64,${item.Image}` }} />}
/> />
</View> </View>

View File

@ -6,27 +6,17 @@ import 'react-native-reanimated';
import { useColorScheme } from 'react-native'; import { useColorScheme } from 'react-native';
import { PaperProvider, Provider } from "react-native-paper"; import { PaperProvider, Provider } from "react-native-paper";
import { themes } from '@/app/themes' import { themes } from '@/app/themes'
import { UserProvider, useUser } from "@/context/UserContext";
// Prevent the splash screen from auto-hiding before asset loading is complete. // Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync(); SplashScreen.preventAutoHideAsync();
export default function RootLayout() { export default function RootLayout() {
return (
<UserProvider>
<InnerRootLayout />
</UserProvider>
);
}
function InnerRootLayout() {
const { currentTheme } = useUser(); // Access the currentTheme from UserContext
console.log(currentTheme);
const colorScheme = useColorScheme(); const colorScheme = useColorScheme();
console.log(colorScheme); console.log(colorScheme);
const [loaded] = useFonts({ const [loaded] = useFonts({
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
}); });
useEffect(() => { useEffect(() => {
if (loaded) { if (loaded) {
SplashScreen.hideAsync(); SplashScreen.hideAsync();
@ -36,18 +26,12 @@ function InnerRootLayout() {
if (!loaded) { if (!loaded) {
return null; return null;
} }
const selectedTheme = 'green';
// Ensure currentTheme is treated as a valid key, or fallback to 'blue' //const [selectedTheme, setSelectedTheme] = useState<'red' | 'blue' | 'yellow' | 'green' | 'orange'>('red');
const themeKey: 'blue' | 'green' | 'red' | 'yellow' | 'orange' = (currentTheme as 'blue' | 'green' | 'red' | 'yellow' | 'orange') || 'blue'; const appTheme = themes[selectedTheme][colorScheme === 'dark' ? 'dark' : 'light'];
// Use the themeKey to index into the themes object
const appTheme = themes[themeKey][colorScheme === 'dark' ? 'dark' : 'light'];
const appTheme2 = themes[currentTheme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light']
return ( return (
<Provider> <Provider>
<PaperProvider theme={appTheme2}> <PaperProvider theme={appTheme} >
<Stack> <Stack>
<Stack.Screen name="index" options={{ headerShown: false }} /> <Stack.Screen name="index" options={{ headerShown: false }} />
</Stack> </Stack>

View File

@ -1,29 +1,106 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import {View, StyleSheet, Text } from "react-native"; import {View, StyleSheet, Text, AppState } from "react-native";
import { useTheme } from "react-native-paper"; import { useTheme } from "react-native-paper";
import { v4 as uuidv4 } from "uuid";
import AsyncStorage from "@react-native-async-storage/async-storage";
import ProfileScreen from "@/app/ProfileScreen"; import ProfileScreen from "@/app/ProfileScreen";
import StatusPage from "@/app/StatusPage"; import StatusPage from "@/app/StatusPage";
import Nav from "@/app/Nav"; import Nav from "@/app/Nav";
import { useUser } from "@/context/UserContext"; import axios from "axios";
export const API_URL = process.env.EXPO_PUBLIC_API_URL;
const Index = () => { const Index = () => {
const theme = useTheme(); const theme = useTheme();
const [isProfileActive, setProfileActive] = useState(false);
const [userId, setUserId] = useState("");
const [userName, setUserName] = useState("");
const [userImage, setUserImage] = useState("");
const [userStatus, setUserStatus] = useState("none");
const [userDataChanged, setUserDataChanged] = useState(false);
const [isLoading, setIsLoading] = useState(true); // New loading state
const [appState, setAppState] = useState(AppState.currentState);
const { useEffect(() => {
isProfileActive, const loadUserData = async () => {
setProfileActive, try {
userId, const storedUserId = await AsyncStorage.getItem("userId");
userName, const storedUserName = await AsyncStorage.getItem("userName");
setUserName, const storedUserImage = await AsyncStorage.getItem("userImage");
userImage, console.log("User Id: ", storedUserId);
setUserImage, if (storedUserId) {
userStatus, setUserId(storedUserId || uuidv4());
setUserStatus, setUserName(storedUserName || "");
setUserDataChanged, setUserImage(storedUserImage || "");
setTheme, setProfileActive(false);
currentTheme, } else {
isLoading, setUserId(uuidv4());
} = useUser(); setUserName("");
setUserImage("");
setProfileActive(true);
}
console.log("Loading data ", userId);
} catch (error) {
console.error("Error loading user data:", error);
} finally {
setIsLoading(false); // Mark loading as complete
}
};
loadUserData();
}, []);
useEffect(() => {
if (!userDataChanged) return;
const saveUserData = async () => {
try {
console.log("Saving data ", userId);
await AsyncStorage.setItem("userId", userId);
await AsyncStorage.setItem("userName", userName);
await AsyncStorage.setItem("userImage", userImage);
setUserDataChanged(false);
} catch (error) {
console.error("Error saving user data:", error);
}
};
saveUserData();
}, [userDataChanged]);
useEffect(() => {
const handleAppStateChange = (nextAppState: string) => {
//console.log("App state", appState);
//console.log("Next App state", nextAppState);
if (appState.match(/inactive|background/) && nextAppState === "active") {
// When the app comes to the foreground, fetch the status
if (!isLoading) {
fetchCurrentStatus().then()
} else {
console.log("Waiting for loading to complete before fetching status...");
}
}
setAppState(AppState.currentState);
};
const listener = AppState.addEventListener("change", handleAppStateChange);
// Cleanup listener on unmount
return () => {
listener.remove();
};
}, [appState]);
const fetchCurrentStatus = async () => {
try {
const response = await axios.post(API_URL + "/get", { id: userId });
console.log("response: ", response);
if (response.data?.status) {
setTimeout(() => {
setUserStatus("none"); // Reset status
}, 0)
}
} catch (error) {
console.error("Error fetching status:", error);
}
};
if (isLoading) { if (isLoading) {
console.log("Still loading"); console.log("Still loading");
@ -45,7 +122,6 @@ const Index = () => {
image={userImage} image={userImage}
currentStatus={userStatus} currentStatus={userStatus}
setStatus={setUserStatus} setStatus={setUserStatus}
currentTheme={currentTheme}
isProfileActive={isProfileActive} isProfileActive={isProfileActive}
/> />
<ProfileScreen <ProfileScreen
@ -55,8 +131,6 @@ const Index = () => {
setName={setUserName} setName={setUserName}
image={userImage} image={userImage}
setImage={setUserImage} setImage={setUserImage}
setTheme={setTheme}
currentTheme={currentTheme}
setChanged={setUserDataChanged} setChanged={setUserDataChanged}
onClose={() => setProfileActive(false)} onClose={() => setProfileActive(false)}
/> />

View File

@ -1,157 +0,0 @@
import React, { createContext, useContext, useEffect, useState, ReactNode } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { AppState } from "react-native";
import { v4 as uuidv4 } from "uuid";
import axios from "axios";
export const API_URL = process.env.EXPO_PUBLIC_API_URL;
// Define context type
interface UserContextType {
isProfileActive: boolean;
setProfileActive: (active: boolean) => void;
userId: string;
userName: string;
setUserName: (name: string) => void;
userImage: string;
setUserImage: (image: string) => void;
userStatus: string;
setUserStatus: (status: string) => void;
setUserDataChanged: (changed: boolean) => void;
isLoading: boolean;
currentTheme: string;
setTheme: (theme: string) => void;
}
// Create context with default values
const UserContext = createContext<UserContextType | undefined>(undefined);
// Define provider props type
interface UserProviderProps {
children: ReactNode;
}
export const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
const [isProfileActive, setProfileActive] = useState(false);
const [userId, setUserId] = useState("");
const [userName, setUserName] = useState("");
const [userImage, setUserImage] = useState("");
const [userStatus, setUserStatus] = useState("none");
const [userDataChanged, setUserDataChanged] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [appState, setAppState] = useState(AppState.currentState);
const [currentTheme, setTheme] = useState("");
useEffect(() => {
const loadUserData = async () => {
try {
const storedUserId = await AsyncStorage.getItem("userId");
const storedUserName = await AsyncStorage.getItem("userName");
const storedUserImage = await AsyncStorage.getItem("userImage");
const storedUserTheme = await AsyncStorage.getItem("theme");
console.log("Stored theme: ", storedUserTheme);
if (storedUserId) {
setUserId(storedUserId);
setUserName(storedUserName || "");
setUserImage(storedUserImage || "");
setTheme(storedUserTheme || "blue");
setProfileActive(false);
} else {
setUserId(uuidv4());
setUserName("");
setUserImage("");
setTheme("blue")
setProfileActive(true);
}
} catch (error) {
console.error("Error loading user data:", error);
} finally {
setIsLoading(false);
}
};
loadUserData();
}, []);
useEffect(() => {
if (!userDataChanged) return;
const saveUserData = async () => {
try {
await AsyncStorage.setItem("userId", userId);
await AsyncStorage.setItem("userName", userName);
await AsyncStorage.setItem("userImage", userImage);
await AsyncStorage.setItem("theme", currentTheme);
console.log("Current theme: ", currentTheme);
setUserDataChanged(false);
} catch (error) {
console.error("Error saving user data:", error);
}
};
saveUserData();
}, [userDataChanged]);
useEffect(() => {
const handleAppStateChange = (nextAppState: string) => {
if (appState.match(/inactive|background/) && nextAppState === "active") {
if (!isLoading) {
fetchCurrentStatus();
} else {
console.log("Waiting for loading to complete before fetching status...");
}
}
setAppState(AppState.currentState);
};
const listener = AppState.addEventListener("change", handleAppStateChange);
return () => {
listener.remove();
};
}, [appState]);
const fetchCurrentStatus = async () => {
try {
const response = await axios.post(API_URL + "/get", { id: userId });
if (response.data?.status) {
setTimeout(() => {
setUserStatus("none");
}, 0);
}
} catch (error) {
console.error("Error fetching status:", error);
}
};
return (
<UserContext.Provider
value={{
isProfileActive,
setProfileActive,
userId,
userName,
setUserName,
userImage,
setUserImage,
userStatus,
setUserStatus,
setUserDataChanged,
isLoading,
currentTheme,
setTheme,
}}
>
{children}
</UserContext.Provider>
);
};
// Custom hook to use context
export const useUser = (): UserContextType => {
const context = useContext(UserContext);
if (!context) {
throw new Error("useUser must be used within a UserProvider");
}
return context;
};

View File

@ -1,3 +0,0 @@
export const featureFlags = {
enableThemeSelection: true, // Toggle this to true or false to enable/disable the feature
};