Bluesky app fork with some witchin' additions 馃挮
at post-text-option 144 lines 4.3 kB view raw
1import {useCallback, useEffect, useRef} from 'react' 2import * as Location from 'expo-location' 3import {createPermissionHook} from 'expo-modules-core' 4 5import {isNative} from '#/platform/detection' 6import * as debug from '#/geolocation/debug' 7import {logger} from '#/geolocation/logger' 8import {type Geolocation} from '#/geolocation/types' 9import {normalizeDeviceLocation} from '#/geolocation/util' 10import {device} from '#/storage' 11 12/** 13 * Location.useForegroundPermissions on web just errors if the 14 * navigator.permissions API is not available. We need to catch and ignore it, 15 * since it's effectively denied. 16 * 17 * @see https://github.com/expo/expo/blob/72f1562ed9cce5ff6dfe04aa415b71632a3d4b87/packages/expo-location/src/Location.ts#L290-L293 18 */ 19const useForegroundPermissions = createPermissionHook({ 20 getMethod: () => 21 Location.getForegroundPermissionsAsync().catch(error => { 22 logger.debug( 23 'useForegroundPermission: error getting location permissions', 24 {safeMessage: error}, 25 ) 26 return { 27 status: Location.PermissionStatus.DENIED, 28 granted: false, 29 canAskAgain: false, 30 expires: 0, 31 } 32 }), 33 requestMethod: () => 34 Location.requestForegroundPermissionsAsync().catch(error => { 35 logger.debug( 36 'useForegroundPermission: error requesting location permissions', 37 {safeMessage: error}, 38 ) 39 return { 40 status: Location.PermissionStatus.DENIED, 41 granted: false, 42 canAskAgain: false, 43 expires: 0, 44 } 45 }), 46}) 47 48export async function getDeviceGeolocation(): Promise<Geolocation> { 49 if (debug.enabled) return debug.resolve(debug.deviceGeolocation) 50 51 try { 52 const geocode = await Location.getCurrentPositionAsync() 53 const locations = await Location.reverseGeocodeAsync({ 54 latitude: geocode.coords.latitude, 55 longitude: geocode.coords.longitude, 56 }) 57 const location = locations.at(0) 58 const normalized = location ? normalizeDeviceLocation(location) : undefined 59 return { 60 countryCode: normalized?.countryCode ?? undefined, 61 regionCode: normalized?.regionCode ?? undefined, 62 } 63 } catch (e) { 64 logger.error('getDeviceGeolocation: failed', {safeMessage: e}) 65 return { 66 countryCode: undefined, 67 regionCode: undefined, 68 } 69 } 70} 71 72export function useRequestDeviceGeolocation(): () => Promise< 73 | { 74 granted: true 75 location: Geolocation | undefined 76 } 77 | { 78 granted: false 79 } 80> { 81 return useCallback(async () => { 82 const status = await Location.requestForegroundPermissionsAsync() 83 if (status.granted) { 84 return { 85 granted: true, 86 location: await getDeviceGeolocation(), 87 } 88 } else { 89 return { 90 granted: false, 91 } 92 } 93 }, []) 94} 95 96/** 97 * Hook to get and sync the device geolocation from the device GPS and store it 98 * using device storage. If permissions are not granted, it will clear any cached 99 * storage value. 100 */ 101export function useSyncDeviceGeolocationOnStartup( 102 sync: (location: Geolocation | undefined) => void, 103) { 104 const synced = useRef(false) 105 const [status] = useForegroundPermissions() 106 useEffect(() => { 107 if (!isNative) return 108 109 async function get() { 110 // no need to set this more than once per session 111 if (synced.current) return 112 logger.debug('useSyncDeviceGeolocationOnStartup: checking perms') 113 if (status?.granted) { 114 const location = await getDeviceGeolocation() 115 if (location) { 116 logger.debug('useSyncDeviceGeolocationOnStartup: got location') 117 sync(location) 118 synced.current = true 119 } 120 } else { 121 const hasCachedValue = device.get(['deviceGeolocation']) !== undefined 122 /** 123 * If we have a cached value, but user has revoked permissions, 124 * quietly (will take effect lazily) clear this out. 125 */ 126 if (hasCachedValue) { 127 logger.debug( 128 'useSyncDeviceGeolocationOnStartup: clearing cached location, perms revoked', 129 ) 130 device.set(['deviceGeolocation'], undefined) 131 } 132 } 133 } 134 135 get().catch(e => { 136 logger.error( 137 'useSyncDeviceGeolocationOnStartup: failed to get location', 138 { 139 safeMessage: e, 140 }, 141 ) 142 }) 143 }, [status, sync]) 144}