diff options
| -rw-r--r-- | app.json | 14 | ||||
| -rw-r--r-- | app/(tabs)/index.tsx | 411 | ||||
| -rw-r--r-- | package-lock.json | 72 | ||||
| -rw-r--r-- | package.json | 7 | ||||
| -rw-r--r-- | pnpm-lock.yaml | 117 | 
5 files changed, 434 insertions, 187 deletions
| @@ -13,7 +13,12 @@        "backgroundColor": "#ffffff"      },      "ios": { -      "supportsTablet": true +      "supportsTablet": true, +      "infoPlist": { +        "NSLocationWhenInUseUsageDescription": "This app needs access to your location.", +        "NSLocationAlwaysUsageDescription": "This app needs access to your location even when in the background.", +        "UIBackgroundModes": ["location"] +      }      },      "android": {        "adaptiveIcon": { @@ -21,7 +26,12 @@          "backgroundColor": "#ffffff"        },        "googleServicesFile": "./google-services.json", -      "package": "com.unibo.cas4" +      "package": "com.unibo.cas4", +      "permissions": [ +        "ACCESS_FINE_LOCATION", +        "ACCESS_COARSE_LOCATION", +        "ACCESS_BACKGROUND_LOCATION" +      ]      },      "web": {        "bundler": "metro", diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 237a032..b5bddcc 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -1,14 +1,38 @@ -import { Alert, Platform, Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; -import ParallaxScrollView from '@/components/ParallaxScrollView'; -import { ThemedText } from '@/components/ThemedText'; -import { ThemedView } from '@/components/ThemedView'; -import React, { useState, useEffect, useRef } from 'react'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import MapView, { Marker } from 'react-native-maps'; -import * as Notifications from 'expo-notifications'; -import * as Device from 'expo-device'; -import Constants from 'expo-constants'; -import { Link } from 'expo-router'; +import { +  Alert, +  Platform, +  Pressable, +  StyleSheet, +  Text, +  TextInput, +  View, +} from "react-native"; +import ParallaxScrollView from "@/components/ParallaxScrollView"; +import { ThemedText } from "@/components/ThemedText"; +import { ThemedView } from "@/components/ThemedView"; +import React, { useState, useEffect, useRef } from "react"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import MapView, { LatLng, Marker } from "react-native-maps"; +import * as Notifications from "expo-notifications"; +import * as Device from "expo-device"; +import * as Location from "expo-location"; +import Constants from "expo-constants"; +import { Link } from "expo-router"; +import * as TaskManager from "expo-task-manager"; + +const LOCATION_TASK_NAME = "background-location-task"; + +TaskManager.defineTask(LOCATION_TASK_NAME, async ({ data, error }) => { +  if (error) { +    console.error(error); +    return; +  } +  if (data) { +    const { locations } = data; +    console.log("Received new locations:", locations); +    // Process the locations here +  } +});  interface NotificationData {    id: string; @@ -23,36 +47,39 @@ Notifications.setNotificationHandler({    }),  }); -  function handleRegistrationError(errorMessage: string) {    Alert.alert("Error registering this device", errorMessage);  }  async function registerForPushNotificationsAsync() { -  if (Platform.OS === 'android') { -    Notifications.setNotificationChannelAsync('default', { -      name: 'default', +  if (Platform.OS === "android") { +    Notifications.setNotificationChannelAsync("default", { +      name: "default",        importance: Notifications.AndroidImportance.MAX,        vibrationPattern: [0, 250, 250, 250], -      lightColor: '#007AFF', +      lightColor: "#007AFF",      });    }    if (Device.isDevice) { -    const { status: existingStatus } = await Notifications.getPermissionsAsync(); +    const { status: existingStatus } = +      await Notifications.getPermissionsAsync();      let finalStatus = existingStatus; -    if (existingStatus !== 'granted') { +    if (existingStatus !== "granted") {        const { status } = await Notifications.requestPermissionsAsync();        finalStatus = status;      } -    if (finalStatus !== 'granted') { -      handleRegistrationError('Permission not granted to get push token for push notification!'); +    if (finalStatus !== "granted") { +      handleRegistrationError( +        "Permission not granted to get push token for push notification!", +      );        return;      }      const projectId = -      Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.easConfig?.projectId; +      Constants?.expoConfig?.extra?.eas?.projectId ?? +      Constants?.easConfig?.projectId;      if (!projectId) { -      handleRegistrationError('Project ID not found'); +      handleRegistrationError("Project ID not found");      }      try {        const pushTokenString = ( @@ -65,71 +92,84 @@ async function registerForPushNotificationsAsync() {        handleRegistrationError(`${e}`);      }    } else { -    handleRegistrationError('Must use physical device for push notifications'); +    handleRegistrationError("Must use physical device for push notifications");    }  }  export default function HomeScreen() { -  const [email, setEmail] = useState(''); -  const [password, setPassword] = useState(''); -  const [token, setToken] = useState(''); -  const [userId, setUserId] = useState(''); -  const [coordinates, setCoordinates] = useState({ latitude: 44.49738301084014, longitude: 11.356121722966094 }); -  const [region, setRegion] = useState({ latitude: 44.49738301084014, longitude: 11.356121722966094, latitudeDelta: 0.03, longitudeDelta: 0.03 }); -  const [notification, setNotification] = useState<NotificationData|null>(null); +  const [email, setEmail] = useState(""); +  const [password, setPassword] = useState(""); +  const [token, setToken] = useState(""); +  const [userId, setUserId] = useState(""); +  const [coordinates, setCoordinates] = useState({ +    latitude: 0, +    longitude: 0, +  }); +  const [region, setRegion] = useState({ +    latitude: 0, +    longitude: 0, +    latitudeDelta: 0.03, +    longitudeDelta: 0.03, +  }); +  const [notification, setNotification] = useState<NotificationData | null>( +    null, +  );    const mapRef = useRef(null);    const storeToken = async (token: string) => { -    if (Platform.OS === 'web') { -      localStorage.setItem('token', token); +    if (Platform.OS === "web") { +      localStorage.setItem("token", token);      } else { -      await AsyncStorage.setItem('token', token); +      await AsyncStorage.setItem("token", token);      }    };    const storeUserId = async (userId: string) => { -    if (Platform.OS === 'web') { -      localStorage.setItem('userId', userId); +    if (Platform.OS === "web") { +      localStorage.setItem("userId", userId);      } else { -      await AsyncStorage.setItem('userId', userId); +      await AsyncStorage.setItem("userId", userId);      }    };    const handleLogin = async () => {      if (!email || !password) { -      Alert.alert('Error', 'Email and password are required.'); +      Alert.alert("Error", "Email and password are required.");        return;      }      try { -      const response = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/graphql`, { -        method: 'POST', -        headers: { -          'Content-Type': 'application/json', -        }, -        body: JSON.stringify({ -          query: ` -            mutation Login($input: LoginCredentials!) {  -              login(input: $input) {  -                accessToken  -                tokenType  +      const response = await fetch( +        `${process.env.EXPO_PUBLIC_API_URL}/graphql`, +        { +          method: "POST", +          headers: { +            "Content-Type": "application/json", +          }, +          body: JSON.stringify({ +            query: ` +            mutation Login($input: LoginCredentials!) { +              login(input: $input) { +                accessToken +                tokenType                  userId -              }  +              }              }`, -          variables: { -            input: { -              email: email, -              password, +            variables: { +              input: { +                email: email, +                password, +              },              }, -          }, -        }), -      }); +          }), +        }, +      );        const data = await response.json();        if (data.errors) {          const errorMessages = data.errors.map((error: any) => error.message); -        Alert.alert('Error', errorMessages.join('\n')); +        Alert.alert("Error", errorMessages.join("\n"));        } else {          const { accessToken, userId } = data.data.login;          await storeToken(accessToken); @@ -138,40 +178,40 @@ export default function HomeScreen() {          setUserId(String(userId));          registerForPushNotificationsAsync() -          .then(async notificationToken => { +          .then(async (notificationToken) => {              if (!notificationToken) return;              const regex = /ExponentPushToken\[(.*?)\]/;              const match = notificationToken.match(regex);              if (match && match[1]) { -                notificationToken = match[1]; +              notificationToken = match[1];              } -              await fetch(`${process.env.EXPO_PUBLIC_API_URL}/graphql`, { -                method: 'POST', -                headers: { -                  'Authorization': `Bearer ${accessToken}`, -                  'Content-Type': 'application/json', -                }, -                body: JSON.stringify({ -                  query: ` +            await fetch(`${process.env.EXPO_PUBLIC_API_URL}/graphql`, { +              method: "POST", +              headers: { +                Authorization: `Bearer ${accessToken}`, +                "Content-Type": "application/json", +              }, +              body: JSON.stringify({ +                query: `                      mutation RegisterDevice($input: RegisterNotificationToken!) {                        registerDevice(input: $input) { id name email }                      }                    `, -                  variables: { -                    input: { -                      token: notificationToken, -                    }, +                variables: { +                  input: { +                    token: notificationToken,                    }, -                }), -              }) +                }, +              }), +            });            })            .catch((error: any) => alert(`${error}`));        }      } catch (err) { -      console.error('Login Error:', err); -      Alert.alert('Error', 'An error occurred during login.'); +      console.error("Login Error:", err); +      Alert.alert("Error", "An error occurred during login.");      }    }; @@ -180,53 +220,74 @@ export default function HomeScreen() {    };    const removeToken = async () => { -    if (Platform.OS === 'web') { -      localStorage.removeItem('token'); -      localStorage.removeItem('userId'); +    if (Platform.OS === "web") { +      localStorage.removeItem("token"); +      localStorage.removeItem("userId");      } else { -      await AsyncStorage.removeItem('token'); -      await AsyncStorage.removeItem('userId'); +      await AsyncStorage.removeItem("token"); +      await AsyncStorage.removeItem("userId");      } -    setToken(''); -    setUserId(''); +    setToken(""); +    setUserId(""); +  }; + +  const formatDate = (timestamp: string) => { +    const date = new Date(parseInt(timestamp) * 1000); +    return `${date.toDateString()} ${date.getHours()}:${(date.getMinutes() < 10 ? "0" : "") + date.getMinutes()}`;    }; -  const fetchMapData = async () => { +  const updateLocation = async (coords: LatLng) => { +    setCoordinates({ +      latitude: coords.latitude, +      longitude: coords.longitude, +    }); + +    if (region.longitude == 0 && region.latitude == 0) { +    setRegion({ +      latitude: coords.latitude, +      longitude: coords.longitude, +      latitudeDelta: 0.03, +      longitudeDelta: 0.03, +    }); +    } + +      if (!token || !userId) return;      try { -      const response = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/graphql`, { -        method: 'POST', -        headers: { -          'Authorization': `Bearer ${token}`, -          'Content-Type': 'application/json', +      const response = await fetch( +        `${process.env.EXPO_PUBLIC_API_URL}/graphql`, +        { +          method: "POST", +          headers: { +            Authorization: `Bearer ${token}`, +            "Content-Type": "application/json", +          }, +          body: JSON.stringify({ +            query: ` +            mutation NewPosition($input: PositionInput!) { +              newPosition(input: $input) { +                id userId createdAt latitude longitude movingActivity +              } +            } +            `, +            variables: { +              input: { +                latitude: coords.latitude, +                longitude: coords.longitude, +                movingActivity: 'STILL', +              }, +            }, +          }),          }, -        body: JSON.stringify({ -          query: `{ positions(userId: ${userId}) { id, userId, createdAt, latitude, longitude } }`, -        }), -      }); - +      );        const data = await response.json(); - -      if (data.data.positions && data.data.positions.length > 0) { -        const position = data.data.positions[0]; -        setCoordinates({ latitude: position.latitude, longitude: position.longitude }); -        setRegion({ -          latitude: position.latitude, -          longitude: position.longitude, -          latitudeDelta: 0.03, -          longitudeDelta: 0.03, -        }); -      } +      console.log(data)      } catch (err) { -      console.error('Fetch Map Data Error:', err); +      console.error("Error on updating position");       } -  }; -  const formatDate = (timestamp: string) => { -    const date = new Date(parseInt(timestamp) * 1000); -    return `${date.toDateString()} ${date.getHours()}:${(date.getMinutes() < 10 ? '0' : '') + date.getMinutes()}`; -  }; +  }    useEffect(() => {      const fetchNotifications = async () => { @@ -236,15 +297,15 @@ export default function HomeScreen() {          const response = await fetch(            `${process.env.EXPO_PUBLIC_API_URL}/graphql`,            { -            method: 'POST', +            method: "POST",              headers: {                Authorization: `Bearer ${token}`, -              'Content-Type': 'application/json', +              "Content-Type": "application/json",              },              body: JSON.stringify({                query: `{ notifications(seen: false) { id, createdAt } }`,              }), -          } +          },          );          const data = await response.json(); @@ -253,45 +314,75 @@ export default function HomeScreen() {            setNotification(data.data.notifications[0]);          }        } catch (err) { -        console.error('Fetch notifications:', err); +        console.error("Fetch notifications:", err);        }      };      if (token && userId) { -      const intervalId = setInterval(fetchNotifications, 2000); +      const intervalId = setInterval(fetchNotifications, 10000);        return () => clearInterval(intervalId);      } else { -      setNotification(''); +      setNotification("");      }    }, [token, userId]); -    useEffect(() => {      const retrieveToken = async () => { -      const storedToken = Platform.OS === 'web' ? localStorage.getItem('token') : await AsyncStorage.getItem('token'); -      setToken(storedToken || ''); -      const storedUserId = Platform.OS === 'web' ? localStorage.getItem('userId') : await AsyncStorage.getItem('userId'); -      setUserId(storedUserId || ''); +      const storedToken = +        Platform.OS === "web" +          ? localStorage.getItem("token") +          : await AsyncStorage.getItem("token"); +      setToken(storedToken || ""); +      const storedUserId = +        Platform.OS === "web" +          ? localStorage.getItem("userId") +          : await AsyncStorage.getItem("userId"); +      setUserId(storedUserId || "");      };      retrieveToken();    }, []);    useEffect(() => { -    if (token && userId) { -      const intervalId = setInterval(fetchMapData, 100000); - -      return () => clearInterval(intervalId); -    } -  }, [token, userId]); - -  useEffect(() => {      if (mapRef.current && region) {        mapRef.current.animateToRegion(region, 1000);      }    }, [region]); +  useEffect(() => { +    const startBackgroundLocationTracking = async () => { +      try { +        const { status } = await Location.requestForegroundPermissionsAsync(); +        if (status === "granted") { +          setInterval(async () => { +            const location = await Location.getCurrentPositionAsync({}); +            updateLocation(location.coords); +          }, 2000); + +          await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, { +            accuracy: Location.Accuracy.Balanced, +            distanceInterval: 0, +            deferredUpdatesInterval: 1000, +            showsBackgroundLocationIndicator: true, +            foregroundService: { +              notificationTitle: "CAS4 Location Tracking", +              notificationBody: +                "Your location is being tracked in the background.", +              notificationColor: "#FFFFFF", +            }, +          }); +        } else { +          Alert.alert("Background location permission not granted"); +        } +      } catch (error) { +        console.error("Error starting background location updates:", error); +      } +    }; + +    startBackgroundLocationTracking(); +  }, []); +    return (      <ParallaxScrollView token={token} userId={userId}>        {token && userId ? ( @@ -300,29 +391,31 @@ export default function HomeScreen() {              <View>                <Link                  href={`/notifications/${notification.id}`} -                style={{ width: '100%' }} +                style={{ width: "100%" }}                > -              <View style={styles.notificationBox}> -                <Text style={styles.notificationBoxText}>Oh no, you are (or have been) in an alerted area in {formatDate(notification.createdAt)}!</Text> -                <Text style={styles.notificationBoxText}>Click this banner to know more!</Text> -              </View> +                <View style={styles.notificationBox}> +                  <Text style={styles.notificationBoxText}> +                    Oh no, you are (or have been) in an alerted area in{" "} +                    {formatDate(notification.createdAt)}! +                  </Text> +                  <Text style={styles.notificationBoxText}> +                    Click this banner to know more! +                  </Text> +                </View>                </Link>              </View>            ) : ( -            <> -            </> +            <></>            )}            <ThemedView>              <Pressable onPress={handleLogout} style={styles.formButton}> -              <Text style={{ color: 'white', textAlign: 'center' }}>Logout</Text> +              <Text style={{ color: "white", textAlign: "center" }}> +                Logout +              </Text>              </Pressable>            </ThemedView>            <View> -            <MapView -              ref={mapRef} -              initialRegion={region} -              style={styles.map} -            > +            <MapView ref={mapRef} initialRegion={region} style={styles.map}>                <Marker coordinate={coordinates} title="Me" />              </MapView>            </View> @@ -352,7 +445,9 @@ export default function HomeScreen() {              </View>              <View style={styles.buttonContainer}>                <Pressable onPress={handleLogin} style={styles.formButton}> -                <Text style={{ color: 'white', textAlign: 'center' }}>Login</Text> +                <Text style={{ color: "white", textAlign: "center" }}> +                  Login +                </Text>                </Pressable>              </View>            </ThemedView> @@ -364,8 +459,8 @@ export default function HomeScreen() {  const styles = StyleSheet.create({    titleContainer: { -    flexDirection: 'row', -    alignItems: 'center', +    flexDirection: "row", +    alignItems: "center",      marginBottom: 20,    },    text: { @@ -376,13 +471,13 @@ const styles = StyleSheet.create({      paddingHorizontal: 20,    },    formInput: { -    width: '100%', +    width: "100%",      paddingVertical: 12,      paddingHorizontal: 16,      borderRadius: 8,      borderWidth: 1, -    borderColor: '#ccc', -    backgroundColor: '#f9f9f9', +    borderColor: "#ccc", +    backgroundColor: "#f9f9f9",      marginBottom: 20,    },    buttonContainer: { @@ -391,12 +486,12 @@ const styles = StyleSheet.create({    formButton: {      paddingVertical: 12,      paddingHorizontal: 24, -    backgroundColor: '#007AFF', +    backgroundColor: "#007AFF",      fontSize: 16, -    fontWeight: '600', -    textAlign: 'center', +    fontWeight: "600", +    textAlign: "center",      borderRadius: 8, -    color: 'white', +    color: "white",    },    map: {      height: 400, @@ -404,15 +499,15 @@ const styles = StyleSheet.create({    notificationBox: {      padding: 40,      borderRadius: 8, -    shadowColor: '#000', +    shadowColor: "#000",      shadowOffset: { width: 0, height: 2 },      shadowOpacity: 0.1,      shadowRadius: 4, -    backgroundColor: '#EA2027', +    backgroundColor: "#EA2027",    },    notificationBoxText: { -    color: '#fff', -    textAlign: 'center', -    fontWeight: 'bold' -  } +    color: "#fff", +    textAlign: "center", +    fontWeight: "bold", +  },  }); diff --git a/package-lock.json b/package-lock.json index 9b34af1..be7528b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,24 @@  {    "name": "cas4", -  "version": "1.0.0", +  "version": "0.2.0",    "lockfileVersion": 2,    "requires": true,    "packages": {      "": {        "name": "cas4", -      "version": "1.0.0", +      "version": "0.2.0",        "dependencies": { +        "@babel/runtime": "^7.25.6",          "@expo/vector-icons": "^14.0.2",          "@react-native-async-storage/async-storage": "1.23.1", +        "@react-native/assets-registry": "^0.75.2",          "@react-navigation/native": "^6.0.2",          "expo": "~51.0.28",          "expo-constants": "~16.0.2",          "expo-device": "~6.0.2",          "expo-font": "~12.0.9",          "expo-linking": "~6.3.1", +        "expo-location": "~17.0.1",          "expo-notifications": "~0.28.16",          "expo-router": "~3.5.23",          "expo-splash-screen": "~0.27.5", @@ -26,7 +29,7 @@          "react-dom": "18.2.0",          "react-native": "0.74.5",          "react-native-gesture-handler": "~2.16.1", -        "react-native-maps": "^1.14.0", +        "react-native-maps": "1.14.0",          "react-native-reanimated": "~3.10.1",          "react-native-safe-area-context": "4.10.5",          "react-native-screens": "3.31.1", @@ -2082,9 +2085,9 @@        "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="      },      "node_modules/@babel/runtime": { -      "version": "7.25.4", -      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.4.tgz", -      "integrity": "sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==", +      "version": "7.25.6", +      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", +      "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",        "dependencies": {          "regenerator-runtime": "^0.14.0"        }, @@ -5891,9 +5894,9 @@        }      },      "node_modules/@react-native/assets-registry": { -      "version": "0.74.87", -      "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.87.tgz", -      "integrity": "sha512-1XmRhqQchN+pXPKEKYdpJlwESxVomJOxtEnIkbo7GAlaN2sym84fHEGDXAjLilih5GVPpcpSmFzTy8jx3LtaFg==", +      "version": "0.75.2", +      "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.2.tgz", +      "integrity": "sha512-P1dLHjpUeC0AIkDHRYcx0qLMr+p92IPWL3pmczzo6T76Qa9XzruQOYy0jittxyBK91Csn6HHQ/eit8TeXW8MVw==",        "engines": {          "node": ">=18"        } @@ -9432,6 +9435,14 @@          "invariant": "^2.2.4"        }      }, +    "node_modules/expo-location": { +      "version": "17.0.1", +      "resolved": "https://registry.npmjs.org/expo-location/-/expo-location-17.0.1.tgz", +      "integrity": "sha512-m+OzotzlAXO3ZZ1uqW5GC25nXW868zN+ROyBA1V4VF6jGay1ZEs4URPglCVUDzZby2F5wt24cMzqDKw2IX6nRw==", +      "peerDependencies": { +        "expo": "*" +      } +    },      "node_modules/expo-modules-autolinking": {        "version": "1.11.2",        "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-1.11.2.tgz", @@ -15873,9 +15884,9 @@        }      },      "node_modules/react-native-maps": { -      "version": "1.18.0", -      "resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-1.18.0.tgz", -      "integrity": "sha512-S17nYUqeMptgIPaAZuVRo+eRelPreBBYQWw6jsxU7qQ12p+THSfFaqabcNn7fBmsXhT3T27iIl8ek8v1H8BaGw==", +      "version": "1.14.0", +      "resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-1.14.0.tgz", +      "integrity": "sha512-ai7h4UdRLGPFCguz1fI8n4sKLEh35nZXHAH4nSWyAeHGrN8K9GjICu9Xd4Q5Ok4h+WwrM6Xz5pGbF3Qm1tO6iQ==",        "dependencies": {          "@types/geojson": "^7946.0.13"        }, @@ -15974,6 +15985,14 @@          "node": ">= 10.14.2"        }      }, +    "node_modules/react-native/node_modules/@react-native/assets-registry": { +      "version": "0.74.87", +      "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.87.tgz", +      "integrity": "sha512-1XmRhqQchN+pXPKEKYdpJlwESxVomJOxtEnIkbo7GAlaN2sym84fHEGDXAjLilih5GVPpcpSmFzTy8jx3LtaFg==", +      "engines": { +        "node": ">=18" +      } +    },      "node_modules/react-native/node_modules/@react-native/normalize-colors": {        "version": "0.74.87",        "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.87.tgz", @@ -19822,9 +19841,9 @@        "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="      },      "@babel/runtime": { -      "version": "7.25.4", -      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.4.tgz", -      "integrity": "sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==", +      "version": "7.25.6", +      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", +      "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",        "requires": {          "regenerator-runtime": "^0.14.0"        } @@ -22654,9 +22673,9 @@        }      },      "@react-native/assets-registry": { -      "version": "0.74.87", -      "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.87.tgz", -      "integrity": "sha512-1XmRhqQchN+pXPKEKYdpJlwESxVomJOxtEnIkbo7GAlaN2sym84fHEGDXAjLilih5GVPpcpSmFzTy8jx3LtaFg==" +      "version": "0.75.2", +      "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.2.tgz", +      "integrity": "sha512-P1dLHjpUeC0AIkDHRYcx0qLMr+p92IPWL3pmczzo6T76Qa9XzruQOYy0jittxyBK91Csn6HHQ/eit8TeXW8MVw=="      },      "@react-native/babel-plugin-codegen": {        "version": "0.74.87", @@ -25334,6 +25353,12 @@          "invariant": "^2.2.4"        }      }, +    "expo-location": { +      "version": "17.0.1", +      "resolved": "https://registry.npmjs.org/expo-location/-/expo-location-17.0.1.tgz", +      "integrity": "sha512-m+OzotzlAXO3ZZ1uqW5GC25nXW868zN+ROyBA1V4VF6jGay1ZEs4URPglCVUDzZby2F5wt24cMzqDKw2IX6nRw==", +      "requires": {} +    },      "expo-modules-autolinking": {        "version": "1.11.2",        "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-1.11.2.tgz", @@ -29965,6 +29990,11 @@              "chalk": "^4.0.0"            }          }, +        "@react-native/assets-registry": { +          "version": "0.74.87", +          "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.87.tgz", +          "integrity": "sha512-1XmRhqQchN+pXPKEKYdpJlwESxVomJOxtEnIkbo7GAlaN2sym84fHEGDXAjLilih5GVPpcpSmFzTy8jx3LtaFg==" +        },          "@react-native/normalize-colors": {            "version": "0.74.87",            "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.87.tgz", @@ -30091,9 +30121,9 @@        }      },      "react-native-maps": { -      "version": "1.18.0", -      "resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-1.18.0.tgz", -      "integrity": "sha512-S17nYUqeMptgIPaAZuVRo+eRelPreBBYQWw6jsxU7qQ12p+THSfFaqabcNn7fBmsXhT3T27iIl8ek8v1H8BaGw==", +      "version": "1.14.0", +      "resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-1.14.0.tgz", +      "integrity": "sha512-ai7h4UdRLGPFCguz1fI8n4sKLEh35nZXHAH4nSWyAeHGrN8K9GjICu9Xd4Q5Ok4h+WwrM6Xz5pGbF3Qm1tO6iQ==",        "requires": {          "@types/geojson": "^7946.0.13"        } diff --git a/package.json b/package.json index 827734e..cb878e3 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@    "scripts": {      "start": "expo start",      "reset-project": "node ./scripts/reset-project.js", -    "android": "expo start --android", -    "ios": "expo start --ios", +    "android": "expo run:android", +    "ios": "expo run:ios",      "web": "expo start --web",      "test": "jest --watchAll",      "lint": "expo lint" @@ -20,16 +20,19 @@      "@react-native-async-storage/async-storage": "1.23.1",      "@react-native/assets-registry": "^0.75.2",      "@react-navigation/native": "^6.0.2", +    "asyncToGenerator": "link:@babel/runtime/helpers/asyncToGenerator",      "expo": "~51.0.28",      "expo-constants": "~16.0.2",      "expo-device": "~6.0.2",      "expo-font": "~12.0.9",      "expo-linking": "~6.3.1", +    "expo-location": "~17.0.1",      "expo-notifications": "~0.28.16",      "expo-router": "~3.5.23",      "expo-splash-screen": "~0.27.5",      "expo-status-bar": "~1.12.1",      "expo-system-ui": "~3.0.7", +    "expo-task-manager": "^11.8.2",      "expo-web-browser": "~13.0.3",      "react": "18.2.0",      "react-dom": "18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a566ec..ca0cf39 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers:        '@react-navigation/native':          specifier: ^6.0.2          version: 6.1.18(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) +      asyncToGenerator: +        specifier: link:@babel/runtime/helpers/asyncToGenerator +        version: link:@babel/runtime/helpers/asyncToGenerator        expo:          specifier: ~51.0.28          version: 51.0.32(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2)) @@ -38,6 +41,9 @@ importers:        expo-linking:          specifier: ~6.3.1          version: 6.3.1(expo@51.0.32(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))) +      expo-location: +        specifier: ~17.0.1 +        version: 17.0.1(expo@51.0.32(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2)))        expo-notifications:          specifier: ~0.28.16          version: 0.28.16(expo@51.0.32(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))) @@ -53,6 +59,9 @@ importers:        expo-system-ui:          specifier: ~3.0.7          version: 3.0.7(expo@51.0.32(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))) +      expo-task-manager: +        specifier: ^11.8.2 +        version: 11.8.2(expo@51.0.32(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2)))        expo-web-browser:          specifier: ~13.0.3          version: 13.0.3(expo@51.0.32(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))) @@ -133,6 +142,9 @@ packages:      resolution: {integrity: sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==}      engines: {node: '>=6.9.0'} +  '@babel/generator@7.2.0': +    resolution: {integrity: sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==} +    '@babel/generator@7.25.6':      resolution: {integrity: sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==}      engines: {node: '>=6.9.0'} @@ -1062,6 +1074,10 @@ packages:      resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==}      engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} +  '@jest/types@24.9.0': +    resolution: {integrity: sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==} +    engines: {node: '>= 6'} +    '@jest/types@26.6.2':      resolution: {integrity: sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==}      engines: {node: '>= 10.14.2'} @@ -1379,6 +1395,9 @@ packages:    '@types/istanbul-lib-report@3.0.3':      resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} +  '@types/istanbul-reports@1.1.2': +    resolution: {integrity: sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==} +    '@types/istanbul-reports@3.0.4':      resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} @@ -1418,6 +1437,9 @@ packages:    '@types/yargs-parser@21.0.3':      resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} +  '@types/yargs@13.0.12': +    resolution: {integrity: sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ==} +    '@types/yargs@15.0.19':      resolution: {integrity: sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==} @@ -1634,8 +1656,8 @@ packages:      peerDependencies:        '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 -  babel-plugin-react-compiler@0.0.0: -    resolution: {integrity: sha512-Kigl0V36a/6hLVH7+CCe1CCtU3mFBqBd829V//VtuG7I/pyq+B2QZJqOefd63snQmdfCryNhO9XW1FbGPBvYDA==} +  babel-plugin-react-compiler@0.0.0-experimental-7449567-20240905: +    resolution: {integrity: sha512-ltBywPFOEf1rRnkRQ1TiiPJeqJ1Cte86bo4tpSPsfqGTTsiyUo8OLyOR13EG08QIFTQd6HfGGgjpE9Kv/t5Vcg==}    babel-plugin-react-native-web@0.19.12:      resolution: {integrity: sha512-eYZ4+P6jNcB37lObWIg0pUbi7+3PKoU1Oie2j0C8UF3cXyXoR74tO2NBjI/FORb2LJyItJZEAmjU5pSaJYEL1w==} @@ -2304,6 +2326,11 @@ packages:    expo-linking@6.3.1:      resolution: {integrity: sha512-xuZCntSBGWCD/95iZ+mTUGTwHdy8Sx+immCqbUBxdvZ2TN61P02kKg7SaLS8A4a/hLrSCwrg5tMMwu5wfKr35g==} +  expo-location@17.0.1: +    resolution: {integrity: sha512-m+OzotzlAXO3ZZ1uqW5GC25nXW868zN+ROyBA1V4VF6jGay1ZEs4URPglCVUDzZby2F5wt24cMzqDKw2IX6nRw==} +    peerDependencies: +      expo: '*' +    expo-modules-autolinking@1.11.2:      resolution: {integrity: sha512-fdcaNO8ucHA3yLNY52ZUENBcAG7KEx8QyMmnVNavO1JVBGRMZG8JyVcbrhYQDtVtpxkbai5YzwvLutINvbDZDQ==}      hasBin: true @@ -2349,6 +2376,11 @@ packages:      peerDependencies:        expo: '*' +  expo-task-manager@11.8.2: +    resolution: {integrity: sha512-Uhy3ol5gYeZOyeRFddYjLI1B2DGRH1gjp/YC8Hpn5p5MVENviySoKNF+wd98rRvOAokzrzElyDBHSTfX+C3tpg==} +    peerDependencies: +      expo: '*' +    expo-web-browser@13.0.3:      resolution: {integrity: sha512-HXb7y82ApVJtqk8tManyudtTrCtx8xcUnVzmJECeHCB0SsWSQ+penVLZxJkcyATWoJOsFMnfVSVdrTcpKKGszQ==}      peerDependencies: @@ -3774,6 +3806,10 @@ packages:      resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}      engines: {node: '>=6'} +  pretty-format@24.9.0: +    resolution: {integrity: sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==} +    engines: {node: '>= 6'} +    pretty-format@26.6.2:      resolution: {integrity: sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==}      engines: {node: '>= 10'} @@ -4483,6 +4519,10 @@ packages:      resolution: {integrity: sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg==}      engines: {node: '>= 0.4'} +  trim-right@1.0.1: +    resolution: {integrity: sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==} +    engines: {node: '>=0.10.0'} +    ts-interface-checker@0.1.13:      resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -4577,6 +4617,9 @@ packages:      resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==}      engines: {node: '>=4'} +  unimodules-app-loader@4.6.0: +    resolution: {integrity: sha512-FRNIlx7sLBDVPG117JnEBhnzZkTIgZTEwYW2rzrY9HdvLBTpRN+k0dxY50U/CAhFHW3zMD0OP5JAlnSQRhx5HA==} +    unique-filename@3.0.0:      resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==}      engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -4880,6 +4923,15 @@ packages:      resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}      engines: {node: '>=10'} +  zod-validation-error@2.1.0: +    resolution: {integrity: sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ==} +    engines: {node: '>=18.0.0'} +    peerDependencies: +      zod: ^3.18.0 + +  zod@3.23.8: +    resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} +  snapshots:    '@ampproject/remapping@2.3.0': @@ -4918,6 +4970,14 @@ snapshots:      transitivePeerDependencies:        - supports-color +  '@babel/generator@7.2.0': +    dependencies: +      '@babel/types': 7.25.6 +      jsesc: 2.5.2 +      lodash: 4.17.21 +      source-map: 0.5.7 +      trim-right: 1.0.1 +    '@babel/generator@7.25.6':      dependencies:        '@babel/types': 7.25.6 @@ -6373,6 +6433,12 @@ snapshots:      transitivePeerDependencies:        - supports-color +  '@jest/types@24.9.0': +    dependencies: +      '@types/istanbul-lib-coverage': 2.0.6 +      '@types/istanbul-reports': 1.1.2 +      '@types/yargs': 13.0.12 +    '@jest/types@26.6.2':      dependencies:        '@types/istanbul-lib-coverage': 2.0.6 @@ -6942,6 +7008,11 @@ snapshots:      dependencies:        '@types/istanbul-lib-coverage': 2.0.6 +  '@types/istanbul-reports@1.1.2': +    dependencies: +      '@types/istanbul-lib-coverage': 2.0.6 +      '@types/istanbul-lib-report': 3.0.3 +    '@types/istanbul-reports@3.0.4':      dependencies:        '@types/istanbul-lib-report': 3.0.3 @@ -6988,6 +7059,10 @@ snapshots:    '@types/yargs-parser@21.0.3': {} +  '@types/yargs@13.0.12': +    dependencies: +      '@types/yargs-parser': 21.0.3 +    '@types/yargs@15.0.19':      dependencies:        '@types/yargs-parser': 21.0.3 @@ -7219,7 +7294,15 @@ snapshots:      transitivePeerDependencies:        - supports-color -  babel-plugin-react-compiler@0.0.0: {} +  babel-plugin-react-compiler@0.0.0-experimental-7449567-20240905: +    dependencies: +      '@babel/generator': 7.2.0 +      '@babel/types': 7.25.6 +      chalk: 4.1.2 +      invariant: 2.2.4 +      pretty-format: 24.9.0 +      zod: 3.23.8 +      zod-validation-error: 2.1.0(zod@3.23.8)    babel-plugin-react-native-web@0.19.12: {} @@ -7257,7 +7340,7 @@ snapshots:        '@babel/preset-react': 7.24.7(@babel/core@7.25.2)        '@babel/preset-typescript': 7.24.7(@babel/core@7.25.2)        '@react-native/babel-preset': 0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2)) -      babel-plugin-react-compiler: 0.0.0 +      babel-plugin-react-compiler: 0.0.0-experimental-7449567-20240905        babel-plugin-react-native-web: 0.19.12        react-refresh: 0.14.2      transitivePeerDependencies: @@ -7945,6 +8028,10 @@ snapshots:        - expo        - supports-color +  expo-location@17.0.1(expo@51.0.32(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))): +    dependencies: +      expo: 51.0.32(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2)) +    expo-modules-autolinking@1.11.2:      dependencies:        chalk: 4.1.2 @@ -8020,6 +8107,11 @@ snapshots:      transitivePeerDependencies:        - supports-color +  expo-task-manager@11.8.2(expo@51.0.32(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))): +    dependencies: +      expo: 51.0.32(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2)) +      unimodules-app-loader: 4.6.0 +    expo-web-browser@13.0.3(expo@51.0.32(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))):      dependencies:        expo: 51.0.32(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2)) @@ -9780,6 +9872,13 @@ snapshots:    pretty-bytes@5.6.0: {} +  pretty-format@24.9.0: +    dependencies: +      '@jest/types': 24.9.0 +      ansi-regex: 4.1.1 +      ansi-styles: 3.2.1 +      react-is: 16.13.1 +    pretty-format@26.6.2:      dependencies:        '@jest/types': 26.6.2 @@ -10578,6 +10677,8 @@ snapshots:        typedarray.prototype.slice: 1.0.3        which-typed-array: 1.1.15 +  trim-right@1.0.1: {} +    ts-interface-checker@0.1.13: {}    tslib@2.7.0: {} @@ -10670,6 +10771,8 @@ snapshots:    unicode-property-aliases-ecmascript@2.1.0: {} +  unimodules-app-loader@4.6.0: {} +    unique-filename@3.0.0:      dependencies:        unique-slug: 4.0.0 @@ -10939,3 +11042,9 @@ snapshots:        yargs-parser: 21.1.1    yocto-queue@0.1.0: {} + +  zod-validation-error@2.1.0(zod@3.23.8): +    dependencies: +      zod: 3.23.8 + +  zod@3.23.8: {} | 
