mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-06-26 00:14:43 -04:00
* (lint) MOB-1063 enforce trailing commas * autofix trailing commas * manually fix newly introduced maxlen violations * add trailing comma convention to i18n build
134 lines
4.0 KiB
TypeScript
134 lines
4.0 KiB
TypeScript
import type {
|
|
GeolocationError,
|
|
GeolocationResponse,
|
|
} from "@react-native-community/geolocation";
|
|
import { useNavigation } from "@react-navigation/native";
|
|
import { useCallback, useEffect, useState } from "react";
|
|
|
|
// Please don't change this to an aliased path or the e2e mock will not get
|
|
// used in our e2e tests on Github Actions
|
|
import { clearWatch, watchPosition } from "../sharedHelpers/geolocationWrapper";
|
|
|
|
export const TARGET_POSITIONAL_ACCURACY = 10;
|
|
const MAX_POSITION_AGE_MS = 60_000;
|
|
|
|
export interface UserLocation {
|
|
latitude: number;
|
|
longitude: number;
|
|
positional_accuracy: number;
|
|
altitude: number | null;
|
|
altitudinal_accuracy: number | null;
|
|
}
|
|
|
|
const geolocationOptions = {
|
|
distanceFilter: 0,
|
|
enableHighAccuracy: true,
|
|
maximumAge: 0,
|
|
};
|
|
|
|
const useWatchPosition = ( options: {
|
|
shouldFetchLocation: boolean;
|
|
} ) => {
|
|
const navigation = useNavigation( );
|
|
const [currentPosition, setCurrentPosition] = useState<GeolocationResponse | null>( null );
|
|
const [subscriptionId, setSubscriptionId] = useState<number | null>( null );
|
|
const [userLocation, setUserLocation] = useState<UserLocation | null>( null );
|
|
const { shouldFetchLocation } = options;
|
|
const [hasFocus, setHasFocus] = useState( true );
|
|
|
|
const shouldStartWatch = shouldFetchLocation
|
|
&& subscriptionId === null
|
|
&& hasFocus
|
|
&& ( !userLocation || userLocation.positional_accuracy >= TARGET_POSITIONAL_ACCURACY );
|
|
|
|
const shouldStopWatch = subscriptionId !== null
|
|
&& currentPosition?.coords?.accuracy < TARGET_POSITIONAL_ACCURACY;
|
|
|
|
const stopWatch = useCallback( ( id: number ) => {
|
|
clearWatch( id );
|
|
setSubscriptionId( null );
|
|
setCurrentPosition( null );
|
|
}, [] );
|
|
|
|
const startWatch = useCallback( ( ) => {
|
|
const success = ( position: GeolocationResponse ) => {
|
|
const age = Date.now() - position.timestamp;
|
|
if ( age > MAX_POSITION_AGE_MS ) return;
|
|
setCurrentPosition( position );
|
|
};
|
|
|
|
const failure = ( error: GeolocationError ) => {
|
|
console.warn( "useWatchPosition error: ", error );
|
|
if ( subscriptionId ) {
|
|
stopWatch( subscriptionId );
|
|
}
|
|
};
|
|
|
|
try {
|
|
const watchID = watchPosition(
|
|
success,
|
|
failure,
|
|
geolocationOptions,
|
|
);
|
|
if ( typeof ( watchID ) !== "number" ) {
|
|
throw new Error( "watchPosition failed to return a watchID" );
|
|
}
|
|
setSubscriptionId( watchID );
|
|
} catch ( error ) {
|
|
failure( error as GeolocationError );
|
|
}
|
|
}, [stopWatch, subscriptionId] );
|
|
|
|
useEffect( ( ) => {
|
|
if ( !currentPosition ) { return; }
|
|
const newLocation = {
|
|
latitude: currentPosition?.coords?.latitude,
|
|
longitude: currentPosition?.coords?.longitude,
|
|
positional_accuracy: currentPosition?.coords?.accuracy,
|
|
altitude: currentPosition?.coords?.altitude,
|
|
altitudinal_accuracy: currentPosition?.coords?.altitudeAccuracy,
|
|
};
|
|
setUserLocation( newLocation );
|
|
if ( shouldStopWatch ) {
|
|
stopWatch( subscriptionId );
|
|
}
|
|
}, [currentPosition, stopWatch, subscriptionId, shouldStopWatch] );
|
|
|
|
useEffect( ( ) => {
|
|
if ( shouldStartWatch ) {
|
|
startWatch( );
|
|
}
|
|
}, [shouldStartWatch, startWatch] );
|
|
|
|
useEffect( ( ) => {
|
|
// When we leave the screen this hook was used on...
|
|
const unsubscribe = navigation.addListener( "blur", ( ) => {
|
|
// ...stop watching for location updates if we were...
|
|
if ( subscriptionId !== null ) {
|
|
stopWatch( subscriptionId );
|
|
}
|
|
// ...and wipe the current location so we don't pick up a stale one later
|
|
setUserLocation( null );
|
|
setHasFocus( false );
|
|
} );
|
|
return unsubscribe;
|
|
}, [navigation, stopWatch, subscriptionId] );
|
|
|
|
// Listen for focus. We only want to fetch location when this screen has focus.
|
|
useEffect( ( ) => {
|
|
const unsubscribe = navigation.addListener( "focus", ( ) => {
|
|
setHasFocus( true );
|
|
} );
|
|
return unsubscribe;
|
|
}, [navigation] );
|
|
|
|
return {
|
|
isFetchingLocation: subscriptionId !== null,
|
|
stopWatch,
|
|
subscriptionId,
|
|
userLocation,
|
|
};
|
|
};
|
|
|
|
export default useWatchPosition;
|