diff --git a/app.json b/app.json index 259a138..3e5f61f 100644 --- a/app.json +++ b/app.json @@ -52,7 +52,7 @@ "applinks:headpat.place", "applinks:api.headpat.place" ], - "buildNumber": "96" + "buildNumber": "99" }, "android": { "permissions": [ @@ -73,7 +73,7 @@ } }, "package": "com.headpat.app", - "versionCode": 70 + "versionCode": 72 }, "extra": { "router": { diff --git a/app/_layout.tsx b/app/_layout.tsx index 4259e04..66e4ce1 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -58,7 +58,6 @@ import DiscordIcon from '~/components/icons/DiscordIcon' import EulaModal from '~/components/EulaModal' import { Muted } from '~/components/ui/typography' import * as Updates from 'expo-updates' -import { LocationObject } from 'expo-location' async function bootstrap() { const initialNotification = await messaging().getInitialNotification() diff --git a/app/locations/(tabs)/index.tsx b/app/locations/(tabs)/index.tsx index 547eeed..fab9341 100644 --- a/app/locations/(tabs)/index.tsx +++ b/app/locations/(tabs)/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react' +import React, { useCallback, useEffect, useRef, useState, useMemo } from 'react' import MapView, { Marker, Polygon, @@ -46,25 +46,20 @@ export default function MutualLocationsPage() { const [userLocation, setUserLocation] = useState(null) const [userStatus, setUserStatus] = useState(null) - const [modalOpen, setModalOpen] = useState(false) const [filtersOpen, setFiltersOpen] = useState(false) const [settingsOpen, setSettingsOpen] = useState(false) - const [events, setEvents] = useState(null) - const [friendsLocations, setFriendsLocations] = - useState(null) + const [friendsLocations, setFriendsLocations] = useState< + LocationType.LocationDocumentsType[] + >([]) const [refreshing, setRefreshing] = useState(false) const [currentEvent, setCurrentEvent] = useState(null) - const [filters, setFilters] = useState({ - showEvents: true, - showUsers: true, - }) + const [filters, setFilters] = useState({ showEvents: true, showUsers: true }) - const fetchEvents = async () => { + const fetchEvents = useCallback(async () => { try { const currentDate = new Date() - const data: Events.EventsType = await database.listDocuments( 'hp_db', 'events', @@ -77,23 +72,20 @@ export default function MutualLocationsPage() { ]), ] ) - setEvents(data) // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (error: any) { + } catch (error) { toast('Failed to fetch events. Please try again later.') } - } + }, []) - const fetchUserLocations = async () => { + const fetchUserLocations = useCallback(async () => { try { - let query = [] const data: LocationType.LocationType = await database.listDocuments( 'hp_db', 'locations', - query + [] ) - const promises = data.documents.map(async (doc) => { if (current?.$id === doc.$id) { setUserStatus(doc) @@ -102,21 +94,20 @@ export default function MutualLocationsPage() { await database.getDocument('hp_db', 'userdata', doc.$id) return { ...doc, userData } }) - const results = await Promise.all(promises) setFriendsLocations(results) // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { toast('Failed to fetch locations. Please try again later.') } - } + }, [current]) - const onRefresh = () => { + const onRefresh = useCallback(() => { setRefreshing(true) - fetchUserLocations().then() - fetchEvents().then() - setRefreshing(false) - } + fetchUserLocations().then(() => + fetchEvents().then(() => setRefreshing(false)) + ) + }, [fetchEvents, fetchUserLocations]) useEffect(() => { let watcher = null @@ -127,9 +118,9 @@ export default function MutualLocationsPage() { } else { watcher = await Location.watchPositionAsync( { - accuracy: Location.Accuracy.High, - timeInterval: 2000, - distanceInterval: 1, + accuracy: Location.Accuracy.Balanced, + timeInterval: 10000, + distanceInterval: 10, }, (location) => { setUserLocation(location.coords) @@ -145,7 +136,7 @@ export default function MutualLocationsPage() { } }, []) - const handleLocationButtonPress = () => { + const handleLocationButtonPress = useCallback(() => { if (userLocation) { mapRef.current.animateToRegion({ latitude: userLocation.latitude, @@ -154,108 +145,103 @@ export default function MutualLocationsPage() { longitudeDelta: 0.0421, }) } - } + }, [userLocation]) - const getUserAvatar = (avatarId: string) => { + const getUserAvatar = useCallback((avatarId: string) => { return `https://api.headpat.place/v1/storage/buckets/avatars/files/${avatarId}/preview?project=hp-main&width=100&height=100` - } + }, []) - let locationsSubscribed = null useFocusEffect( useCallback(() => { - // Refresh and subscribe to events when the component is focused onRefresh() - handleSubscribedEvents() - return () => { - // Remove the event listener when the component is unmounted - setUserStatus(null) - locationsSubscribed() - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [current]) - ) + const locationsSubscribed = client.subscribe( + ['databases.hp_db.collections.locations.documents'], + async (response) => { + const eventType = response.events[0].split('.').pop() + const updatedDocument: any = response.payload - function handleSubscribedEvents() { - locationsSubscribed = client.subscribe( - ['databases.hp_db.collections.locations.documents'], - async (response) => { - const eventType = response.events[0].split('.').pop() - const updatedDocument: any = response.payload - - switch (eventType) { - case 'update': - if (current && updatedDocument.$id === current.$id) { - setUserStatus(updatedDocument) - } - - setFriendsLocations((prevLocations) => { - const existingLocation = prevLocations.find( - (location) => location?.$id === updatedDocument.$id - ) - if (existingLocation) { - // Merge updated document with existing one, preserving userData - return prevLocations.map((location) => - location?.$id === updatedDocument.$id - ? { - ...location, - ...updatedDocument, - userData: location.userData, - } - : location + switch (eventType) { + case 'update': + if (current && updatedDocument.$id === current.$id) { + setUserStatus(updatedDocument) + } + setFriendsLocations((prevLocations) => { + if (!prevLocations) { + prevLocations = [] + } + const existingLocation = prevLocations.find( + (location) => location?.$id === updatedDocument.$id ) - } else { - return [...prevLocations, updatedDocument] + if (existingLocation) { + return prevLocations.map((location) => + location?.$id === updatedDocument.$id + ? { + ...location, + ...updatedDocument, + userData: location.userData, + } + : location + ) + } else { + return [...prevLocations, updatedDocument] + } + }) + break + case 'delete': + if (current && updatedDocument.$id === current.$id) { + setUserStatus(null) } - }) - break - case 'delete': - if (current && updatedDocument.$id === current.$id) { - setUserStatus(null) - } - setFriendsLocations((prevLocations) => { - return prevLocations.filter( - (location) => location?.$id !== updatedDocument.$id - ) - }) - break - case 'create': - if (current && updatedDocument.$id === current.$id) { - setUserStatus(updatedDocument) - } - - const userData: UserData.UserDataDocumentsType = - await database.getDocument( - 'hp_db', - 'userdata', - `${updatedDocument.$id}` - ) - const updatedLocationWithUserData = { ...updatedDocument, userData } - - setFriendsLocations((prevLocations) => { - const locationExists = prevLocations.some( - (location) => location?.$id === updatedDocument.$id - ) - if (locationExists) { - // Update existing location - return prevLocations.map((location) => - location.$id === updatedDocument.$id - ? updatedLocationWithUserData - : location + setFriendsLocations((prevLocations) => { + return prevLocations.filter( + (location) => location?.$id !== updatedDocument.$id + ) + }) + break + case 'create': + if (current && updatedDocument.$id === current.$id) { + setUserStatus(updatedDocument) + } + const userData: UserData.UserDataDocumentsType = + await database.getDocument( + 'hp_db', + 'userdata', + `${updatedDocument.$id}` ) - } else { - // Add new location - return [...prevLocations, updatedLocationWithUserData] + const updatedLocationWithUserData = { + ...updatedDocument, + userData, } - }) - break - default: - Sentry.captureException('Unknown event type:', updatedDocument) + setFriendsLocations((prevLocations) => { + const locationExists = prevLocations.some( + (location) => location?.$id === updatedDocument.$id + ) + if (locationExists) { + return prevLocations.map((location) => + location.$id === updatedDocument.$id + ? updatedLocationWithUserData + : location + ) + } else { + return [...prevLocations, updatedLocationWithUserData] + } + }) + break + default: + Sentry.captureException('Unknown event type:', updatedDocument) + } } + ) + return () => { + setUserStatus(null) + locationsSubscribed() } - ) - } + }, [current, onRefresh]) + ) - const sanitizedDescription = sanitizeHtml(currentEvent?.description) + const sanitizedDescription = useMemo( + () => sanitizeHtml(currentEvent?.description), + [currentEvent?.description] + ) return ( @@ -265,21 +251,16 @@ export default function MutualLocationsPage() { )} - {/* Location permission dialog */} - - {/* Filters dialog */} - - {/* Settings dialog */} @@ -325,7 +298,7 @@ export default function MutualLocationsPage() { provider={ Platform.OS === 'android' ? PROVIDER_GOOGLE : PROVIDER_DEFAULT } - showsUserLocation={!userStatus} // Disable if userStatus is set + showsUserLocation={!userStatus} > {filters.showUsers && friendsLocations?.map((user, index: number) => { @@ -335,10 +308,7 @@ export default function MutualLocationsPage() { return ( @@ -349,7 +319,7 @@ export default function MutualLocationsPage() { style={{ borderWidth: 2, borderColor: user?.statusColor, - }} // Apply border based on statusColor + }} > { - setCurrentEvent(event) - }} + onPress={() => setCurrentEvent(event)} fillColor="rgba(100, 200, 200, 0.5)" strokeColor="rgba(255,0,0,0.5)" /> @@ -397,13 +365,11 @@ export default function MutualLocationsPage() { const [centerLatitude, centerLongitude] = event?.coordinates[0] .split(',') .map(Number) - const polygonCoords = generatePolygonCoords( centerLatitude, centerLongitude, event?.circleRadius ) - if (polygonCoords.length) return ( @@ -411,9 +377,7 @@ export default function MutualLocationsPage() { key={index} coordinates={polygonCoords} tappable={true} - onPress={() => { - setCurrentEvent(event) - }} + onPress={() => setCurrentEvent(event)} fillColor="rgba(100, 200, 200, 0.5)" strokeColor="rgba(255,0,0,0.5)" /> @@ -432,7 +396,6 @@ export default function MutualLocationsPage() { - {/* Don't show if there is no friendsLocation of current user */} {userStatus && ( { checkStatusAsync().then() - checkPreciseStatus().then() }, []) ) - const checkPreciseStatus = async () => { - const preciseLocation = await AsyncStorage.getItem('preciseLocation') - if (!preciseLocation) { - await AsyncStorage.setItem('preciseLocation', 'true') - setPreciseLocation(true) - } else { - setPreciseLocation(preciseLocation === 'true') - } - } - const checkStatusAsync = async () => { const status = await BackgroundFetch.getStatusAsync() const isRegistered = await TaskManager.isTaskRegisteredAsync( @@ -91,21 +77,12 @@ export default function ShareLocationView() { setModalOpen(true) return } - /* - await Location.startLocationUpdatesAsync('background-location-task', { - accuracy: - preciseLocationItem === 'true' - ? Location.Accuracy.High - : Location.Accuracy.Low, - showsBackgroundLocationIndicator: true, - distanceInterval: 10, - timeInterval: 10000, - }) - */ + await Location.startLocationUpdatesAsync('background-location-task', { - accuracy: Location.Accuracy.High, + accuracy: Location.Accuracy.Balanced, showsBackgroundLocationIndicator: false, - pausesUpdatesAutomatically: true, + //pausesUpdatesAutomatically: true, + //activityType: Location.ActivityType.Other, distanceInterval: 10, timeInterval: 10000, }) @@ -132,39 +109,15 @@ export default function ShareLocationView() { if (isRegistered) { await unregisterBackgroundFetchAsync() } else { - await registerBackgroundFetchAsync(current.$id) + if (current) { + await registerBackgroundFetchAsync(current.$id) + } else { + Alert.alert('Error', 'Please log in to share location') + } } await checkStatusAsync() } - /* - const updateLocationAccuracy = async () => { - AsyncStorage.setItem( - 'preciseLocation', - preciseLocation ? 'false' : 'true' - ).then(() => { - setPreciseLocation(!preciseLocation) - }) - - const preciseLocationItem = await AsyncStorage.getItem('preciseLocation') - const isRegistered = await TaskManager.isTaskRegisteredAsync( - 'background-location-task' - ) - - if (isRegistered) { - await Location.stopLocationUpdatesAsync('background-location-task') - await Location.startLocationUpdatesAsync('background-location-task', { - accuracy: - preciseLocationItem === 'true' - ? Location.Accuracy.High - : Location.Accuracy.Low, - showsBackgroundLocationIndicator: true, - distanceInterval: 10, - timeInterval: 10000, - }) - } - } - */ return ( @@ -210,18 +163,6 @@ export default function ShareLocationView() { {isRegistered ? 'Disable sharing' : 'Enable sharing'} - {/* TODO: Implement this feature - - - - - */} NOTE: Please only enable this for conventions or other kinds of events. In the future you will be able to properly select users to share your