Initial commit
38
.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Expo
|
||||||
|
.expo/
|
||||||
|
dist/
|
||||||
|
web-build/
|
||||||
|
expo-env.d.ts
|
||||||
|
|
||||||
|
# Native
|
||||||
|
*.orig.*
|
||||||
|
*.jks
|
||||||
|
*.p8
|
||||||
|
*.p12
|
||||||
|
*.key
|
||||||
|
*.mobileprovision
|
||||||
|
|
||||||
|
# Metro
|
||||||
|
.metro-health-check*
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.*
|
||||||
|
yarn-debug.*
|
||||||
|
yarn-error.*
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
.idea
|
50
README.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Welcome to your Expo app 👋
|
||||||
|
|
||||||
|
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
|
||||||
|
|
||||||
|
## Get started
|
||||||
|
|
||||||
|
1. Install dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Start the app
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx expo start
|
||||||
|
```
|
||||||
|
|
||||||
|
In the output, you'll find options to open the app in a
|
||||||
|
|
||||||
|
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
|
||||||
|
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
|
||||||
|
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
|
||||||
|
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
|
||||||
|
|
||||||
|
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
|
||||||
|
|
||||||
|
## Get a fresh project
|
||||||
|
|
||||||
|
When you're ready, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run reset-project
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
|
||||||
|
|
||||||
|
## Learn more
|
||||||
|
|
||||||
|
To learn more about developing your project with Expo, look at the following resources:
|
||||||
|
|
||||||
|
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
|
||||||
|
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
|
||||||
|
|
||||||
|
## Join the community
|
||||||
|
|
||||||
|
Join our community of developers creating universal apps.
|
||||||
|
|
||||||
|
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
|
||||||
|
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
|
41
app.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"expo": {
|
||||||
|
"name": "pogdark_app_rn",
|
||||||
|
"slug": "pogdark_app_rn",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"orientation": "portrait",
|
||||||
|
"icon": "./assets/images/icon.png",
|
||||||
|
"scheme": "myapp",
|
||||||
|
"userInterfaceStyle": "automatic",
|
||||||
|
"newArchEnabled": true,
|
||||||
|
"ios": {
|
||||||
|
"supportsTablet": true
|
||||||
|
},
|
||||||
|
"android": {
|
||||||
|
"adaptiveIcon": {
|
||||||
|
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||||
|
"backgroundColor": "#ffffff"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"bundler": "metro",
|
||||||
|
"output": "static",
|
||||||
|
"favicon": "./assets/images/favicon.png"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"expo-router",
|
||||||
|
[
|
||||||
|
"expo-splash-screen",
|
||||||
|
{
|
||||||
|
"image": "./assets/images/splash-icon.png",
|
||||||
|
"imageWidth": 200,
|
||||||
|
"resizeMode": "contain",
|
||||||
|
"backgroundColor": "#ffffff"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"experiments": {
|
||||||
|
"typedRoutes": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
app/ProfileScreen.tsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
import { Button, TextInput, Dialog, Portal, Avatar, useTheme } from "react-native-paper";
|
||||||
|
import { Asset } from 'expo-asset';
|
||||||
|
import * as FileSystem from 'expo-file-system';
|
||||||
|
import * as ImagePicker from "expo-image-picker";
|
||||||
|
|
||||||
|
interface ProfileScreenProps {
|
||||||
|
visible: boolean;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
setName: (name: string) => void;
|
||||||
|
image: string;
|
||||||
|
setImage: (image: string) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, image, setImage, onClose }) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadDefaultImage = async () => {
|
||||||
|
const asset = Asset.fromModule(require("../assets/images/default_profile_image.png"));
|
||||||
|
await asset.downloadAsync();
|
||||||
|
if (Platform.OS === 'web') {
|
||||||
|
const response = await fetch(asset.uri);
|
||||||
|
const blob = await response.blob();
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => {
|
||||||
|
const base64String = reader.result?.toString().replace(/^data:.+;base64,/, '');
|
||||||
|
resolve(base64String);
|
||||||
|
if (typeof base64String == "string") {
|
||||||
|
setImage(base64String);
|
||||||
|
} else {
|
||||||
|
throw new Error("Failed to load asset.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
});
|
||||||
|
} else if (asset.uri) {
|
||||||
|
const base64 = await FileSystem.readAsStringAsync(asset.uri, { encoding: FileSystem.EncodingType.Base64 });
|
||||||
|
setImage(base64);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadImage = async () => {
|
||||||
|
if (!image) {
|
||||||
|
await loadDefaultImage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadImage().then(r => null);
|
||||||
|
}, [image]);
|
||||||
|
|
||||||
|
const pickImage = async () => {
|
||||||
|
let result = await ImagePicker.launchImageLibraryAsync({ base64: true });
|
||||||
|
if (!result.canceled && result.assets.length > 0) {
|
||||||
|
setImage(result.assets[0].base64 || image);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<Dialog visible={visible} onDismiss={onClose} style={{ backgroundColor: colors.background }}>
|
||||||
|
<Dialog.Title style={{ color: colors.onBackground, textAlign: 'center' }}>Edit Your Profile</Dialog.Title>
|
||||||
|
<Dialog.Content>
|
||||||
|
<Avatar.Image
|
||||||
|
size={100}
|
||||||
|
source={image ? { uri: `data:image/png;base64,${image}` } : require("../assets/images/default_profile_image.png")}
|
||||||
|
style={{ alignSelf: 'center', marginBottom: 15 }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onPress={pickImage}
|
||||||
|
mode="contained"
|
||||||
|
style={{ backgroundColor: colors.primary, marginBottom: 10 }}
|
||||||
|
labelStyle={{ color: colors.onPrimary }}
|
||||||
|
>
|
||||||
|
Change Profile Picture
|
||||||
|
</Button>
|
||||||
|
<TextInput
|
||||||
|
label="Your Pet's Name"
|
||||||
|
mode="outlined"
|
||||||
|
value={name}
|
||||||
|
onChangeText={setName}
|
||||||
|
style={{ marginBottom: 15, backgroundColor: colors.surface }}
|
||||||
|
placeholderTextColor={colors.onSurface}
|
||||||
|
theme={{ colors: { text: colors.onSurface } }}
|
||||||
|
/>
|
||||||
|
</Dialog.Content>
|
||||||
|
<Dialog.Actions>
|
||||||
|
<Button
|
||||||
|
onPress={onClose}
|
||||||
|
mode="contained"
|
||||||
|
style={{ backgroundColor: colors.secondary }}
|
||||||
|
labelStyle={{ color: colors.onPrimary }}>Save</Button>
|
||||||
|
</Dialog.Actions>
|
||||||
|
</Dialog>
|
||||||
|
</Portal>
|
||||||
|
/*
|
||||||
|
<Portal>
|
||||||
|
<Dialog visible={visible} onDismiss={onClose} style={{ backgroundColor: colors.background }}>
|
||||||
|
<Dialog.Title>Edit Your Profile</Dialog.Title>
|
||||||
|
<Dialog.Content>
|
||||||
|
<Avatar.Image
|
||||||
|
size={100}
|
||||||
|
source={image ? { uri: `data:image/png;base64,${image}` } : require("../assets/images/default_profile_image.png")}
|
||||||
|
style={{ alignSelf: 'center', marginBottom: 15 }}
|
||||||
|
/>
|
||||||
|
<Button onPress={pickImage} mode="contained" style={{ backgroundColor: colors.primary, marginBottom: 10 }}>Change Profile Picture</Button>
|
||||||
|
<TextInput
|
||||||
|
label="Your Pet's Name"
|
||||||
|
mode="outlined"
|
||||||
|
value={name}
|
||||||
|
onChangeText={setName}
|
||||||
|
style={{ marginBottom: 15, backgroundColor: colors.surface, color: colors.primary }}
|
||||||
|
/>
|
||||||
|
</Dialog.Content>
|
||||||
|
<Dialog.Actions>
|
||||||
|
<Button onPress={onClose} mode="contained" style={{ backgroundColor: colors.secondary }}>Save</Button>
|
||||||
|
</Dialog.Actions>
|
||||||
|
</Dialog>
|
||||||
|
</Portal>
|
||||||
|
*/
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProfileScreen;
|
194
app/StatusPage.tsx
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
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;
|
41
app/_layout.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
||||||
|
import { useFonts } from 'expo-font';
|
||||||
|
import { Stack } from 'expo-router';
|
||||||
|
import * as SplashScreen from 'expo-splash-screen';
|
||||||
|
import { StatusBar } from 'expo-status-bar';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import 'react-native-reanimated';
|
||||||
|
import { useColorScheme } from 'react-native';
|
||||||
|
import { Provider } from "react-native-paper";
|
||||||
|
|
||||||
|
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||||
|
SplashScreen.preventAutoHideAsync();
|
||||||
|
|
||||||
|
export default function RootLayout() {
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
console.log(colorScheme);
|
||||||
|
const [loaded] = useFonts({
|
||||||
|
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (loaded) {
|
||||||
|
SplashScreen.hideAsync();
|
||||||
|
}
|
||||||
|
}, [loaded]);
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Provider>
|
||||||
|
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||||
|
<StatusBar style={colorScheme === 'dark' ? 'light' : 'dark'} />
|
||||||
|
<Stack>
|
||||||
|
<Stack.Screen name="index" options={{ headerShown: false }} />
|
||||||
|
</Stack>
|
||||||
|
</ThemeProvider>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
82
app/index.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { View, StyleSheet } from "react-native";
|
||||||
|
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 StatusPage from "@/app/StatusPage";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const Index = () => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const [isProfileActive, setProfileActive] = useState(false);
|
||||||
|
const [userId, setUserId] = useState(uuidv4());
|
||||||
|
const [userName, setUserName] = useState("");
|
||||||
|
const [userImage, setUserImage] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadUserData = async () => {
|
||||||
|
try {
|
||||||
|
const storedUserId = await AsyncStorage.getItem("userId");
|
||||||
|
const storedUserName = await AsyncStorage.getItem("userName");
|
||||||
|
const storedUserImage = await AsyncStorage.getItem("userImage");
|
||||||
|
if(storedUserId) {
|
||||||
|
setUserId(storedUserId || uuidv4());
|
||||||
|
setUserName(storedUserName || "");
|
||||||
|
setUserImage(storedUserImage || "");
|
||||||
|
setProfileActive(false);
|
||||||
|
}else{
|
||||||
|
setUserId(uuidv4());
|
||||||
|
setUserName("");
|
||||||
|
setUserImage("");
|
||||||
|
setProfileActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading user data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadUserData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const saveUserData = async () => {
|
||||||
|
try {
|
||||||
|
await AsyncStorage.setItem("userId", userId);
|
||||||
|
await AsyncStorage.setItem("userName", userName);
|
||||||
|
await AsyncStorage.setItem("userImage", userImage);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error saving user data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
saveUserData();
|
||||||
|
}, [userId, userName, userImage]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.container,{ backgroundColor: colors.background }]}>
|
||||||
|
<StatusPage
|
||||||
|
toggleProfile={() => setProfileActive(true)}
|
||||||
|
id={userId}
|
||||||
|
name={userName}
|
||||||
|
image={userImage}
|
||||||
|
isProfileActive={isProfileActive}
|
||||||
|
/>
|
||||||
|
<ProfileScreen
|
||||||
|
visible={isProfileActive}
|
||||||
|
id={userId}
|
||||||
|
name={userName}
|
||||||
|
setName={setUserName}
|
||||||
|
image={userImage}
|
||||||
|
setImage={setUserImage}
|
||||||
|
onClose={() => setProfileActive(false)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: { flex: 1, alignItems: "stretch" },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Index;
|
BIN
assets/fonts/SpaceMono-Regular.ttf
Executable file
BIN
assets/images/Icon-192.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
assets/images/Icon-512.png
Normal file
After Width: | Height: | Size: 323 KiB |
BIN
assets/images/Icon-maskable-192.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
assets/images/Icon-maskable-512.png
Normal file
After Width: | Height: | Size: 387 KiB |
BIN
assets/images/default_profile_image.png
Normal file
After Width: | Height: | Size: 1009 KiB |
BIN
assets/images/favicon.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
assets/images/logo-new.png
Normal file
After Width: | Height: | Size: 341 KiB |
BIN
assets/images/logo-old.png
Normal file
After Width: | Height: | Size: 336 KiB |
BIN
assets/images/logo.png
Normal file
After Width: | Height: | Size: 387 KiB |
BIN
assets/images/pogdark_logo.png
Normal file
After Width: | Height: | Size: 155 KiB |
15580
package-lock.json
generated
Normal file
62
package.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"name": "pogdark_app_rn",
|
||||||
|
"main": "expo-router/entry",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"start": "expo start",
|
||||||
|
"reset-project": "node ./scripts/reset-project.js",
|
||||||
|
"android": "expo start --android",
|
||||||
|
"ios": "expo start --ios",
|
||||||
|
"web": "expo start --web",
|
||||||
|
"test": "jest --watchAll",
|
||||||
|
"lint": "expo lint"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"preset": "jest-expo"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@expo/vector-icons": "^14.0.2",
|
||||||
|
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||||
|
"@react-navigation/native": "^7.0.14",
|
||||||
|
"expo": "~52.0.25",
|
||||||
|
"expo-blur": "~14.0.2",
|
||||||
|
"expo-constants": "~17.0.4",
|
||||||
|
"expo-font": "~13.0.3",
|
||||||
|
"expo-haptics": "~14.0.1",
|
||||||
|
"expo-linking": "~7.0.4",
|
||||||
|
"expo-router": "~4.0.16",
|
||||||
|
"expo-splash-screen": "~0.29.20",
|
||||||
|
"expo-status-bar": "~2.0.1",
|
||||||
|
"expo-symbols": "~0.2.1",
|
||||||
|
"expo-system-ui": "~4.0.7",
|
||||||
|
"expo-web-browser": "~14.0.2",
|
||||||
|
"react": "18.3.1",
|
||||||
|
"react-dom": "18.3.1",
|
||||||
|
"react-native": "0.77.1",
|
||||||
|
"react-native-gesture-handler": "~2.20.2",
|
||||||
|
"react-native-reanimated": "~3.16.1",
|
||||||
|
"react-native-safe-area-context": "4.12.0",
|
||||||
|
"react-native-screens": "~4.4.0",
|
||||||
|
"react-native-web": "~0.19.13",
|
||||||
|
"react-native-webview": "13.12.5",
|
||||||
|
"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",
|
||||||
|
"uuid": "~11.0.5",
|
||||||
|
"react-native-paper": "~5.13.1",
|
||||||
|
"react-native-vector-icons": "~10.2.0",
|
||||||
|
"@expo/metro-runtime": "~4.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.25.2",
|
||||||
|
"@types/jest": "^29.5.12",
|
||||||
|
"@types/react": "~18.3.12",
|
||||||
|
"@types/react-test-renderer": "^18.3.0",
|
||||||
|
"jest": "^29.2.1",
|
||||||
|
"jest-expo": "~52.0.3",
|
||||||
|
"react-test-renderer": "18.3.1",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
},
|
||||||
|
"private": true
|
||||||
|
}
|
17
tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "expo/tsconfig.base",
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".expo/types/**/*.ts",
|
||||||
|
"expo-env.d.ts"
|
||||||
|
]
|
||||||
|
}
|