Compare commits

...

18 Commits

Author SHA1 Message Date
e102d1ccb7 Merge pull request 'location-menu' (#5) from location-menu into master
All checks were successful
Build Flutter Web and Docker Image for Local Registry / Build React Native Web App (push) Successful in 10m33s
Reviewed-on: https://www.kaiser-labs.com/public/pogdark-app/pulls/5
2025-03-30 01:56:39 +00:00
2b4f13cdb7 Fixed theme incorrectly placed 2025-03-28 00:22:22 -04:00
bd8d1fb31e Added broken page to bottomnav 2025-03-27 23:35:47 -04:00
294ee04b34 Fixed location/bottom nav, label in topnav 2025-03-25 23:38:19 -04:00
ebaef46456 Updating workflow 2025-03-23 17:16:56 -04:00
eb1309f163 Cleaning, updating libraries 2025-03-23 01:20:10 -04:00
299bab4e6a Cleaning up promise returns with void 2025-03-23 01:06:26 -04:00
356f604323 Mods to Broken 2025-03-23 01:00:07 -04:00
2ba1bc0658 Added broken.png 2025-03-22 19:25:12 -04:00
77fa3417ef Added broken, modified location and bottomnav 2025-03-22 19:24:34 -04:00
7671fc9787 Added menu item for privacy, stub bug report 2025-03-22 19:23:58 -04:00
bf22da655f Added scrolling lists 2025-03-22 18:05:35 -04:00
6eff2b311f Removing unnecessary Views 2025-03-22 17:06:21 -04:00
c2d173a4ba Cleaning up
Some checks failed
Build Flutter Web and Docker Image for Local Registry / Build React Native Web App (pull_request) Has been cancelled
2025-03-22 13:48:00 -04:00
84a975e89a Refactored Navs, renamed files, added About 2025-03-22 13:46:28 -04:00
e4ee0ba827 Added favicon.png and robots.txt 2025-03-22 13:45:11 -04:00
01ca1efa32 Fixed bottom nav, added layout.web 2025-03-09 22:48:18 -04:00
8178179ac2 Move components out of app, add BottomNav 2025-03-08 17:49:48 -05:00
20 changed files with 413 additions and 117 deletions

View File

@ -4,9 +4,6 @@ on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:

View File

@ -6,11 +6,11 @@ import 'react-native-reanimated';
import { useColorScheme } from 'react-native';
import { PaperProvider, Provider } from "react-native-paper";
import { UserProvider, useUser } from "@/context/UserContext";
import themes from '@/app/themes'
import themes from '@/assets/themes'
import log from "@/util/log"
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
void SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
return (
@ -34,7 +34,7 @@ function InnerRootLayout() {
});
useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
void SplashScreen.hideAsync();
}
}, [loaded]);

62
app/_layout.web.tsx Normal file
View File

@ -0,0 +1,62 @@
import { useFonts } from 'expo-font';
import { Stack } from 'expo-router';
import * as SplashScreen from 'expo-splash-screen';
import { useEffect } from 'react';
import 'react-native-reanimated';
import { useColorScheme } from 'react-native';
import { PaperProvider, Provider } from "react-native-paper";
import { UserProvider, useUser } from "@/context/UserContext";
import themes from '@/assets/themes'
import log from "@/util/log"
// Prevent the splash screen from auto-hiding before asset loading is complete.
void SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
return (
<UserProvider>
<InnerRootLayout />
</UserProvider>
);
}
function InnerRootLayout() {
const { currentTheme } = useUser(); // Access the currentTheme from UserContext
log.debug(currentTheme);
const colorScheme = useColorScheme();
log.debug(colorScheme);
const [loaded] = useFonts({
SpaceReg: require('../assets/fonts/SpaceMono-Regular.ttf'),
SpaceBold: require('../assets/fonts/SpaceMono-Bold.ttf'),
Light: require('../assets/fonts/font-light.otf'),
Medium: require('../assets/fonts/font-medium.otf'),
Heavy: require('../assets/fonts/font-heavy.otf'),
});
useEffect(() => {
if (loaded) {
void SplashScreen.hideAsync();
}
}, [loaded]);
if (!loaded) {
return null;
}
// Ensure currentTheme is treated as a valid key, or fallback to 'blue'
//const themeKey: 'blue' | 'green' | 'red' | 'yellow' | 'orange' = (currentTheme as 'blue' | 'green' | 'red' | 'yellow' | 'orange') || 'blue';
// Use the themeKey to index into the themes object
//const appTheme = themes[themeKey][colorScheme === 'dark' ? 'dark' : 'light'];
const appTheme = themes[currentTheme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light']
return (
<Provider>
<PaperProvider theme={appTheme}>
<Stack>
<Stack.Screen name="index" options={{ headerShown: false }} />
</Stack>
</PaperProvider>
</Provider>
);
}

View File

@ -1,11 +1,12 @@
import React from 'react';
import {View, Text } from "react-native";
import { useTheme } from "react-native-paper";
import ProfileScreen from "@/app/ProfileScreen";
import StatusPage from "@/app/StatusPage";
import Nav from "@/app/Nav";
import Profile from "@/components/Profile";
import Status from "@/components/Status";
import TopNav from "@/components/TopNav";
import BottomNav from "@/components/BottomNav"
import { useUser } from "@/context/UserContext";
import styles from "@/app/styles";
import styles from "@/assets/styles";
import log from "@/util/log"
const Index = () => {
@ -38,10 +39,10 @@ const Index = () => {
return (
<View style={[styles.indexContainer, { backgroundColor: theme.colors.background }]}>
<Nav
<TopNav
toggleProfile={() => setProfileActive(true)}
/>
<StatusPage
<Status
id={userId}
name={userName}
image={userImage}
@ -50,7 +51,7 @@ const Index = () => {
currentTheme={currentTheme}
isProfileActive={isProfileActive}
/>
<ProfileScreen
<Profile
visible={isProfileActive}
id={userId}
name={userName}
@ -62,6 +63,9 @@ const Index = () => {
setChanged={setUserDataChanged}
onClose={() => setProfileActive(false)}
/>
<BottomNav
isProfileActive={isProfileActive}
/>
</View>
);
};

BIN
assets/images/broken.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 KiB

View File

@ -3,7 +3,15 @@ import {StyleSheet} from "react-native";
const styles = StyleSheet.create({
//StatusPage
container: { flex: 1, alignItems: "stretch" },
listContainer: { flex: 1, width: "100%", backgroundColor: 'transparent' },
listContainer: {
flex: 1,
width: "100%",
backgroundColor: 'transparent',
flexDirection: "row",
justifyContent: "space-between",
//alignItems: "flex-start", // Aligns subheaders properly
paddingHorizontal: 10, // Adds some spacing
},
listSubheader: {
fontFamily: "Medium",
fontSize: 18, // Larger text
@ -12,20 +20,16 @@ const styles = StyleSheet.create({
marginBottom: 10, // Add spacing below
zIndex: 0,
},
listWrapper: { flex: 1, padding: 5 },
listRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "flex-start", // Aligns subheaders properly
paddingHorizontal: 10, // Adds some spacing
},
//listWrapper: { flex: 1, padding: 5 },
listColumn: { flex: 1, paddingHorizontal: 5, zIndex: 1},
buttonContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignSelf: "stretch",
verticalAlign: "bottom",
paddingHorizontal: 10,
paddingBottom: 20,
marginHorizontal: 5,
},
actionButton: {
flex: 1,
@ -74,7 +78,9 @@ const styles = StyleSheet.create({
},
//Nav
logoContainer: { flex: 1, alignItems: "center" },
logo: { width: 150, height: 75 },
logo: { width: 140, height: 70 },
topBar: { },
bottomBar: { height: 38 },
//index
indexImageBackground: {
position: "absolute", // Allows child elements to layer on top

47
components/About.tsx Normal file
View File

@ -0,0 +1,47 @@
import * as React from 'react';
import { ScrollView } from 'react-native';
import { Card, Title, Paragraph, Text, Dialog} from 'react-native-paper';
const About = () => {
return (
<Dialog.Content style={{ maxHeight: 300 }}>
<ScrollView style={{ padding: 16 }}>
<Card style={{ marginBottom: 16 }}>
<Card.Content>
<Paragraph style={{ fontSize: 20 }}>
<Text style={{ fontWeight: 'bold' }}>Pogdark</Text> is a dog park communication application that
allows users to inform others when they are on the way or have arrived at a dog park of their choice.
</Paragraph>
</Card.Content>
</Card>
<Card style={{ marginBottom: 16 }}>
<Card.Content>
<Title style={{ fontSize: 20, marginBottom: 8 }}>How It Works</Title>
<Paragraph style={{ fontSize: 16 }}>
1. Users select a simple status:{"\n"}
{" "} "On My Way"{"\n"}
{" "} "Arrived"{"\n\n"}
2. The status is instantly sent via a WebSocket broker to other users who are listening.{"\n\n"}
3. Users can click again to cancel their current status, or status messages will automatically expire, ensuring no long-term data retention.
</Paragraph>
</Card.Content>
</Card>
<Card>
<Card.Content>
<Title style={{ fontSize: 20, marginBottom: 8 }}>Key Features</Title>
<Paragraph style={{ fontSize: 16 }}>
<Text style={{ fontWeight: 'bold' }}>No Tracking, No Storage</Text> The system doesnt store user data or track locations. {"\n\n"}
<Text style={{ fontWeight: 'bold' }}>Instant Updates</Text> Real-time messaging ensures quick communication. {"\n\n"}
<Text style={{ fontWeight: 'bold' }}>No Links or Logins</Text> Users dont need to generate links or sign up. Profiles are stored locally, and created on initial use.{"\n\n"}
<Text style={{ fontWeight: 'bold' }}>Ephemeral by Design</Text> Messages exist only for a 30 (On My Way) or 90 (Arrived) minute lifespan.
</Paragraph>
</Card.Content>
</Card>
</ScrollView>
</Dialog.Content>
);
};
export default About;

48
components/BottomNav.tsx Normal file
View File

@ -0,0 +1,48 @@
import {Appbar, Portal, Button, Dialog, Text, useTheme } from "react-native-paper";
import { View } from "react-native";
import styles from "@/assets/styles";
import React, {useState} from "react";
import Location from "@/components/Location";
import Broken from "@/components/Broken";
interface BNProps {
isProfileActive: boolean;
}
const BottomNav: React.FC<BNProps> = ({ isProfileActive }) => {
const theme = useTheme();
const [menuVisible, setMenuVisible] = useState(false);
return (
<View style={ isProfileActive && { display: 'none' }}>
<View style={{ backgroundColor: theme.colors.background }}>
<Appbar.Header style={[styles.bottomBar, { backgroundColor: theme.colors.primaryContainer }]} >
<View style={{ alignItems: "center", flexDirection: "row", justifyContent: "space-between", padding: 10, flex: 1, paddingHorizontal: 15 }}>
<Text style={{ color: theme.colors.primary, fontFamily: "SpaceReg" }}>Daisy Knight Dog Park</Text>
<Button
mode="text"
onPress={() => setMenuVisible(true) }
style={{ backgroundColor: theme.colors.primary, height: styles.bottomBar.height/2, justifyContent: "center"}}
labelStyle={{ color: theme.colors.onPrimary, fontFamily: "SpaceReg"}}>
Change
</Button>
</View>
</Appbar.Header>
<Portal>
<Dialog visible={menuVisible} onDismiss={() => setMenuVisible(false)} style={{ backgroundColor: theme.colors.primaryContainer, maxHeight: 400 }}>
<Dialog.Title style={{ color: theme.colors.primary, textAlign: 'center' }}>Location</Dialog.Title>
<Broken />
<Dialog.Actions style={{ justifyContent: "center" }}>
<Button onPress={() => setMenuVisible(false)} mode="contained" style={{ backgroundColor: theme.colors.inversePrimary }} labelStyle={{ color: theme.colors.primary }}>
Close
</Button>
</Dialog.Actions>
</Dialog>
</Portal>
</View>
</View>
)
}
export default BottomNav;

21
components/Broken.tsx Normal file
View File

@ -0,0 +1,21 @@
import {Title, useTheme} from "react-native-paper";
import { Image, View } from "react-native";
import React from "react";
const Broken = () => {
const theme = useTheme();
return (
<View>
<Title style={{color: theme.colors.onBackground, fontSize: 16, textAlign: 'center', fontFamily: "Light"}}>The Internet is a Series of Tubes</Title>
<Image
source={require("../assets/images/broken.png")}
style={{ alignSelf: 'center', resizeMode: "contain", height: 400/3 }}
/>
<Title style={{color: theme.colors.onBackground, fontSize: 16, textAlign: 'center', fontFamily: "Light"}}>And these aren't connected. {"\n"} (We're still working on this part.)</Title>
</View>
);
};
export default Broken;

10
components/Location.tsx Normal file
View File

@ -0,0 +1,10 @@
import React from "react";
import Broken from "@/components/Broken";
const Location = () => {
return (
<Broken />
)
}
export default Location;

88
components/Privacy.tsx Normal file
View File

@ -0,0 +1,88 @@
import * as React from 'react';
import { ScrollView } from 'react-native';
import { Card, Title, Paragraph, Text, Dialog } from 'react-native-paper';
const Privacy = () => {
return (
<Dialog.Content style={{ maxHeight: 300 }}>
<ScrollView style={{ padding: 16 }}>
<Card style={{ marginBottom: 16 }}>
<Card.Content>
<Title style={{ fontSize: 20, marginBottom: 8 }}>Privacy Policy</Title>
<Paragraph style={{ fontSize: 16 }}>
<Text style={{ fontStyle: 'italic' }}>Effective Date: 4/1/25</Text>
</Paragraph>
<Paragraph style={{ fontSize: 16 }}>
Thank you for using <Text style={{ fontWeight: 'bold' }}>Pogdark</Text> ("we", "our", or "us"). We are committed to protecting your privacy. This Privacy Policy explains how we handle your information when you use our application.
</Paragraph>
</Card.Content>
</Card>
<Card style={{ marginBottom: 16 }}>
<Card.Content>
<Title style={{ fontSize: 20, marginBottom: 8 }}>1. No Tracking</Title>
<Paragraph style={{ fontSize: 16 }}>
We do <Text style={{ fontWeight: 'bold' }}>not</Text> collect or track any personal information. We do <Text style={{ fontWeight: 'bold' }}>not</Text> use analytics tools, cookies, or any third-party tracking services.
</Paragraph>
</Card.Content>
</Card>
<Card style={{ marginBottom: 16 }}>
<Card.Content>
<Title style={{ fontSize: 20, marginBottom: 8 }}>2. Ephemeral Data Storage</Title>
<Paragraph style={{ fontSize: 16 }}>
Any data entered or generated within the app is stored only <Text style={{ fontWeight: 'bold' }}>temporarily</Text> and exists only for the duration of your session. Once the app is closed or the session ends, all data is deleted automatically. We do not retain any user data on our servers or devices beyond the current session.
</Paragraph>
</Card.Content>
</Card>
<Card style={{ marginBottom: 16 }}>
<Card.Content>
<Title style={{ fontSize: 20, marginBottom: 8 }}>3. No Account or Registration Required</Title>
<Paragraph style={{ fontSize: 16 }}>
You are not required to create an account or provide any personal details (such as name, email address, or phone number) to use the app.
</Paragraph>
</Card.Content>
</Card>
<Card style={{ marginBottom: 16 }}>
<Card.Content>
<Title style={{ fontSize: 20, marginBottom: 8 }}>4. No Data Sharing</Title>
<Paragraph style={{ fontSize: 16 }}>
Since we do not collect data, we do not share any data with third parties.
</Paragraph>
</Card.Content>
</Card>
<Card style={{ marginBottom: 16 }}>
<Card.Content>
<Title style={{ fontSize: 20, marginBottom: 8 }}>5. Security</Title>
<Paragraph style={{ fontSize: 16 }}>
While we dont store personal data, we still take standard precautions to secure ephemeral information during your use of the app.
</Paragraph>
</Card.Content>
</Card>
<Card style={{ marginBottom: 16 }}>
<Card.Content>
<Title style={{ fontSize: 20, marginBottom: 8 }}>6. Changes to This Privacy Policy</Title>
<Paragraph style={{ fontSize: 16 }}>
If we ever make changes to this policy, we will update the date at the top and clearly communicate the changes within the app. However, any future version will always respect your privacy.
</Paragraph>
</Card.Content>
</Card>
<Card>
<Card.Content>
<Title style={{ fontSize: 20, marginBottom: 8 }}>7. Contact</Title>
<Paragraph style={{ fontSize: 16 }}>
If you have any questions or concerns about this policy, feel free to contact us at <Text style={{ fontStyle: 'italic' }}>support@pogdark.com</Text>.
</Paragraph>
</Card.Content>
</Card>
</ScrollView>
</Dialog.Content>
);
};
export default Privacy;

View File

@ -4,8 +4,8 @@ import { Button, TextInput, Dialog, Portal, Avatar, useTheme, Text } from "react
import { Asset } from 'expo-asset';
import * as FileSystem from 'expo-file-system';
import * as ImagePicker from "expo-image-picker";
import themes from '@/app/themes';
import styles from "@/app/styles";
import themes from '@/assets/themes';
import styles from "@/assets/styles";
import log from "@/util/log"
import featureFlags from '@/util/featureFlags';
@ -22,7 +22,7 @@ interface ProfileScreenProps {
onClose: () => void;
}
const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, image, setImage, setChanged, currentTheme, setTheme, onClose }) => {
const Profile: React.FC<ProfileScreenProps> = ({ visible, name, setName, image, setImage, setChanged, currentTheme, setTheme, onClose }) => {
const theme = useTheme();
const isNameEmpty = !name.trim();
const themeColors = ['red', 'blue', 'yellow', 'green', 'orange', 'purple'];
@ -167,4 +167,4 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, i
);
};
export default ProfileScreen;
export default Profile;

View File

@ -1,10 +1,10 @@
import React, { useEffect, useState, useRef } from "react";
import useWebSocket from "react-use-websocket";
import axios from "axios";
import {Animated, Easing, ImageBackground, useColorScheme, View} from "react-native";
import {Animated, Easing, ImageBackground, ScrollView, useColorScheme, View} from "react-native";
import { Avatar, List, Button, useTheme, } from "react-native-paper";
import themes from "@/app/themes";
import styles from "@/app/styles";
import themes from "@/assets/themes";
import styles from "@/assets/styles";
import log from "@/util/log"
export const API_URL = process.env.EXPO_PUBLIC_API_URL;
@ -29,7 +29,7 @@ interface StatusProps {
isProfileActive: boolean;
}
const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, setStatus, currentTheme, isProfileActive }) => {
const Status: React.FC<StatusProps> = ({ id, name, image, currentStatus, setStatus, currentTheme, isProfileActive }) => {
log.debug("WebSocket URL: ", WS_URL);
log.debug("API URL: ", API_URL);
const theme = useTheme();
@ -167,53 +167,46 @@ const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, set
return (
<View style={[styles.container, isProfileActive && { display: 'none' }]}>
<ImageBackground source={require('../assets/images/bg.webp')} style={styles.imageBackground} />
<View style={styles.listContainer}>
<View style={styles.listRow}>
<View style={styles.listColumn}>
<List.Section>
<List.Section style={styles.listColumn}>
<List.Subheader style={[styles.listSubheader, { color: theme.colors.primary }]}>On the Way</List.Subheader>
<ScrollView>
{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: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.primaryContainer }]}>
<List.Item
key={item.Id}
title={item.Name}
titleStyle={{ color: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.primary, fontFamily: "SpaceBold" }}
style={[styles.card, { backgroundColor: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.primaryContainer }]}
description={new Date(item.Timestamp).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', hour12: true })}
descriptionStyle={{ color: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.primary, fontFamily: "SpaceReg" }}
left={(props) => <Avatar.Image {...props} size={50} source={{ uri: `data:image/png;base64,${item.Image}` }} />}
/>
</View>
))}
</ScrollView>
</List.Section>
</View>
<View style={styles.listColumn}>
<List.Section>
<List.Section style={styles.listColumn}>
<List.Subheader style={[styles.listSubheader, { color: theme.colors.primary }]}>Arrived</List.Subheader>
<ScrollView>
{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: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.primaryContainer }]}>
<List.Item
key={item.Id}
title={item.Name}
titleStyle={{ color: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.primary, fontFamily: "SpaceBold" }}
style={[styles.card, { backgroundColor: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.primaryContainer }]}
description={new Date(item.Timestamp).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', hour12: true })}
descriptionStyle={{ color: themes[item.Theme as keyof typeof themes][colorScheme === 'dark' ? 'dark' : 'light'].colors.primary, fontFamily: "SpaceReg" }}
left={(props) => <Avatar.Image {...props} size={50} source={{ uri: `data:image/png;base64,${item.Image}` }} />}
/>
</View>
))}
</ScrollView>
</List.Section>
</View>
</View>
</View>
<ImageBackground source={require('../assets/images/bg.webp')} style={styles.imageBackground} />
<View style={styles.buttonContainer}>
<Animated.View style={{ flex: 1 }}>
<Button
mode="contained"
onPress={() => handleStatusPress("On the Way")}
@ -221,11 +214,9 @@ const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, set
styles.actionButton,
{ backgroundColor: currentStatus === "On the Way" ? pulseColorOnTheWay : theme.colors.primaryContainer }
]}
labelStyle={{ color: theme.colors.primary, fontFamily: "Heavy",}}>
labelStyle={{ color: theme.colors.primary, fontFamily: "Medium", fontSize: 16 }}>
{getButtonLabel("On the Way")}
</Button>
</Animated.View>
<Animated.View style={{ flex: 1 }}>
<Button
mode="contained"
onPress={() => handleStatusPress("Arrived")}
@ -233,13 +224,12 @@ const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, set
styles.actionButton,
{ backgroundColor: currentStatus === "Arrived" ? pulseColorArrived : theme.colors.primaryContainer }
]}
labelStyle={{ color: theme.colors.primary, fontFamily: "Heavy", }}>
labelStyle={{ color: theme.colors.primary, fontFamily: "Medium", fontSize: 16 }}>
{getButtonLabel("Arrived")}
</Button>
</Animated.View>
</View>
</View>
);
};
export default StatusPage;
export default Status;

View File

@ -1,21 +1,28 @@
import {Appbar, Portal, Button, Dialog, Menu, Text, useTheme} from "react-native-paper";
import {Appbar, Portal, Button, Dialog, Menu, useTheme} from "react-native-paper";
import {Image, useColorScheme, View} from "react-native";
import React, {useState} from "react";
import styles from "@/app/styles";
import styles from "@/assets/styles";
import About from "@/components/About";
import Privacy from "@/components/Privacy";
import Broken from "@/components/Broken";
const Nav = ({ toggleProfile }: { toggleProfile: () => void; }) => {
const TopNav = ({ toggleProfile }: { toggleProfile: () => void; }) => {
const theme = useTheme();
const colorScheme = useColorScheme();
const [aboutVisible, setAboutVisible] = useState(false);
const [privacyVisible, setPrivacyVisible] = useState(false);
const [bugVisible, setBugVisible] = useState(false);
const [menuVisible, setMenuVisible] = useState(false);
const [menuAnchor, setMenuAnchor] = useState<{ x: number; y: number } | null>(null);
return (
<View style={{ backgroundColor: theme.colors.background }}>
<Appbar.Header style={{ backgroundColor: theme.colors.primaryContainer }}>
<Appbar.Header style={[styles.topBar, { backgroundColor: theme.colors.primaryContainer }]}>
<View>
<Menu visible={menuVisible} onDismiss={() => setMenuVisible(false)} anchor={menuAnchor} style={{ backgroundColor: theme.colors.primaryContainer }}>
<Menu.Item onPress={() => { setMenuVisible(false); setAboutVisible(true);}} title="About Us" style={{ backgroundColor: theme.colors.primaryContainer }}/>
<Menu.Item onPress={() => { setMenuVisible(false); setPrivacyVisible(true);}} title="Privacy Policy" style={{ backgroundColor: theme.colors.primaryContainer }}/>
<Menu.Item onPress={() => { setMenuVisible(false); setBugVisible(true);}} title="Report a Bug" style={{ backgroundColor: theme.colors.primaryContainer }}/>
</Menu>
<Appbar.Action icon="menu"
onPressIn={(event) => {
@ -35,20 +42,34 @@ const Nav = ({ toggleProfile }: { toggleProfile: () => void; }) => {
<Portal>
<Dialog visible={aboutVisible} onDismiss={() => setAboutVisible(false)} style={{ backgroundColor: theme.colors.primaryContainer }}>
<Dialog.Title style={{ color: theme.colors.primary, textAlign: 'center' }}>About Us</Dialog.Title>
<Dialog.Content>
<Text style={{ color: theme.colors.primary, 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>
<About />
<Dialog.Actions style={{ justifyContent: "center" }}>
<Button onPress={() => setAboutVisible(false)} mode="contained" style={{ backgroundColor: theme.colors.inversePrimary }} labelStyle={{ color: theme.colors.primary }}>
Close
</Button>
</Dialog.Actions>
</Dialog>
<Dialog visible={privacyVisible} onDismiss={() => setPrivacyVisible(false)} style={{ backgroundColor: theme.colors.primaryContainer }}>
<Dialog.Title style={{ color: theme.colors.primary, textAlign: 'center' }}>Privacy Policy</Dialog.Title>
<Privacy />
<Dialog.Actions style={{ justifyContent: "center" }}>
<Button onPress={() => setPrivacyVisible(false)} mode="contained" style={{ backgroundColor: theme.colors.inversePrimary }} labelStyle={{ color: theme.colors.primary }}>
Close
</Button>
</Dialog.Actions>
</Dialog>
<Dialog visible={bugVisible} onDismiss={() => setBugVisible(false)} style={{ backgroundColor: theme.colors.primaryContainer }}>
<Dialog.Title style={{ color: theme.colors.primary, textAlign: 'center' }}>Report A Bug</Dialog.Title>
<Broken />
<Dialog.Actions style={{ justifyContent: "center" }}>
<Button onPress={() => setBugVisible(false)} mode="contained" style={{ backgroundColor: theme.colors.inversePrimary }} labelStyle={{ color: theme.colors.primary }}>
Close
</Button>
</Dialog.Actions>
</Dialog>
</Portal>
</View>
);
};
export default Nav;
export default TopNav;

View File

@ -71,7 +71,7 @@ export const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
}
};
loadUserData();
void loadUserData();
}, []);
useEffect(() => {
@ -90,14 +90,14 @@ export const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
}
};
saveUserData();
void saveUserData();
}, [userDataChanged]);
useEffect(() => {
const handleAppStateChange = (nextAppState: string) => {
if (appState.match(/inactive|background/) && nextAppState === "active") {
if (!isLoading) {
fetchCurrentStatus();
void fetchCurrentStatus();
} else {
log.debug("Waiting for loading to complete before fetching status...");
}

8
package-lock.json generated
View File

@ -13,7 +13,7 @@
"@react-native-async-storage/async-storage": "~2.1.1",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14",
"axios": "~1.7.9",
"axios": "~1.8.4",
"expo": "~52.0.25",
"expo-blur": "~14.0.2",
"expo-constants": "~17.0.4",
@ -5197,9 +5197,9 @@
}
},
"node_modules/axios": {
"version": "1.7.9",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",

View File

@ -43,7 +43,7 @@
"react-use-websocket": "~4.13.0",
"@react-native-async-storage/async-storage": "~2.1.1",
"expo-image-picker": "~16.0.6",
"axios": "~1.7.9",
"axios": "~1.8.4",
"uuid": "~11.0.5",
"react-native-paper": "~5.13.1",
"react-native-vector-icons": "~10.2.0",

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow: /