Add location to NoAccessScreen, move around GPS request

This commit is contained in:
Eric Bailey
2026-07-01 16:25:09 -05:00
parent 545cf13c81
commit f023e34bca
6 changed files with 137 additions and 100 deletions
+81 -49
View File
@@ -43,19 +43,20 @@ import {logger} from '#/ageAssurance/logger'
import {unsafeGetAndComputeAgeAssurance} from '#/ageAssurance/state' import {unsafeGetAndComputeAgeAssurance} from '#/ageAssurance/state'
import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess' import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess'
import { import {
createGeolocationString,
getAgeAssuranceDataFromDeviceSignals, getAgeAssuranceDataFromDeviceSignals,
isLegacyBirthdateBug, isLegacyBirthdateBug,
useAgeAssuranceRegionConfig, useAgeAssuranceRegionConfig,
} from '#/ageAssurance/util' } from '#/ageAssurance/util'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_NATIVE, IS_WEB} from '#/env' import {IS_NATIVE, IS_WEB} from '#/env'
import {useDeviceGeolocationApi} from '#/geolocation' import {useDeviceGeolocationApi, useGeolocation} from '#/geolocation'
const textStyles = [a.text_md, a.leading_snug] const textStyles = [a.text_md, a.leading_snug]
export function NoAccessScreen() { export function NoAccessScreen() {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_, i18n} = useLingui()
const ax = useAnalytics() const ax = useAnalytics()
const {gtPhone} = useBreakpoints() const {gtPhone} = useBreakpoints()
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
@@ -67,6 +68,10 @@ export function NoAccessScreen() {
const isBirthdateUpdateAllowed = useIsBirthdateUpdateAllowed() const isBirthdateUpdateAllowed = useIsBirthdateUpdateAllowed()
const {logoutCurrentAccount} = useSessionApi() const {logoutCurrentAccount} = useSessionApi()
const createSupportLink = useCreateSupportLink() const createSupportLink = useCreateSupportLink()
const geolocation = useGeolocation()
const {setDeviceGeolocation} = useDeviceGeolocationApi()
const locationControl = Dialog.useDialogControl()
const computeAgeAssuranceRegionAccess = useComputeAgeAssuranceRegionAccess()
const aa = useAgeAssurance() const aa = useAgeAssurance()
const isBlocked = aa.state.status === aa.Status.Blocked const isBlocked = aa.state.status === aa.Status.Blocked
@@ -74,6 +79,8 @@ export function NoAccessScreen() {
const hasDeclaredAge = aa.flags.hasDeclaredAge const hasDeclaredAge = aa.flags.hasDeclaredAge
const canUpdateBirthday = const canUpdateBirthday =
isBirthdateUpdateAllowed || isLegacyBirthdateBug(metadata?.birthdate || '') isBirthdateUpdateAllowed || isLegacyBirthdateBug(metadata?.birthdate || '')
const geolocationString = createGeolocationString(geolocation, i18n.locale)
const isUsingGPS = !!geolocation.deviceGeolocation?.countryCode && IS_NATIVE
useEffect(() => { useEffect(() => {
// just counting overall hits here // just counting overall hits here
@@ -189,6 +196,78 @@ export function NoAccessScreen() {
</Trans> </Trans>
</Text> </Text>
{region && (
<Text style={[textStyles]}>
{isUsingGPS ? (
<Trans>
Based on your device's location, we think you're
in{' '}
<Text style={[textStyles, a.font_bold]}>
{geolocationString}
</Text>
.
</Trans>
) : (
<>
<Trans>
Based on your network, we think you're in{' '}
<Text style={[textStyles, a.font_bold]}>
{geolocationString}
</Text>
. This estimate may be inaccurate if you're
using a VPN.
</Trans>{' '}
{IS_NATIVE && (
<>
<Trans>
You can also{' '}
<SimpleInlineLinkText
label={_(msg`Update your location`)}
{...createStaticClick(() => {
locationControl.open()
})}
style={[textStyles]}>
tap here to update your location with GPS.
</SimpleInlineLinkText>
</Trans>
<DeviceLocationRequestDialog
control={locationControl}
onLocationAcquired={props => {
const access =
computeAgeAssuranceRegionAccess(
props.geolocation,
)
if (access !== aa.Access.Full) {
props.disableDialogAction()
props.setDialogError(
_(
msg`We're sorry, but based on your device's location, you are currently located in a region that requires age assurance.`,
),
)
} else {
props.closeDialog(() => {
// set this after close!
setDeviceGeolocation(
props.geolocation,
)
Toast.show(
_(msg`Thanks! You're all set.`),
{
type: 'success',
},
)
})
}
}}
/>
</>
)}
</>
)}
</Text>
)}
{!aa.flags.isOverRegionMinAccessAge && ( {!aa.flags.isOverRegionMinAccessAge && (
<Text style={[textStyles]}> <Text style={[textStyles]}>
<Trans> <Trans>
@@ -312,10 +391,7 @@ function AccessSection() {
const ax = useAnalytics() const ax = useAnalytics()
const control = useDialogControl() const control = useDialogControl()
const appealControl = Dialog.useDialogControl() const appealControl = Dialog.useDialogControl()
const locationControl = Dialog.useDialogControl()
const getTimeAgo = useGetTimeAgo() const getTimeAgo = useGetTimeAgo()
const {setDeviceGeolocation} = useDeviceGeolocationApi()
const computeAgeAssuranceRegionAccess = useComputeAgeAssuranceRegionAccess()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const region = useAgeAssuranceRegionConfig() const region = useAgeAssuranceRegionConfig()
@@ -505,50 +581,6 @@ function AccessSection() {
</View> </View>
</> </>
)} )}
<View style={[a.gap_xs]}>
{IS_NATIVE && (
<>
<Admonition>
<Trans>
Is your location not accurate?{' '}
<SimpleInlineLinkText
label={_(msg`Update your location`)}
{...createStaticClick(() => {
locationControl.open()
})}>
Tap here to update your location with GPS.
</SimpleInlineLinkText>
</Trans>
</Admonition>
<DeviceLocationRequestDialog
control={locationControl}
onLocationAcquired={props => {
const access = computeAgeAssuranceRegionAccess(
props.geolocation,
)
if (access !== aa.Access.Full) {
props.disableDialogAction()
props.setDialogError(
_(
msg`We're sorry, but based on your device's location, you are currently located in a region that requires age assurance.`,
),
)
} else {
props.closeDialog(() => {
// set this after close!
setDeviceGeolocation(props.geolocation)
Toast.show(_(msg`Thanks! You're all set.`), {
type: 'success',
})
})
}
}}
/>
</>
)}
</View>
</View> </View>
</> </>
) )
+10 -12
View File
@@ -9,24 +9,22 @@ import {type OtherRequiredData} from '#/ageAssurance/data'
import {IS_DEV, IS_E2E} from '#/env' import {IS_DEV, IS_E2E} from '#/env'
import {type Geolocation} from '#/geolocation' import {type Geolocation} from '#/geolocation'
export const enabled = (IS_DEV && false) || IS_E2E export const enabled = (IS_DEV && true) || IS_E2E
export const geolocation: Geolocation | undefined = enabled export const geolocation: Geolocation | undefined = enabled
? { ? {
countryCode: 'AA', countryCode: 'US',
regionCode: undefined, regionCode: 'TX',
} }
: undefined : undefined
const deviceGeolocationEnabled = false || IS_E2E export const deviceGeolocation: Geolocation | undefined = enabled
export const deviceGeolocation: Geolocation | undefined = ? {
enabled && deviceGeolocationEnabled countryCode: 'AA',
? { regionCode: undefined,
countryCode: 'AA', ...geolocation,
regionCode: undefined, }
...geolocation, : undefined
}
: undefined
export const otherRequiredData: OtherRequiredData = { export const otherRequiredData: OtherRequiredData = {
birthdate: new Date(2000, 12, 1).toISOString(), birthdate: new Date(2000, 12, 1).toISOString(),
+26
View File
@@ -7,6 +7,7 @@ import {
} from '@atproto/api' } from '@atproto/api'
import {getAge} from '#/lib/strings/time' import {getAge} from '#/lib/strings/time'
import {regionName} from '#/locale/helpers'
import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/const' import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/const'
import { import {
DEVICE_SIGNALS_SUPPORTED, DEVICE_SIGNALS_SUPPORTED,
@@ -21,6 +22,7 @@ import {
type AgeAssuranceState, type AgeAssuranceState,
} from '#/ageAssurance/types' } from '#/ageAssurance/types'
import {type Geolocation, useGeolocation} from '#/geolocation' import {type Geolocation, useGeolocation} from '#/geolocation'
import {USRegionNameToRegionCode} from '#/geolocation/util'
/** /**
* Get age assurance region config based on geolocation, with fallback to * Get age assurance region config based on geolocation, with fallback to
@@ -206,3 +208,27 @@ export function computeAgeAssuranceFlags({
allowsDeviceVerification, allowsDeviceVerification,
} }
} }
const USRegionCodeToRegionName: {[regionCode: string]: string} =
Object.fromEntries(
Object.entries(USRegionNameToRegionCode).map(([name, code]) => [
code,
name,
]),
)
export function createGeolocationString(
geolocation: Geolocation,
appLang: string,
): string | undefined {
const {countryCode, regionCode} = geolocation
if (!countryCode) return undefined
const country = regionName(countryCode, appLang)
// If `regionName` couldn't resolve a real name and fell through to the raw
// code, we'd rather show nothing than a bare ISO code in the prose.
if (country === countryCode) return undefined
if (regionCode && countryCode === 'US') {
const state = USRegionCodeToRegionName[regionCode]
if (state) return `${state}, ${country}`
}
return country
}
@@ -2,7 +2,6 @@ import {View} from 'react-native'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
import {regionName} from '#/locale/helpers'
import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf' import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf'
import {Admonition} from '#/components/Admonition' import {Admonition} from '#/components/Admonition'
import {AgeAssuranceAppealDialog} from '#/components/ageAssurance/AgeAssuranceAppealDialog' import {AgeAssuranceAppealDialog} from '#/components/ageAssurance/AgeAssuranceAppealDialog'
@@ -22,40 +21,10 @@ import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess' import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess'
import {createGeolocationString} from '#/ageAssurance/util'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
import { import {useDeviceGeolocationApi, useGeolocation} from '#/geolocation'
type Geolocation,
useDeviceGeolocationApi,
useGeolocation,
} from '#/geolocation'
import {USRegionNameToRegionCode} from '#/geolocation/util'
import {device, useStorage} from '#/storage'
const USRegionCodeToRegionName: {[regionCode: string]: string} =
Object.fromEntries(
Object.entries(USRegionNameToRegionCode).map(([name, code]) => [
code,
name,
]),
)
function formatRegion(
geolocation: Geolocation,
appLang: string,
): string | undefined {
const {countryCode, regionCode} = geolocation
if (!countryCode) return undefined
const country = regionName(countryCode, appLang)
// If `regionName` couldn't resolve a real name and fell through to the raw
// code, we'd rather show nothing than a bare ISO code in the prose.
if (country === countryCode) return undefined
if (regionCode && countryCode === 'US') {
const state = USRegionCodeToRegionName[regionCode]
if (state) return `${state}, ${country}`
}
return country
}
export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) { export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) {
const aa = useAgeAssurance() const aa = useAgeAssurance()
@@ -220,13 +189,12 @@ function RegionNotice() {
const {t: l, i18n} = useLingui() const {t: l, i18n} = useLingui()
const aa = useAgeAssurance() const aa = useAgeAssurance()
const geolocation = useGeolocation() const geolocation = useGeolocation()
const [deviceGeolocation] = useStorage(device, ['deviceGeolocation'])
const {setDeviceGeolocation} = useDeviceGeolocationApi() const {setDeviceGeolocation} = useDeviceGeolocationApi()
const computeAgeAssuranceRegionAccess = useComputeAgeAssuranceRegionAccess() const computeAgeAssuranceRegionAccess = useComputeAgeAssuranceRegionAccess()
const locationControl = Dialog.useDialogControl() const locationControl = Dialog.useDialogControl()
const region = formatRegion(geolocation, i18n.locale) const region = createGeolocationString(geolocation, i18n.locale)
const isGPS = !!deviceGeolocation?.countryCode && IS_NATIVE const isGPS = !!geolocation.deviceGeolocation?.countryCode && IS_NATIVE
return ( return (
<> <>
@@ -258,8 +226,7 @@ function RegionNotice() {
{isGPS ? ( {isGPS ? (
<Trans> <Trans>
Based on your device's location, we think you're in{' '} Based on your device's location, we think you're in{' '}
<Text style={[a.text_sm, a.font_bold]}>{region}</Text>. This <Text style={[a.text_sm, a.font_bold]}>{region}</Text>.
estimate may be inaccurate if you're using a VPN.
</Trans> </Trans>
) : ( ) : (
<Trans> <Trans>
+13 -1
View File
@@ -22,6 +22,8 @@ export * from '#/geolocation/types'
const GeolocationContext = createContext<Geolocation>({ const GeolocationContext = createContext<Geolocation>({
countryCode: undefined, countryCode: undefined,
regionCode: undefined, regionCode: undefined,
serviceGeolocation: undefined,
deviceGeolocation: undefined,
}) })
const DeviceGeolocationAPIContext = createContext<{ const DeviceGeolocationAPIContext = createContext<{
@@ -44,7 +46,17 @@ export function Provider({children}: {children: ReactNode}) {
'deviceGeolocation', 'deviceGeolocation',
]) ])
const geolocation = useMemo(() => { const geolocation = useMemo(() => {
return mergeGeolocations(deviceGeolocation, geolocationService) const geo = mergeGeolocations(deviceGeolocation, geolocationService)
// create new objects to avoid cyclical references
geo.serviceGeolocation = {
countryCode: geolocationService?.countryCode,
regionCode: geolocationService?.regionCode,
}
geo.deviceGeolocation = {
countryCode: deviceGeolocation?.countryCode,
regionCode: deviceGeolocation?.regionCode,
}
return geo
}, [deviceGeolocation, geolocationService]) }, [deviceGeolocation, geolocationService])
useEffect(() => { useEffect(() => {
+2
View File
@@ -1,4 +1,6 @@
export type Geolocation = { export type Geolocation = {
countryCode: string | undefined countryCode: string | undefined
regionCode: string | undefined regionCode: string | undefined
serviceGeolocation?: Geolocation
deviceGeolocation?: Geolocation
} }