Color & storage fixes. Readme update
All checks were successful
Build Flutter Web and Docker Image for Local Registry / Build React Native Web App (push) Successful in 10m32s
All checks were successful
Build Flutter Web and Docker Image for Local Registry / Build React Native Web App (push) Successful in 10m32s
This commit is contained in:
parent
4abd732fe8
commit
d4d8d34d1f
@ -13,3 +13,11 @@
|
|||||||
```bash
|
```bash
|
||||||
npm run web
|
npm run web
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Currently we're not looking for contributors.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This work is licensed under [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/)
|
||||||
|
@ -15,7 +15,7 @@ const Nav = ({ toggleProfile }: { toggleProfile: () => void; }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ backgroundColor: theme.colors.background }}>
|
<View style={{ backgroundColor: theme.colors.background }}>
|
||||||
<Appbar.Header style={{ backgroundColor: theme.colors.primary }}>
|
<Appbar.Header style={{ backgroundColor: theme.colors.inversePrimary }}>
|
||||||
<View>
|
<View>
|
||||||
<Menu visible={menuVisible} onDismiss={() => setMenuVisible(false)} anchor={menuAnchor}>
|
<Menu visible={menuVisible} onDismiss={() => setMenuVisible(false)} anchor={menuAnchor}>
|
||||||
<Menu.Item onPress={() => { setMenuVisible(false); setAboutVisible(true);}} title="About Us"/>
|
<Menu.Item onPress={() => { setMenuVisible(false); setAboutVisible(true);}} title="About Us"/>
|
||||||
@ -25,12 +25,12 @@ const Nav = ({ toggleProfile }: { toggleProfile: () => void; }) => {
|
|||||||
setMenuAnchor({ x: event.nativeEvent.pageX, y: event.nativeEvent.pageY + 40 });
|
setMenuAnchor({ x: event.nativeEvent.pageX, y: event.nativeEvent.pageY + 40 });
|
||||||
setMenuVisible(true);
|
setMenuVisible(true);
|
||||||
}}
|
}}
|
||||||
iconColor={theme.colors.inversePrimary} />
|
iconColor={theme.colors.primary} />
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.logoContainer}>
|
<View style={styles.logoContainer}>
|
||||||
<Image source={require("../assets/images/pogdark_logo.png")} style={styles.logo} />
|
<Image source={require("../assets/images/pogdark_logo.png")} style={styles.logo} />
|
||||||
</View>
|
</View>
|
||||||
<Appbar.Action icon="pencil" onPress={toggleProfile} iconColor={ theme.colors.inversePrimary } />
|
<Appbar.Action icon="pencil" onPress={toggleProfile} iconColor={ theme.colors.primary } />
|
||||||
</Appbar.Header>
|
</Appbar.Header>
|
||||||
<Portal>
|
<Portal>
|
||||||
<Dialog visible={aboutVisible} onDismiss={() => setAboutVisible(false)} style={{ backgroundColor: theme.colors.background }}>
|
<Dialog visible={aboutVisible} onDismiss={() => setAboutVisible(false)} style={{ backgroundColor: theme.colors.background }}>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { Button, TextInput, Dialog, Portal, Avatar, useTheme } 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';
|
||||||
@ -12,11 +12,24 @@ interface ProfileScreenProps {
|
|||||||
setName: (name: string) => void;
|
setName: (name: string) => void;
|
||||||
image: string;
|
image: string;
|
||||||
setImage: (image: string) => void;
|
setImage: (image: string) => void;
|
||||||
|
setChanged: (dataChanged: boolean) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, image, setImage, onClose }) => {
|
const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, image, setImage, setChanged, onClose }) => {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
const isNameEmpty = !name.trim();
|
||||||
|
|
||||||
|
// Track the initial values when the component first mounts
|
||||||
|
const [initialName, setInitialName] = useState(name);
|
||||||
|
const [initialImage, setInitialImage] = useState(image);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
setInitialName(name); // Store initial name when the profile opens
|
||||||
|
setInitialImage(image); // Store initial image when the profile opens
|
||||||
|
}
|
||||||
|
}, [visible]); // Reset when the dialog is opened
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadDefaultImage = async () => {
|
const loadDefaultImage = async () => {
|
||||||
@ -45,23 +58,43 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, i
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadImage = async () => {
|
const loadImage = async () => {
|
||||||
if (!image) {
|
if (!image || image === "") {
|
||||||
|
console.log("Loading ", image);
|
||||||
await loadDefaultImage();
|
await loadDefaultImage();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
loadImage().then(r => null);
|
loadImage().then(() => null);
|
||||||
}, [image]);
|
}, [image]);
|
||||||
|
|
||||||
const pickImage = async () => {
|
const pickImage = async () => {
|
||||||
let result = await ImagePicker.launchImageLibraryAsync({ base64: true });
|
let result = await ImagePicker.launchImageLibraryAsync({ base64: true });
|
||||||
if (!result.canceled && result.assets.length > 0) {
|
if (!result.canceled && result.assets.length > 0) {
|
||||||
setImage(result.assets[0].base64 || image);
|
if (result.assets[0].base64 !== image) { // Only update if the image actually changes
|
||||||
|
setImage(result.assets[0].base64 || image);
|
||||||
|
console.log("Picking Image");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
// Check if the name or image has changed
|
||||||
|
const hasChanged = name !== initialName || image !== initialImage;
|
||||||
|
if (hasChanged) {
|
||||||
|
setChanged(true);
|
||||||
|
}
|
||||||
|
onClose(); // Close the profile screen
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<Dialog visible={visible} onDismiss={onClose} style={{ backgroundColor: colors.background }}>
|
<Dialog
|
||||||
|
visible={visible}
|
||||||
|
onDismiss={() => {
|
||||||
|
if (!isNameEmpty) { // Prevent closing if name is empty
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ backgroundColor: colors.background }}>
|
||||||
<Dialog.Title style={{ color: 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
|
||||||
@ -81,7 +114,12 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, i
|
|||||||
label="Your Pet's Name"
|
label="Your Pet's Name"
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
value={name}
|
value={name}
|
||||||
onChangeText={setName}
|
onChangeText={(newName) => {
|
||||||
|
if (newName !== name) { // Only trigger change if it's different
|
||||||
|
setName(newName);
|
||||||
|
console.log("Name change");
|
||||||
|
}
|
||||||
|
}}
|
||||||
style={{ marginBottom: 15, backgroundColor: colors.surface }}
|
style={{ marginBottom: 15, backgroundColor: colors.surface }}
|
||||||
placeholderTextColor={colors.onSurface}
|
placeholderTextColor={colors.onSurface}
|
||||||
theme={{ colors: { text: colors.onSurface } }}
|
theme={{ colors: { text: colors.onSurface } }}
|
||||||
@ -89,38 +127,20 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ visible, name, setName, i
|
|||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
<Dialog.Actions>
|
<Dialog.Actions>
|
||||||
<Button
|
<Button
|
||||||
onPress={onClose}
|
onPress={handleSave}
|
||||||
mode="contained"
|
mode="contained"
|
||||||
style={{ backgroundColor: colors.secondary }}
|
disabled={isNameEmpty} // Disable if name is empty
|
||||||
labelStyle={{ color: colors.onPrimary }}>Save</Button>
|
style={{
|
||||||
|
backgroundColor: isNameEmpty ? colors.tertiary : colors.secondary, // Dim the button
|
||||||
|
opacity: isNameEmpty ? 0.5 : 1, // Visually dim the button
|
||||||
|
}}
|
||||||
|
labelStyle={{ color: colors.onPrimary }}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
</Dialog.Actions>
|
</Dialog.Actions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Portal>
|
</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>
|
|
||||||
*/
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, set
|
|||||||
<View style={styles.listRow}>
|
<View style={styles.listRow}>
|
||||||
<View style={styles.listColumn}>
|
<View style={styles.listColumn}>
|
||||||
<List.Section>
|
<List.Section>
|
||||||
<List.Subheader style={[styles.listSubheader, { color: theme.colors.onSurface }]}>On the Way</List.Subheader>
|
<List.Subheader style={[styles.listSubheader, { color: theme.colors.primary }]}>On the Way</List.Subheader>
|
||||||
{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 => (
|
||||||
@ -207,7 +207,7 @@ const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, set
|
|||||||
</View>
|
</View>
|
||||||
<View style={styles.listColumn}>
|
<View style={styles.listColumn}>
|
||||||
<List.Section>
|
<List.Section>
|
||||||
<List.Subheader style={[styles.listSubheader, { color: theme.colors.onSurface }]}>Arrived</List.Subheader>
|
<List.Subheader style={[styles.listSubheader, { color: theme.colors.primary }]}>Arrived</List.Subheader>
|
||||||
{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 => (
|
||||||
@ -234,9 +234,9 @@ const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, set
|
|||||||
onPress={() => handleStatusPress("On the Way")}
|
onPress={() => handleStatusPress("On the Way")}
|
||||||
style={[
|
style={[
|
||||||
styles.actionButton,
|
styles.actionButton,
|
||||||
{ backgroundColor: currentStatus === "On the Way" ? pulseColorOnTheWay : theme.colors.primary }
|
{ backgroundColor: currentStatus === "On the Way" ? pulseColorOnTheWay : theme.colors.inversePrimary }
|
||||||
]}
|
]}
|
||||||
labelStyle={{ color: theme.colors.onPrimary }}>
|
labelStyle={{ color: currentStatus === "On the Way" ? theme.colors.inversePrimary : theme.colors.primary }}>
|
||||||
{getButtonLabel("On the Way")}
|
{getButtonLabel("On the Way")}
|
||||||
</Button>
|
</Button>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
@ -246,9 +246,9 @@ const StatusPage: React.FC<StatusProps> = ({ id, name, image, currentStatus, set
|
|||||||
onPress={() => handleStatusPress("Arrived")}
|
onPress={() => handleStatusPress("Arrived")}
|
||||||
style={[
|
style={[
|
||||||
styles.actionButton,
|
styles.actionButton,
|
||||||
{ backgroundColor: currentStatus === "Arrived" ? pulseColorArrived : theme.colors.primary }
|
{ backgroundColor: currentStatus === "Arrived" ? pulseColorArrived : theme.colors.inversePrimary }
|
||||||
]}
|
]}
|
||||||
labelStyle={{ color: theme.colors.onPrimary }}>
|
labelStyle={{ color: currentStatus === "Arrived" ? theme.colors.inversePrimary : theme.colors.primary }}>
|
||||||
{getButtonLabel("Arrived")}
|
{getButtonLabel("Arrived")}
|
||||||
</Button>
|
</Button>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {View, StyleSheet } from "react-native";
|
import {View, StyleSheet, Text } from "react-native";
|
||||||
import { useTheme } from "react-native-paper";
|
import { useTheme } from "react-native-paper";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
@ -10,10 +10,12 @@ import Nav from "@/app/Nav";
|
|||||||
const Index = () => {
|
const Index = () => {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
const [isProfileActive, setProfileActive] = useState(false);
|
const [isProfileActive, setProfileActive] = useState(false);
|
||||||
const [userId, setUserId] = useState(uuidv4());
|
const [userId, setUserId] = useState("");
|
||||||
const [userName, setUserName] = useState("");
|
const [userName, setUserName] = useState("");
|
||||||
const [userImage, setUserImage] = useState("");
|
const [userImage, setUserImage] = useState("");
|
||||||
const [userStatus, setUserStatus] = useState("");
|
const [userStatus, setUserStatus] = useState("");
|
||||||
|
const [userDataChanged, setUserDataChanged] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(true); // New loading state
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadUserData = async () => {
|
const loadUserData = async () => {
|
||||||
@ -21,40 +23,56 @@ const Index = () => {
|
|||||||
const storedUserId = await AsyncStorage.getItem("userId");
|
const storedUserId = await AsyncStorage.getItem("userId");
|
||||||
const storedUserName = await AsyncStorage.getItem("userName");
|
const storedUserName = await AsyncStorage.getItem("userName");
|
||||||
const storedUserImage = await AsyncStorage.getItem("userImage");
|
const storedUserImage = await AsyncStorage.getItem("userImage");
|
||||||
if(storedUserId) {
|
|
||||||
|
if (storedUserId) {
|
||||||
setUserId(storedUserId || uuidv4());
|
setUserId(storedUserId || uuidv4());
|
||||||
setUserName(storedUserName || "");
|
setUserName(storedUserName || "");
|
||||||
setUserImage(storedUserImage || "");
|
setUserImage(storedUserImage || "");
|
||||||
setProfileActive(false);
|
setProfileActive(false);
|
||||||
}else{
|
} else {
|
||||||
setUserId(uuidv4());
|
setUserId(uuidv4());
|
||||||
setUserName("");
|
setUserName("");
|
||||||
setUserImage("");
|
setUserImage("");
|
||||||
setProfileActive(true);
|
setProfileActive(true);
|
||||||
}
|
}
|
||||||
|
console.log("Loading data ", userId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading user data:", error);
|
console.error("Error loading user data:", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false); // Mark loading as complete
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
loadUserData();
|
loadUserData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!userDataChanged) return;
|
||||||
|
|
||||||
const saveUserData = async () => {
|
const saveUserData = async () => {
|
||||||
try {
|
try {
|
||||||
|
console.log("Saving data ", userId);
|
||||||
await AsyncStorage.setItem("userId", userId);
|
await AsyncStorage.setItem("userId", userId);
|
||||||
await AsyncStorage.setItem("userName", userName);
|
await AsyncStorage.setItem("userName", userName);
|
||||||
await AsyncStorage.setItem("userImage", userImage);
|
await AsyncStorage.setItem("userImage", userImage);
|
||||||
|
setUserDataChanged(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving user data:", error);
|
console.error("Error saving user data:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
saveUserData();
|
saveUserData();
|
||||||
}, [userId, userName, userImage]);
|
}, [userDataChanged]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
console.log("Still loading");
|
||||||
|
return (
|
||||||
|
<View style={[styles.container, { backgroundColor: colors.background, justifyContent: 'center', alignItems: 'center' }]}>
|
||||||
|
<Text>Loading...</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container,{ backgroundColor: colors.background }]}>
|
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||||
<Nav
|
<Nav
|
||||||
toggleProfile={() => setProfileActive(true)}
|
toggleProfile={() => setProfileActive(true)}
|
||||||
/>
|
/>
|
||||||
@ -73,6 +91,7 @@ const Index = () => {
|
|||||||
setName={setUserName}
|
setName={setUserName}
|
||||||
image={userImage}
|
image={userImage}
|
||||||
setImage={setUserImage}
|
setImage={setUserImage}
|
||||||
|
setChanged={setUserDataChanged}
|
||||||
onClose={() => setProfileActive(false)}
|
onClose={() => setProfileActive(false)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
Loading…
Reference in New Issue
Block a user