diff --git a/app.json b/app.json index 90cfc82..3136697 100644 --- a/app.json +++ b/app.json @@ -52,7 +52,7 @@ "applinks:headpat.place", "applinks:api.headpat.place" ], - "buildNumber": "91" + "buildNumber": "93" }, "android": { "permissions": [ @@ -73,7 +73,7 @@ } }, "package": "com.headpat.app", - "versionCode": 64 + "versionCode": 66 }, "extra": { "router": { diff --git a/app/_layout.tsx b/app/_layout.tsx index f792f84..c64d9fe 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -58,6 +58,7 @@ 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() @@ -75,37 +76,54 @@ async function bootstrap() { } } -TaskManager.defineTask('background-location-task', async ({ data, error }) => { - if (error) { - console.error(error) - Sentry.captureException(error) - return BackgroundFetch.BackgroundFetchResult.Failed - } - - // Use the user data from the task - const userId = await AsyncStorage.getItem('userId') +TaskManager.defineTask( + 'background-location-task', + async ({ + data, + error, + }: { + data: { locations: LocationObject[] } + error: any + }) => { + if (error) { + console.error(error) + Sentry.captureException(error) + return BackgroundFetch.BackgroundFetchResult.Failed + } - if (!userId) { - return BackgroundFetch.BackgroundFetchResult.Failed - } + // Use the user data from the task + const userId = await AsyncStorage.getItem('userId') + const preciseLocation = await AsyncStorage.getItem('preciseLocation') - // Get current location - const location = await Location.getCurrentPositionAsync() + if (!userId) { + return Location.stopLocationUpdatesAsync('background-location-task') + } - // Make API calls to update or create location document - await database - .updateDocument('hp_db', 'locations', userId, { - long: location.coords.longitude, - lat: location.coords.latitude, + // Get current location + const location = await Location.getCurrentPositionAsync({ + accuracy: + preciseLocation === 'true' + ? Location.Accuracy.High + : Location.Accuracy.Low, }) - .catch(async () => { - await database.createDocument('hp_db', 'locations', userId, { + + // Make API calls to update or create location document + await database + .updateDocument('hp_db', 'locations', userId, { long: location.coords.longitude, lat: location.coords.latitude, - timeUntilEnd: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), }) - }) -}) + .catch(async () => { + await database.createDocument('hp_db', 'locations', userId, { + long: location.coords.longitude, + lat: location.coords.latitude, + timeUntilEnd: new Date( + Date.now() + 24 * 60 * 60 * 1000 + ).toISOString(), + }) + }) + } +) const LIGHT_THEME: Theme = { dark: false, @@ -323,7 +341,7 @@ function CustomDrawerContent() { ) }} - onPress={() => router.navigate('/community/list')} + onPress={() => router.navigate('/community')} /> diff --git a/app/account/(stacks)/userprofile/(tabs)/avatarAdd/index.tsx b/app/account/(stacks)/userprofile/(tabs)/avatarAdd/index.tsx index 81ad30d..a2e477e 100644 --- a/app/account/(stacks)/userprofile/(tabs)/avatarAdd/index.tsx +++ b/app/account/(stacks)/userprofile/(tabs)/avatarAdd/index.tsx @@ -15,7 +15,6 @@ export default function AvatarAdd() { const [image, setImage] = useState(null) const { showLoadingModal, hideLoadingModal, showAlertModal } = useAlertModal() const maxFileSize = 1.5 * 1024 * 1024 // 1.5 MB in bytes - const maxResolution = 8 * 1024 * 1024 const pickImage = async () => { try { @@ -29,16 +28,10 @@ export default function AvatarAdd() { return } - if (result.width + result.height > maxResolution) { - showAlertModal('FAILED', 'Image resolution is too large.') - return - } - - if (result.width + result.height <= maxResolution) { - setImage(result) - } + setImage(result) + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { - console.log(error) + //console.log(error) //showAlertModal('FAILED', 'Error picking image.') //Sentry.captureException(error) } @@ -114,7 +107,7 @@ export default function AvatarAdd() { } catch (error) { console.log('error', error) showAlertModal('FAILED', 'Error uploading image.') - Sentry.captureException(error) + Sentry.captureMessage(error, 'log') } } diff --git a/app/account/(stacks)/userprofile/(tabs)/bannerAdd/index.tsx b/app/account/(stacks)/userprofile/(tabs)/bannerAdd/index.tsx index 1086641..015bd89 100644 --- a/app/account/(stacks)/userprofile/(tabs)/bannerAdd/index.tsx +++ b/app/account/(stacks)/userprofile/(tabs)/bannerAdd/index.tsx @@ -15,7 +15,6 @@ export default function BannerAdd() { const [image, setImage] = useState(null) const { showLoadingModal, hideLoadingModal, showAlertModal } = useAlertModal() const maxFileSize = 5 * 1024 * 1024 // 1.5 MB in bytes - const maxResolution = 8 * 1024 * 1024 const pickImage = async () => { try { @@ -28,16 +27,10 @@ export default function BannerAdd() { return } - if (result.width + result.height > maxResolution) { - showAlertModal('FAILED', 'Image resolution is too large.') - return - } - - if (result.width + result.height <= maxResolution) { - setImage(result) - } + setImage(result) + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { - console.log(error) + //console.log(error) //showAlertModal('FAILED', 'Error picking image.') //Sentry.captureException(error) } @@ -115,9 +108,9 @@ export default function BannerAdd() { hideLoadingModal() handleFinish() } catch (error) { - console.log(error) + //console.log(error) showAlertModal('FAILED', 'Error picking image.') - Sentry.captureException(error) + Sentry.captureMessage(error, 'log') } } diff --git a/app/announcements/(stacks)/index.tsx b/app/announcements/(stacks)/index.tsx index bea11d5..a8d1962 100644 --- a/app/announcements/(stacks)/index.tsx +++ b/app/announcements/(stacks)/index.tsx @@ -5,7 +5,7 @@ import { View, } from 'react-native' import { H1, H3, Muted } from '~/components/ui/typography' -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { Announcements } from '~/lib/types/collections' import { database } from '~/lib/appwrite-client' import { Query } from 'react-native-appwrite' @@ -22,6 +22,7 @@ import { formatDate } from '~/components/calculateTimeLeft' import { useColorScheme } from '~/lib/useColorScheme' import { useAlertModal } from '~/components/contexts/AlertModalProvider' import * as Sentry from '@sentry/react-native' +import { useFocusEffect } from '@react-navigation/core' export default function AnnouncementsPage() { const { isDarkColorScheme } = useColorScheme() @@ -59,8 +60,15 @@ export default function AnnouncementsPage() { setRefreshing(false) } + useFocusEffect( + useCallback(() => { + onRefresh() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + ) + useEffect(() => { - fetchAnnouncements().then() + showLoadingModal() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) diff --git a/app/community/_layout.tsx b/app/community/_layout.tsx index badb171..e85e598 100644 --- a/app/community/_layout.tsx +++ b/app/community/_layout.tsx @@ -4,7 +4,7 @@ import React from 'react' function _layout() { return ( - + ) diff --git a/app/community/list/index.tsx b/app/community/index.tsx similarity index 100% rename from app/community/list/index.tsx rename to app/community/index.tsx diff --git a/app/events/(tabs)/archived.tsx b/app/events/(tabs)/archived.tsx index 1a5cc22..1c2c420 100644 --- a/app/events/(tabs)/archived.tsx +++ b/app/events/(tabs)/archived.tsx @@ -13,7 +13,7 @@ import { } from '~/components/ui/card' import { ClockIcon, MapPinIcon } from 'lucide-react-native' import { useColorScheme } from '~/lib/useColorScheme' -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { functions } from '~/lib/appwrite-client' import { Events } from '~/lib/types/collections' import { H1, H3, Muted } from '~/components/ui/typography' @@ -24,6 +24,7 @@ import { } from '~/components/calculateTimeLeft' import { Link } from 'expo-router' import { useAlertModal } from '~/components/contexts/AlertModalProvider' +import { useFocusEffect } from '@react-navigation/core' export default function ArchivedEventsPage() { const { isDarkColorScheme } = useColorScheme() @@ -59,13 +60,18 @@ export default function ArchivedEventsPage() { const onRefresh = () => { setRefreshing(true) - showLoadingModal() fetchEvents().then() } + useFocusEffect( + useCallback(() => { + onRefresh() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + ) + useEffect(() => { showLoadingModal() - fetchEvents().then() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -79,7 +85,7 @@ export default function ArchivedEventsPage() { -

Events

+

Archived

No events available diff --git a/app/events/(tabs)/index.tsx b/app/events/(tabs)/index.tsx index db58358..ef22511 100644 --- a/app/events/(tabs)/index.tsx +++ b/app/events/(tabs)/index.tsx @@ -13,7 +13,7 @@ import { } from '~/components/ui/card' import { ClockIcon, MapPinIcon } from 'lucide-react-native' import { useColorScheme } from '~/lib/useColorScheme' -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { functions } from '~/lib/appwrite-client' import { Events } from '~/lib/types/collections' import { H1, H3, Muted } from '~/components/ui/typography' @@ -24,6 +24,7 @@ import { } from '~/components/calculateTimeLeft' import { Link } from 'expo-router' import { useAlertModal } from '~/components/contexts/AlertModalProvider' +import { useFocusEffect } from '@react-navigation/core' export default function EventsPage() { const { isDarkColorScheme } = useColorScheme() @@ -59,13 +60,18 @@ export default function EventsPage() { const onRefresh = () => { setRefreshing(true) - showLoadingModal() fetchEvents().then() } + useFocusEffect( + useCallback(() => { + onRefresh() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + ) + useEffect(() => { showLoadingModal() - fetchEvents().then() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -79,7 +85,7 @@ export default function EventsPage() { -

Events

+

Active

No events available diff --git a/app/events/(tabs)/upcoming.tsx b/app/events/(tabs)/upcoming.tsx index 8f22094..193c2a5 100644 --- a/app/events/(tabs)/upcoming.tsx +++ b/app/events/(tabs)/upcoming.tsx @@ -13,7 +13,7 @@ import { } from '~/components/ui/card' import { ClockIcon, MapPinIcon } from 'lucide-react-native' import { useColorScheme } from '~/lib/useColorScheme' -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { functions } from '~/lib/appwrite-client' import { Events } from '~/lib/types/collections' import { H1, H3, Muted } from '~/components/ui/typography' @@ -24,6 +24,7 @@ import { } from '~/components/calculateTimeLeft' import { Link } from 'expo-router' import { useAlertModal } from '~/components/contexts/AlertModalProvider' +import { useFocusEffect } from '@react-navigation/core' export default function EventsPage() { const { isDarkColorScheme } = useColorScheme() @@ -33,7 +34,6 @@ export default function EventsPage() { const { showLoadingModal, hideLoadingModal, showAlertModal } = useAlertModal() const fetchEvents = async () => { - showLoadingModal() try { const data = await functions.createExecution( 'event-endpoints', @@ -63,8 +63,15 @@ export default function EventsPage() { fetchEvents().then() } + useFocusEffect( + useCallback(() => { + onRefresh() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + ) + useEffect(() => { - fetchEvents().then() + showLoadingModal() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -78,7 +85,7 @@ export default function EventsPage() { -

Events

+

Upcoming

No upcoming events diff --git a/app/gallery/(stacks)/index.tsx b/app/gallery/(stacks)/index.tsx index dffba3b..abb11d9 100644 --- a/app/gallery/(stacks)/index.tsx +++ b/app/gallery/(stacks)/index.tsx @@ -209,8 +209,9 @@ export default function GalleryPage() { refreshing={refreshing} numColumns={maxColumns} contentContainerStyle={{ flexGrow: 1 }} - onEndReached={loadMore} - onEndReachedThreshold={0.5} + // TODO: Implement this in the future + //onEndReached={loadMore} + //onEndReachedThreshold={0.5} ListFooterComponent={ loadingMore && ( (false) const [events, setEvents] = useState(null) - const [friendsLocations, setFriendsLocations] = useState(null) + const [friendsLocations, setFriendsLocations] = + useState(null) const [refreshing, setRefreshing] = useState(false) const [currentEvent, setCurrentEvent] = useState(null) const [filters, setFilters] = useState({ @@ -187,39 +188,35 @@ export default function MutualLocationsPage() { setUserStatus(updatedDocument) } - setFriendsLocations( - (prevLocations: LocationType.LocationDocumentsType[]) => { - const existingLocation = prevLocations.find( - (location) => location?.$id === updatedDocument.$id + 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 ) - if (existingLocation) { - // Merge updated document with existing one, preserving userData - return prevLocations.map((location) => - location?.$id === updatedDocument.$id - ? { - ...location, - ...updatedDocument, - userData: location.userData, - } - : location - ) - } else { - return [...prevLocations, updatedDocument] - } + } else { + return [...prevLocations, updatedDocument] } - ) + }) break case 'delete': if (current && updatedDocument.$id === current.$id) { setUserStatus(null) } - setFriendsLocations( - (prevLocations: LocationType.LocationDocumentsType[]) => { - return prevLocations.filter( - (location) => location?.$id !== updatedDocument.$id - ) - } - ) + setFriendsLocations((prevLocations) => { + return prevLocations.filter( + (location) => location?.$id !== updatedDocument.$id + ) + }) break case 'create': if (current && updatedDocument.$id === current.$id) { @@ -234,24 +231,22 @@ export default function MutualLocationsPage() { ) const updatedLocationWithUserData = { ...updatedDocument, userData } - setFriendsLocations( - (prevLocations: LocationType.LocationDocumentsType[]) => { - const locationExists = prevLocations.some( - (location) => location?.$id === updatedDocument.$id + 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 ) - if (locationExists) { - // Update existing location - return prevLocations.map((location) => - location.$id === updatedDocument.$id - ? updatedLocationWithUserData - : location - ) - } else { - // Add new location - return [...prevLocations, updatedLocationWithUserData] - } + } else { + // Add new location + return [...prevLocations, updatedLocationWithUserData] } - ) + }) break default: Sentry.captureException('Unknown event type:', updatedDocument) diff --git a/app/locations/(tabs)/share.tsx b/app/locations/(tabs)/share.tsx index 6bf392a..5214372 100644 --- a/app/locations/(tabs)/share.tsx +++ b/app/locations/(tabs)/share.tsx @@ -18,16 +18,34 @@ import { AlertDialogHeader, AlertDialogTitle, } from '~/components/ui/alert-dialog' +import { useFocusEffect } from '@react-navigation/native' +import { Switch } from '~/components/ui/switch' +import { Label } from '~/components/ui/label' +import { Separator } from '~/components/ui/separator' export default function ShareLocationView() { const [isRegistered, setIsRegistered] = React.useState(false) const [status, setStatus] = React.useState(null) const [modalOpen, setModalOpen] = React.useState(false) + const [preciseLocation, setPreciseLocation] = React.useState(false) const { current } = useUser() - React.useEffect(() => { - checkStatusAsync().then() - }, []) + useFocusEffect( + React.useCallback(() => { + 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() @@ -60,15 +78,30 @@ export default function ShareLocationView() { } async function registerBackgroundFetchAsync(userId: string) { + if (!userId) { + setIsRegistered(false) + return Alert.alert('Error', 'No user ID found. Are you logged in?') + } await AsyncStorage.setItem('userId', userId) - //await requestPermissions() + //const preciseLocationItem = await AsyncStorage.getItem('preciseLocation') + let foreground = await Location.getForegroundPermissionsAsync() let background = await Location.getBackgroundPermissionsAsync() if (foreground.status !== 'granted' || background.status !== 'granted') { 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, showsBackgroundLocationIndicator: true, @@ -82,8 +115,7 @@ export default function ShareLocationView() { await Location.stopLocationUpdatesAsync('background-location-task') try { - await database.deleteDocument('hp_db', 'locations', userId) - return + return await database.deleteDocument('hp_db', 'locations', userId) // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return @@ -104,6 +136,34 @@ export default function ShareLocationView() { 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 ( @@ -148,6 +208,19 @@ export default function ShareLocationView() { + + {/* 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 diff --git a/app/user/(stacks)/index.tsx b/app/user/(stacks)/index.tsx index af38569..a782f48 100644 --- a/app/user/(stacks)/index.tsx +++ b/app/user/(stacks)/index.tsx @@ -14,7 +14,7 @@ export default function UserListPage() { const [loadingMore, setLoadingMore] = useState(false) const [offset, setOffset] = useState(0) - const fetchUsers = useCallback(async (newOffset: number = 0) => { + const fetchUsers = async (newOffset: number = 0) => { try { const data: UserData.UserDataType = await database.listDocuments( 'hp_db', @@ -37,7 +37,7 @@ export default function UserListPage() { toast('Failed to fetch users. Please try again later.') Sentry.captureException(error) } - }, []) + } const onRefresh = async () => { setRefreshing(true) @@ -58,7 +58,7 @@ export default function UserListPage() { useEffect(() => { fetchUsers(0).then() - }, [fetchUsers]) + }, []) const renderItem = ({ item }: { item: UserData.UserDataDocumentsType }) => ( diff --git a/components/locations/SettingsModal.tsx b/components/locations/SettingsModal.tsx index 13d862c..c063bb8 100644 --- a/components/locations/SettingsModal.tsx +++ b/components/locations/SettingsModal.tsx @@ -14,6 +14,7 @@ import { Text } from '~/components/ui/text' import { Input } from '~/components/ui/input' import { database } from '~/lib/appwrite-client' import { Switch } from '~/components/ui/switch' +import { Separator } from '~/components/ui/separator' export default function SettingsModal({ openModal, @@ -69,6 +70,7 @@ export default function SettingsModal({ maxLength={40} /> + - - - - {text} + + + + + + + {text} + + + - + ) }