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