summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSanto Cariotti <santo@dcariotti.me>2024-09-09 16:35:28 +0200
committerSanto Cariotti <santo@dcariotti.me>2024-09-09 16:35:28 +0200
commit558d10b39ceb6380272047da742c6d6b62830008 (patch)
tree5d3de5f46a34885ec351b1b13b774f346c5a5faf
parentd14ce814d083e2ee61f29a235f60d936a545ab68 (diff)
Add location update
-rw-r--r--app.json14
-rw-r--r--app/(tabs)/index.tsx411
-rw-r--r--package-lock.json72
-rw-r--r--package.json7
-rw-r--r--pnpm-lock.yaml117
5 files changed, 434 insertions, 187 deletions
diff --git a/app.json b/app.json
index 96e872e..7e6df92 100644
--- a/app.json
+++ b/app.json
@@ -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: {}