195 lines
9.5 KiB
TypeScript
195 lines
9.5 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import useWebSocket from "react-use-websocket";
|
|
import axios from "axios";
|
|
import { Image, StyleSheet, View } from "react-native";
|
|
import { Appbar, Avatar, Button, List, useTheme, Dialog, Portal, Text, Menu } from "react-native-paper";
|
|
|
|
interface Message {
|
|
Id: string;
|
|
Name: string;
|
|
Image: string;
|
|
Status: string;
|
|
Timestamp: string;
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1, alignItems: "stretch" },
|
|
logoContainer: { flex: 1, alignItems: "center" },
|
|
logo: { width: 150, height: 75, resizeMode: "contain" },
|
|
listContainer: { flex: 1, width: "100%" },
|
|
listSubheader: {
|
|
fontSize: 18, // Larger text
|
|
textAlign: "center", // Center the text
|
|
fontWeight: "bold", // Make it more distinct
|
|
marginBottom: 10, // Add spacing below
|
|
},
|
|
listWrapper: { flex: 1, padding: 5 },
|
|
listRow: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
alignItems: "flex-start", // Aligns subheaders properly
|
|
paddingHorizontal: 10, // Adds some spacing
|
|
},
|
|
listColumn: { flex: 1, paddingHorizontal: 5 },
|
|
buttonContainer: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
alignSelf: "stretch",
|
|
paddingHorizontal: 10,
|
|
paddingBottom: 20,
|
|
},
|
|
actionButton: {
|
|
flex: 1,
|
|
marginHorizontal: 5,
|
|
},
|
|
card: {
|
|
marginVertical: 5,
|
|
elevation: 4, // Android shadow
|
|
shadowColor: "#000", // iOS shadow
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.2,
|
|
shadowRadius: 4,
|
|
borderRadius: 10
|
|
},
|
|
});
|
|
|
|
const StatusPage = ({ toggleProfile, id, name, image, isProfileActive }: { toggleProfile: () => void; id: string; name: string; image: string; isProfileActive: boolean }) => {
|
|
const theme = useTheme();
|
|
|
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
const { lastMessage } = useWebSocket("ws://localhost:8080/ws", {
|
|
shouldReconnect: () => true,
|
|
});
|
|
const [aboutVisible, setAboutVisible] = useState(false);
|
|
const [menuVisible, setMenuVisible] = useState(false);
|
|
const [menuAnchor, setMenuAnchor] = useState<{ x: number; y: number } | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (lastMessage?.data) {
|
|
try {
|
|
const newMessage: Message = JSON.parse(lastMessage.data);
|
|
setMessages((prev) => {
|
|
if (newMessage.Status === "removed") {
|
|
return prev.filter((msg) => msg.Id !== newMessage.Id);
|
|
}
|
|
return prev.filter((msg) => msg.Id !== newMessage.Id).concat(newMessage);
|
|
});
|
|
} catch (error) {
|
|
console.error("Error parsing WebSocket message:", error);
|
|
}
|
|
}
|
|
}, [lastMessage]);
|
|
|
|
const sendStatus = async (status: string) => {
|
|
try {
|
|
const message: Message = { Id: id, Name: name, Image: image, Status: status, Timestamp: new Date().toISOString() };
|
|
await axios.post("http://localhost:8080/set", message);
|
|
} catch (error) {
|
|
console.error("Error sending status:", error);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<View style={[styles.container, { backgroundColor: theme.colors.background }, isProfileActive && { display: 'none' }]}>
|
|
<Appbar.Header style={{ backgroundColor: theme.colors.primary }}>
|
|
<View>
|
|
<Menu
|
|
visible={menuVisible}
|
|
onDismiss={() => setMenuVisible(false)}
|
|
anchor={menuAnchor}
|
|
>
|
|
<Menu.Item
|
|
onPress={() => {
|
|
setMenuVisible(false);
|
|
setAboutVisible(true);
|
|
}}
|
|
title="About Us"
|
|
/>
|
|
</Menu>
|
|
<Appbar.Action
|
|
icon="menu"
|
|
onPressIn={(event) => {
|
|
setMenuAnchor({ x: event.nativeEvent.pageX, y: event.nativeEvent.pageY + 40 });
|
|
setMenuVisible(true);
|
|
}}
|
|
iconColor={theme.colors.inversePrimary}
|
|
/>
|
|
</View>
|
|
<View style={styles.logoContainer}>
|
|
<Image source={require("../assets/images/pogdark_logo.png")} style={styles.logo} />
|
|
</View>
|
|
<Appbar.Action icon="pencil" onPress={toggleProfile} iconColor={ theme.colors.inversePrimary } />
|
|
</Appbar.Header>
|
|
<View style={[styles.listContainer, { backgroundColor: theme.colors.surface }]}>
|
|
<View style={styles.listRow}>
|
|
<View style={styles.listColumn}>
|
|
<List.Section>
|
|
<List.Subheader style={[styles.listSubheader, { color: theme.colors.onSurface }]}>On the Way</List.Subheader>
|
|
{messages.filter(msg => msg.Status === "On the Way")
|
|
.sort((a, b) => new Date(a.Timestamp).getTime() - new Date(b.Timestamp).getTime())
|
|
.map(item => (
|
|
<View key={item.Id} style={[styles.card, { backgroundColor: theme.colors.primaryContainer }]}>
|
|
<List.Item
|
|
key={item.Id}
|
|
title={item.Name}
|
|
titleStyle={{ color: theme.colors.onSurface }}
|
|
description={new Date(item.Timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: true })}
|
|
descriptionStyle={{ color: theme.colors.onSurface }}
|
|
left={(props) => <Avatar.Image {...props} size={40} source={{ uri: `data:image/png;base64,${item.Image}` }} />}
|
|
/>
|
|
</View>
|
|
))}
|
|
</List.Section>
|
|
</View>
|
|
<View style={styles.listColumn}>
|
|
<List.Section>
|
|
<List.Subheader style={[styles.listSubheader, { color: theme.colors.onSurface }]}>Arrived</List.Subheader>
|
|
{messages.filter(msg => msg.Status === "Arrived")
|
|
.sort((a, b) => new Date(a.Timestamp).getTime() - new Date(b.Timestamp).getTime())
|
|
.map(item => (
|
|
<View key={item.Id} style={[styles.card, { backgroundColor: theme.colors.primaryContainer }]}>
|
|
<List.Item
|
|
key={item.Id}
|
|
title={item.Name}
|
|
titleStyle={{ color: theme.colors.onSurface }}
|
|
description={new Date(item.Timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: true })}
|
|
descriptionStyle={{ color: theme.colors.onSurface }}
|
|
left={(props) => <Avatar.Image {...props} size={40} source={{ uri: `data:image/png;base64,${item.Image}` }} />}
|
|
/>
|
|
</View>
|
|
))}
|
|
</List.Section>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
<View style={styles.buttonContainer}>
|
|
<Button mode="contained" onPress={() => sendStatus("On the Way")}
|
|
style={[styles.actionButton, { backgroundColor: theme.colors.primary }]}
|
|
labelStyle={{ color: theme.colors.onPrimary }}>On the Way</Button>
|
|
<Button mode="contained" onPress={() => sendStatus("Arrived")}
|
|
style={[styles.actionButton, { backgroundColor: theme.colors.primary }]}
|
|
labelStyle={{ color: theme.colors.onPrimary }} >Arrived</Button>
|
|
</View>
|
|
<Portal>
|
|
<Dialog visible={aboutVisible} onDismiss={() => setAboutVisible(false)} style={{ backgroundColor: theme.colors.background }}>
|
|
<Dialog.Title style={{ color: theme.colors.onBackground, textAlign: 'center' }}>About Us</Dialog.Title>
|
|
<Dialog.Content>
|
|
<Text style={{ color: theme.colors.onSurface, textAlign: 'justify' }}>
|
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam auctor, nisl nec laoreet luctus, felis sapien facilisis augue, at tristique enim turpis nec turpis. Donec convallis justo vel mi consectetur, at pulvinar justo dictum. Mauris vulputate dapibus neque, sed sagittis libero dapibus eu. Integer nec mi at quam cursus suscipit sed et tortor.
|
|
</Text>
|
|
</Dialog.Content>
|
|
<Dialog.Actions style={{ justifyContent: "center" }}>
|
|
<Button onPress={() => setAboutVisible(false)} mode="contained" style={{ backgroundColor: theme.colors.secondary }} labelStyle={{ color: theme.colors.onPrimary }}>
|
|
Close
|
|
</Button>
|
|
</Dialog.Actions>
|
|
</Dialog>
|
|
</Portal>
|
|
</View>
|
|
|
|
|
|
);
|
|
};
|
|
|
|
export default StatusPage;
|