about summary refs log tree commit diff
path: root/src/screens
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens')
-rw-r--r--src/screens/Messages/ChatList.tsx18
-rw-r--r--src/screens/Messages/Conversation.tsx17
-rw-r--r--src/screens/Messages/Inbox.tsx33
-rw-r--r--src/screens/Messages/Settings.tsx18
-rw-r--r--src/screens/Moderation/index.tsx59
-rw-r--r--src/screens/Settings/AboutSettings.tsx16
-rw-r--r--src/screens/Settings/AccountSettings.tsx22
-rw-r--r--src/screens/Settings/Settings.tsx3
8 files changed, 147 insertions, 39 deletions
diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx
index 388d23ec2..e13f0617b 100644
--- a/src/screens/Messages/ChatList.tsx
+++ b/src/screens/Messages/ChatList.tsx
@@ -23,6 +23,8 @@ import {useSession} from '#/state/session'
 import {List, type ListRef} from '#/view/com/util/List'
 import {ChatListLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
 import {atoms as a, useBreakpoints, useTheme} from '#/alf'
+import {AgeRestrictedScreen} from '#/components/ageAssurance/AgeRestrictedScreen'
+import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {type DialogControlProps, useDialogControl} from '#/components/Dialog'
 import {NewChat} from '#/components/dms/dialogs/NewChatDialog'
@@ -64,7 +66,21 @@ function keyExtractor(item: ListItem) {
 }
 
 type Props = NativeStackScreenProps<MessagesTabNavigatorParams, 'Messages'>
-export function MessagesScreen({navigation, route}: Props) {
+
+export function MessagesScreen(props: Props) {
+  const {_} = useLingui()
+  const aaCopy = useAgeAssuranceCopy()
+
+  return (
+    <AgeRestrictedScreen
+      screenTitle={_(msg`Chats`)}
+      infoText={aaCopy.chatsInfoText}>
+      <MessagesScreenInner {...props} />
+    </AgeRestrictedScreen>
+  )
+}
+
+export function MessagesScreenInner({navigation, route}: Props) {
   const {_} = useLingui()
   const t = useTheme()
   const {currentAccount} = useSession()
diff --git a/src/screens/Messages/Conversation.tsx b/src/screens/Messages/Conversation.tsx
index 90547a8d4..7f3b53b94 100644
--- a/src/screens/Messages/Conversation.tsx
+++ b/src/screens/Messages/Conversation.tsx
@@ -32,6 +32,8 @@ import {useProfileQuery} from '#/state/queries/profile'
 import {useSetMinimalShellMode} from '#/state/shell'
 import {MessagesList} from '#/screens/Messages/components/MessagesList'
 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
+import {AgeRestrictedScreen} from '#/components/ageAssurance/AgeRestrictedScreen'
+import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy'
 import {
   EmailDialogScreenID,
   useEmailDialogControl,
@@ -46,7 +48,20 @@ type Props = NativeStackScreenProps<
   CommonNavigatorParams,
   'MessagesConversation'
 >
-export function MessagesConversationScreen({route}: Props) {
+
+export function MessagesConversationScreen(props: Props) {
+  const {_} = useLingui()
+  const aaCopy = useAgeAssuranceCopy()
+  return (
+    <AgeRestrictedScreen
+      screenTitle={_(msg`Conversation`)}
+      infoText={aaCopy.chatsInfoText}>
+      <MessagesConversationScreenInner {...props} />
+    </AgeRestrictedScreen>
+  )
+}
+
+export function MessagesConversationScreenInner({route}: Props) {
   const {gtMobile} = useBreakpoints()
   const setMinimalShellMode = useSetMinimalShellMode()
 
diff --git a/src/screens/Messages/Inbox.tsx b/src/screens/Messages/Inbox.tsx
index 0f64d2014..8765cf0ba 100644
--- a/src/screens/Messages/Inbox.tsx
+++ b/src/screens/Messages/Inbox.tsx
@@ -1,17 +1,23 @@
 import {useCallback, useMemo, useState} from 'react'
 import {View} from 'react-native'
-import {ChatBskyConvoDefs, ChatBskyConvoListConvos} from '@atproto/api'
+import {
+  type ChatBskyConvoDefs,
+  type ChatBskyConvoListConvos,
+} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useFocusEffect, useNavigation} from '@react-navigation/native'
-import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query'
+import {
+  type InfiniteData,
+  type UseInfiniteQueryResult,
+} from '@tanstack/react-query'
 
 import {useAppState} from '#/lib/hooks/useAppState'
 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
 import {
-  CommonNavigatorParams,
-  NativeStackScreenProps,
-  NavigationProp,
+  type CommonNavigatorParams,
+  type NativeStackScreenProps,
+  type NavigationProp,
 } from '#/lib/routes/types'
 import {cleanError} from '#/lib/strings/errors'
 import {logger} from '#/logger'
@@ -26,6 +32,8 @@ import {List} from '#/view/com/util/List'
 import {ChatListLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
 import * as Toast from '#/view/com/util/Toast'
 import {atoms as a, useBreakpoints, useTheme} from '#/alf'
+import {AgeRestrictedScreen} from '#/components/ageAssurance/AgeRestrictedScreen'
+import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {useRefreshOnFocus} from '#/components/hooks/useRefreshOnFocus'
 import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow'
@@ -39,7 +47,20 @@ import {Text} from '#/components/Typography'
 import {RequestListItem} from './components/RequestListItem'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesInbox'>
-export function MessagesInboxScreen({}: Props) {
+
+export function MessagesInboxScreen(props: Props) {
+  const {_} = useLingui()
+  const aaCopy = useAgeAssuranceCopy()
+  return (
+    <AgeRestrictedScreen
+      screenTitle={_(msg`Chat requests`)}
+      infoText={aaCopy.chatsInfoText}>
+      <MessagesInboxScreenInner {...props} />
+    </AgeRestrictedScreen>
+  )
+}
+
+export function MessagesInboxScreenInner({}: Props) {
   const {gtTablet} = useBreakpoints()
 
   const listConvosQuery = useListConvosQuery({status: 'request'})
diff --git a/src/screens/Messages/Settings.tsx b/src/screens/Messages/Settings.tsx
index 0b8c88b9d..6015c07cd 100644
--- a/src/screens/Messages/Settings.tsx
+++ b/src/screens/Messages/Settings.tsx
@@ -12,6 +12,8 @@ import {useSession} from '#/state/session'
 import * as Toast from '#/view/com/util/Toast'
 import {atoms as a} from '#/alf'
 import {Admonition} from '#/components/Admonition'
+import {AgeRestrictedScreen} from '#/components/ageAssurance/AgeRestrictedScreen'
+import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy'
 import {Divider} from '#/components/Divider'
 import * as Toggle from '#/components/forms/Toggle'
 import * as Layout from '#/components/Layout'
@@ -21,7 +23,21 @@ import {useBackgroundNotificationPreferences} from '../../../modules/expo-backgr
 type AllowIncoming = 'all' | 'none' | 'following'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesSettings'>
-export function MessagesSettingsScreen({}: Props) {
+
+export function MessagesSettingsScreen(props: Props) {
+  const {_} = useLingui()
+  const aaCopy = useAgeAssuranceCopy()
+
+  return (
+    <AgeRestrictedScreen
+      screenTitle={_(msg`Chat Settings`)}
+      infoText={aaCopy.chatsInfoText}>
+      <MessagesSettingsScreenInner {...props} />
+    </AgeRestrictedScreen>
+  )
+}
+
+export function MessagesSettingsScreenInner({}: Props) {
   const {_} = useLingui()
   const {currentAccount} = useSession()
   const {data: profile} = useProfileQuery({
diff --git a/src/screens/Moderation/index.tsx b/src/screens/Moderation/index.tsx
index 78b0a6ae9..a7b434e52 100644
--- a/src/screens/Moderation/index.tsx
+++ b/src/screens/Moderation/index.tsx
@@ -12,6 +12,7 @@ import {
 } from '#/lib/routes/types'
 import {logger} from '#/logger'
 import {isIOS} from '#/platform/detection'
+import {useAgeAssurance} from '#/state/ageAssurance/useAgeAssurance'
 import {
   useMyLabelersQuery,
   usePreferencesQuery,
@@ -20,8 +21,8 @@ import {
 } from '#/state/queries/preferences'
 import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities'
 import {useSetMinimalShellMode} from '#/state/shell'
-import {ViewHeader} from '#/view/com/util/ViewHeader'
 import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf'
+import {AgeAssuranceAdmonition} from '#/components/ageAssurance/AgeAssuranceAdmonition'
 import {Button, ButtonText} from '#/components/Button'
 import * as Dialog from '#/components/Dialog'
 import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
@@ -84,13 +85,22 @@ export function ModerationScreen(
     error: preferencesError,
     data: preferences,
   } = usePreferencesQuery()
+  const {isReady: isAgeInfoReady} = useAgeAssurance()
 
-  const isLoading = isPreferencesLoading
+  const isLoading = isPreferencesLoading || !isAgeInfoReady
   const error = preferencesError
 
   return (
     <Layout.Screen testID="moderationScreen">
-      <ViewHeader title={_(msg`Moderation`)} showOnDesktop />
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content>
+          <Layout.Header.TitleText>
+            <Trans>Moderation</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Layout.Header.Slot />
+      </Layout.Header.Outer>
       <Layout.Content>
         {isLoading ? (
           <ListMaybePlaceholder isLoading={true} sideBorders={false} />
@@ -157,6 +167,7 @@ export function ModerationScreenInner({
     data: labelers,
     error: labelersError,
   } = useMyLabelersQuery()
+  const {declaredAge, isDeclaredUnderage, isAgeRestricted} = useAgeAssurance()
 
   useFocusEffect(
     useCallback(() => {
@@ -170,8 +181,6 @@ export function ModerationScreenInner({
     (optimisticAdultContent && optimisticAdultContent.enabled) ||
     (!optimisticAdultContent && preferences.moderationPrefs.adultContentEnabled)
   )
-  const ageNotSet = !preferences.userAge
-  const isUnderage = (preferences.userAge || 0) < 18
 
   const onToggleAdultContentEnabled = useCallback(
     async (selected: boolean) => {
@@ -306,8 +315,14 @@ export function ModerationScreenInner({
         <Trans>Content filters</Trans>
       </Text>
 
+      <AgeAssuranceAdmonition style={[a.pb_md]}>
+        <Trans>
+          You must complete age assurance in order to access the settings below.
+        </Trans>
+      </AgeAssuranceAdmonition>
+
       <View style={[a.gap_md]}>
-        {ageNotSet && (
+        {declaredAge === undefined && (
           <>
             <Button
               label={_(msg`Confirm your birthdate`)}
@@ -336,7 +351,7 @@ export function ModerationScreenInner({
             a.overflow_hidden,
             t.atoms.bg_contrast_25,
           ]}>
-          {!ageNotSet && !isUnderage && (
+          {!isDeclaredUnderage && !isAgeRestricted && (
             <>
               <View
                 style={[
@@ -389,21 +404,25 @@ export function ModerationScreenInner({
                 </View>
               )}
               <Divider />
+
+              {adultContentEnabled && (
+                <>
+                  <GlobalLabelPreference labelDefinition={LABELS.porn} />
+                  <Divider />
+                  <GlobalLabelPreference labelDefinition={LABELS.sexual} />
+                  <Divider />
+                  <GlobalLabelPreference
+                    labelDefinition={LABELS['graphic-media']}
+                  />
+                  <Divider />
+                </>
+              )}
             </>
           )}
-          {!isUnderage && adultContentEnabled && (
-            <>
-              <GlobalLabelPreference labelDefinition={LABELS.porn} />
-              <Divider />
-              <GlobalLabelPreference labelDefinition={LABELS.sexual} />
-              <Divider />
-              <GlobalLabelPreference
-                labelDefinition={LABELS['graphic-media']}
-              />
-              <Divider />
-            </>
-          )}
-          <GlobalLabelPreference labelDefinition={LABELS.nudity} />
+          <GlobalLabelPreference
+            disabled={isDeclaredUnderage || isAgeRestricted}
+            labelDefinition={LABELS.nudity}
+          />
         </View>
       </View>
 
diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx
index 0ce127ff3..6be881a88 100644
--- a/src/screens/Settings/AboutSettings.tsx
+++ b/src/screens/Settings/AboutSettings.tsx
@@ -20,9 +20,11 @@ import {BroomSparkle_Stroke2_Corner2_Rounded as BroomSparkleIcon} from '#/compon
 import {CodeLines_Stroke2_Corner2_Rounded as CodeLinesIcon} from '#/components/icons/CodeLines'
 import {Globe_Stroke2_Corner0_Rounded as GlobeIcon} from '#/components/icons/Globe'
 import {Newspaper_Stroke2_Corner2_Rounded as NewspaperIcon} from '#/components/icons/Newspaper'
+import {ShieldCheck_Stroke2_Corner0_Rounded as Shield} from '#/components/icons/Shield'
 import {Wrench_Stroke2_Corner2_Rounded as WrenchIcon} from '#/components/icons/Wrench'
 import * as Layout from '#/components/Layout'
 import {Loader} from '#/components/Loader'
+import {device} from '#/storage'
 import {useDemoMode} from '#/storage/hooks/demo-mode'
 import {useDevMode} from '#/storage/hooks/dev-mode'
 import {OTAInfo} from './components/OTAInfo'
@@ -179,6 +181,20 @@ export function AboutSettingsScreen({}: Props) {
                   </SettingsList.ItemText>
                 </SettingsList.PressableItem>
               )}
+
+              <SettingsList.PressableItem
+                onPress={() => {
+                  device.set(['geolocation'], {
+                    countryCode: 'GB',
+                    isAgeRestrictedGeo: true,
+                  })
+                }}
+                label="Simulate age restriction">
+                <SettingsList.ItemIcon icon={Shield} />
+                <SettingsList.ItemText>
+                  Simulate age restriction
+                </SettingsList.ItemText>
+              </SettingsList.PressableItem>
             </>
           )}
         </SettingsList.Container>
diff --git a/src/screens/Settings/AccountSettings.tsx b/src/screens/Settings/AccountSettings.tsx
index 393bad2f8..86652d277 100644
--- a/src/screens/Settings/AccountSettings.tsx
+++ b/src/screens/Settings/AccountSettings.tsx
@@ -7,6 +7,7 @@ import {useModalControls} from '#/state/modals'
 import {useSession} from '#/state/session'
 import * as SettingsList from '#/screens/Settings/components/SettingsList'
 import {atoms as a, useTheme} from '#/alf'
+import {AgeAssuranceAccountCard} from '#/components/ageAssurance/AgeAssuranceAccountCard'
 import {useDialogControl} from '#/components/Dialog'
 import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
 import {
@@ -114,16 +115,6 @@ export function AccountSettingsScreen({}: Props) {
             <SettingsList.Chevron />
           </SettingsList.PressableItem>
           <SettingsList.Divider />
-          <SettingsList.Item>
-            <SettingsList.ItemIcon icon={BirthdayCakeIcon} />
-            <SettingsList.ItemText>
-              <Trans>Birthday</Trans>
-            </SettingsList.ItemText>
-            <SettingsList.BadgeButton
-              label={_(msg`Edit`)}
-              onPress={() => birthdayControl.open()}
-            />
-          </SettingsList.Item>
           <SettingsList.PressableItem
             label={_(msg`Password`)}
             onPress={() => openModal({name: 'change-password'})}>
@@ -143,6 +134,17 @@ export function AccountSettingsScreen({}: Props) {
             </SettingsList.ItemText>
             <SettingsList.Chevron />
           </SettingsList.PressableItem>
+          <SettingsList.Item>
+            <SettingsList.ItemIcon icon={BirthdayCakeIcon} />
+            <SettingsList.ItemText>
+              <Trans>Birthday</Trans>
+            </SettingsList.ItemText>
+            <SettingsList.BadgeButton
+              label={_(msg`Edit`)}
+              onPress={() => birthdayControl.open()}
+            />
+          </SettingsList.Item>
+          <AgeAssuranceAccountCard style={[a.px_xl, a.pt_xs, a.pb_md]} />
           <SettingsList.Divider />
           <SettingsList.PressableItem
             label={_(msg`Export my data`)}
diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx
index aaba0b4b5..4d10a9d0d 100644
--- a/src/screens/Settings/Settings.tsx
+++ b/src/screens/Settings/Settings.tsx
@@ -32,6 +32,7 @@ import * as Toast from '#/view/com/util/Toast'
 import {UserAvatar} from '#/view/com/util/UserAvatar'
 import * as SettingsList from '#/screens/Settings/components/SettingsList'
 import {atoms as a, platform, tokens, useBreakpoints, useTheme} from '#/alf'
+import {AgeAssuranceDismissibleNotice} from '#/components/ageAssurance/AgeAssuranceDismissibleNotice'
 import {AvatarStackWithFetch} from '#/components/AvatarStack'
 import {useDialogControl} from '#/components/Dialog'
 import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount'
@@ -96,6 +97,8 @@ export function SettingsScreen({}: Props) {
       </Layout.Header.Outer>
       <Layout.Content>
         <SettingsList.Container>
+          <AgeAssuranceDismissibleNotice style={[a.px_lg, a.pt_xs, a.pb_xl]} />
+
           <View
             style={[
               a.px_xl,