Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1import {useState} from 'react'
2import {View} from 'react-native'
3import {msg} from '@lingui/core/macro'
4import {useLingui} from '@lingui/react'
5import {Trans} from '@lingui/react/macro'
6
7import {wait} from '#/lib/async/wait'
8import {isNetworkError, useCleanError} from '#/lib/hooks/useCleanError'
9import {logger} from '#/logger'
10import {atoms as a, useTheme, web} from '#/alf'
11import {Admonition} from '#/components/Admonition'
12import {Button, ButtonIcon, ButtonText} from '#/components/Button'
13import * as Dialog from '#/components/Dialog'
14import {PinLocation_Stroke2_Corner0_Rounded as LocationIcon} from '#/components/icons/PinLocation'
15import {Loader} from '#/components/Loader'
16import {Text} from '#/components/Typography'
17import {IS_WEB} from '#/env'
18import {type Geolocation, useRequestDeviceGeolocation} from '#/geolocation'
19
20export type Props = {
21 onLocationAcquired?: (props: {
22 geolocation: Geolocation
23 setDialogError: (error: string) => void
24 disableDialogAction: () => void
25 closeDialog: (callback?: () => void) => void
26 }) => void
27}
28
29export function DeviceLocationRequestDialog({
30 control,
31 onLocationAcquired,
32}: Props & {
33 control: Dialog.DialogOuterProps['control']
34}) {
35 const {_} = useLingui()
36 return (
37 <Dialog.Outer control={control}>
38 <Dialog.Handle />
39
40 <Dialog.ScrollableInner
41 label={_(msg`Confirm your location`)}
42 style={[web({maxWidth: 380})]}>
43 <DeviceLocationRequestDialogInner
44 onLocationAcquired={onLocationAcquired}
45 />
46 <Dialog.Close />
47 </Dialog.ScrollableInner>
48 </Dialog.Outer>
49 )
50}
51
52function DeviceLocationRequestDialogInner({onLocationAcquired}: Props) {
53 const t = useTheme()
54 const {_} = useLingui()
55 const {close} = Dialog.useDialogContext()
56 const requestDeviceLocation = useRequestDeviceGeolocation()
57 const cleanError = useCleanError()
58
59 const [isRequesting, setIsRequesting] = useState(false)
60 const [error, setError] = useState<string>('')
61 const [dialogDisabled, setDialogDisabled] = useState(false)
62
63 const onPressConfirm = async () => {
64 setError('')
65 setIsRequesting(true)
66
67 try {
68 const req = await wait(1e3, requestDeviceLocation())
69
70 if (req.granted) {
71 const location = req.location
72
73 if (location && location.countryCode) {
74 onLocationAcquired?.({
75 geolocation: location,
76 setDialogError: setError,
77 disableDialogAction: () => setDialogDisabled(true),
78 closeDialog: close,
79 })
80 } else {
81 setError(_(msg`Failed to resolve location. Please try again.`))
82 }
83 } else {
84 setError(
85 _(
86 msg`Unable to access location. You'll need to visit your system settings to enable location services for Bluesky.`,
87 ),
88 )
89 }
90 } catch (e: any) {
91 const {clean, raw} = cleanError(e)
92 setError(clean || raw || e.message)
93 if (!isNetworkError(e)) {
94 logger.error(`blockedGeoOverlay: unexpected error`, {
95 safeMessage: e.message,
96 })
97 }
98 } finally {
99 setIsRequesting(false)
100 }
101 }
102
103 return (
104 <View style={[a.gap_md]}>
105 <Text style={[a.text_xl, a.font_bold]}>
106 <Trans>Confirm your location</Trans>
107 </Text>
108 <View style={[a.gap_sm, a.pb_xs]}>
109 <Text style={[a.text_md, a.leading_snug, t.atoms.text_contrast_medium]}>
110 <Trans>
111 Tap below to allow Bluesky to access your GPS location. We will then
112 use that data to more accurately determine the content and features
113 available in your region.
114 </Trans>
115 </Text>
116 </View>
117
118 {error && (
119 <View style={[a.pb_xs]}>
120 <Admonition type="error">{error}</Admonition>
121 </View>
122 )}
123
124 <View style={[a.gap_sm]}>
125 {!dialogDisabled && (
126 <Button
127 disabled={isRequesting}
128 label={_(msg`Allow location access`)}
129 onPress={onPressConfirm}
130 size={IS_WEB ? 'small' : 'large'}
131 color="primary">
132 <ButtonIcon icon={isRequesting ? Loader : LocationIcon} />
133 <ButtonText>
134 <Trans>Allow location access</Trans>
135 </ButtonText>
136 </Button>
137 )}
138
139 {!IS_WEB && (
140 <Button
141 label={_(msg`Cancel`)}
142 onPress={() => close()}
143 size={IS_WEB ? 'small' : 'large'}
144 color="secondary">
145 <ButtonText>
146 <Trans>Cancel</Trans>
147 </ButtonText>
148 </Button>
149 )}
150 </View>
151 </View>
152 )
153}