Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
120
fork

Configure Feed

Select the types of activity you want to include in your feed.

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