···1111import {useProfileQuery} from '#/state/queries/profile'
1212import {type SessionAccount, useSession} from '#/state/session'
1313import {useOnboardingState} from '#/state/shell'
1414-import {InitialVerificationAnnouncement} from '#/components/dialogs/nuxs/InitialVerificationAnnouncement'
1414+import {ActivitySubscriptionsNUX} from '#/components/dialogs/nuxs/ActivitySubscriptions'
1515/*
1616 * NUXs
1717 */
1818import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
1919-import {isDaysOld} from '#/components/dialogs/nuxs/utils'
1919+import {isExistingUserAsOf} from '#/components/dialogs/nuxs/utils'
20202121type Context = {
2222 activeNux: Nux | undefined
···3333 }) => boolean
3434}[] = [
3535 {
3636- id: Nux.InitialVerificationAnnouncement,
3636+ id: Nux.ActivitySubscriptions,
3737 enabled: ({currentProfile}) => {
3838- return isDaysOld(2, currentProfile.createdAt)
3838+ return isExistingUserAsOf(
3939+ '2025-07-03T00:00:00.000Z',
4040+ currentProfile.createdAt,
4141+ )
3942 },
4043 },
4144]
···111114 }
112115113116 React.useEffect(() => {
114114- if (snoozed) return
117117+ if (snoozed) return // comment this out to test
115118 if (!nuxs) return
116119117120 for (const {id, enabled} of queuedNuxs) {
···119122120123 // check if completed first
121124 if (nux && nux.completed) {
122122- continue
125125+ continue // comment this out to test
123126 }
124127125128 // then check gate (track exposure)
···172175 return (
173176 <Context.Provider value={ctx}>
174177 {/*For example, activeNux === Nux.NeueTypography && <NeueTypography />*/}
175175- {activeNux === Nux.InitialVerificationAnnouncement && (
176176- <InitialVerificationAnnouncement />
177177- )}
178178+ {activeNux === Nux.ActivitySubscriptions && <ActivitySubscriptionsNUX />}
178179 </Context.Provider>
179180 )
180181}
+15
src/components/dialogs/nuxs/utils.ts
···1616 if (isOldEnough) return true
1717 return false
1818}
1919+2020+export function isExistingUserAsOf(date: string, createdAt?: string) {
2121+ /*
2222+ * Should never happen because we gate NUXs to only accounts with a valid
2323+ * profile and a `createdAt` (see `nuxs/index.tsx`). But if it ever did, the
2424+ * account is either old enough to be pre-onboarding, or some failure happened
2525+ * during account creation. Fail closed. - esb
2626+ */
2727+ if (!createdAt) return false
2828+2929+ const threshold = Date.parse(date)
3030+ const then = new Date(createdAt).getTime()
3131+3232+ return then < threshold
3333+}
···11import {msg, Trans} from '@lingui/macro'
22import {useLingui} from '@lingui/react'
33-import {NativeStackScreenProps} from '@react-navigation/native-stack'
33+import {type NativeStackScreenProps} from '@react-navigation/native-stack'
4455-import {CommonNavigatorParams} from '#/lib/routes/types'
55+import {type CommonNavigatorParams} from '#/lib/routes/types'
66import {isNative} from '#/platform/detection'
77import {
88 useHapticsDisabled,
···1616} from '#/state/preferences/large-alt-badge'
1717import * as SettingsList from '#/screens/Settings/components/SettingsList'
1818import {atoms as a} from '#/alf'
1919-import {Admonition} from '#/components/Admonition'
2019import * as Toggle from '#/components/forms/Toggle'
2120import {Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon} from '#/components/icons/Accessibility'
2221import {Haptic_Stroke2_Corner2_Rounded as HapticIcon} from '#/components/icons/Haptic'
2322import * as Layout from '#/components/Layout'
2424-import {InlineLinkText} from '#/components/Link'
25232624type Props = NativeStackScreenProps<
2725 CommonNavigatorParams,
···10098 </SettingsList.Group>
10199 </>
102100 )}
103103- <SettingsList.Item>
104104- <Admonition type="info" style={[a.flex_1]}>
105105- <Trans>
106106- Autoplay options have moved to the{' '}
107107- <InlineLinkText
108108- to="/settings/content-and-media"
109109- label={_(msg`Content and media`)}>
110110- Content and Media settings
111111- </InlineLinkText>
112112- .
113113- </Trans>
114114- </Admonition>
115115- </SettingsList.Item>
116101 </SettingsList.Container>
117102 </Layout.Content>
118103 </Layout.Screen>
+140
src/screens/Settings/ActivityPrivacySettings.tsx
···11+import {View} from 'react-native'
22+import {type AppBskyNotificationDeclaration} from '@atproto/api'
33+import {msg, Trans} from '@lingui/macro'
44+import {useLingui} from '@lingui/react'
55+66+import {
77+ type AllNavigatorParams,
88+ type NativeStackScreenProps,
99+} from '#/lib/routes/types'
1010+import {
1111+ useNotificationDeclarationMutation,
1212+ useNotificationDeclarationQuery,
1313+} from '#/state/queries/activity-subscriptions'
1414+import {atoms as a, useTheme} from '#/alf'
1515+import {Admonition} from '#/components/Admonition'
1616+import * as Toggle from '#/components/forms/Toggle'
1717+import {BellRinging_Stroke2_Corner0_Rounded as BellRingingIcon} from '#/components/icons/BellRinging'
1818+import * as Layout from '#/components/Layout'
1919+import {Loader} from '#/components/Loader'
2020+import * as SettingsList from './components/SettingsList'
2121+import {ItemTextWithSubtitle} from './NotificationSettings/components/ItemTextWithSubtitle'
2222+2323+type Props = NativeStackScreenProps<
2424+ AllNavigatorParams,
2525+ 'ActivityPrivacySettings'
2626+>
2727+export function ActivityPrivacySettingsScreen({}: Props) {
2828+ const {
2929+ data: notificationDeclaration,
3030+ isPending,
3131+ isError,
3232+ } = useNotificationDeclarationQuery()
3333+3434+ return (
3535+ <Layout.Screen>
3636+ <Layout.Header.Outer>
3737+ <Layout.Header.BackButton />
3838+ <Layout.Header.Content>
3939+ <Layout.Header.TitleText>
4040+ <Trans>Privacy and Security</Trans>
4141+ </Layout.Header.TitleText>
4242+ </Layout.Header.Content>
4343+ <Layout.Header.Slot />
4444+ </Layout.Header.Outer>
4545+ <Layout.Content>
4646+ <SettingsList.Container>
4747+ <SettingsList.Item style={[a.align_start]}>
4848+ <SettingsList.ItemIcon icon={BellRingingIcon} />
4949+ <ItemTextWithSubtitle
5050+ bold
5151+ titleText={
5252+ <Trans>Allow others to be notified of your posts</Trans>
5353+ }
5454+ subtitleText={
5555+ <Trans>
5656+ This feature allows users to receive notifications for your
5757+ new posts and replies. Who do you want to enable this for?
5858+ </Trans>
5959+ }
6060+ />
6161+ </SettingsList.Item>
6262+ <View style={[a.px_xl, a.pt_md]}>
6363+ {isError ? (
6464+ <Admonition type="error">
6565+ <Trans>Failed to load preference.</Trans>
6666+ </Admonition>
6767+ ) : isPending ? (
6868+ <View style={[a.w_full, a.pt_5xl, a.align_center]}>
6969+ <Loader size="xl" />
7070+ </View>
7171+ ) : (
7272+ <Inner notificationDeclaration={notificationDeclaration} />
7373+ )}
7474+ </View>
7575+ </SettingsList.Container>
7676+ </Layout.Content>
7777+ </Layout.Screen>
7878+ )
7979+}
8080+8181+export function Inner({
8282+ notificationDeclaration,
8383+}: {
8484+ notificationDeclaration: {
8585+ uri?: string
8686+ cid?: string
8787+ value: AppBskyNotificationDeclaration.Record
8888+ }
8989+}) {
9090+ const t = useTheme()
9191+ const {_} = useLingui()
9292+ const {mutate} = useNotificationDeclarationMutation()
9393+9494+ const onChangeFilter = ([declaration]: string[]) => {
9595+ mutate({
9696+ $type: 'app.bsky.notification.declaration',
9797+ allowSubscriptions: declaration,
9898+ })
9999+ }
100100+101101+ return (
102102+ <Toggle.Group
103103+ type="radio"
104104+ label={_(
105105+ msg`Filter who can opt to receive notifications for your activity`,
106106+ )}
107107+ values={[notificationDeclaration.value.allowSubscriptions]}
108108+ onChange={onChangeFilter}>
109109+ <View style={[a.gap_sm]}>
110110+ <Toggle.Item
111111+ label={_(msg`Anyone who follows me`)}
112112+ name="followers"
113113+ style={[a.flex_row, a.py_xs, a.gap_sm]}>
114114+ <Toggle.Radio />
115115+ <Toggle.LabelText style={[t.atoms.text, a.font_normal, a.text_md]}>
116116+ <Trans>Anyone who follows me</Trans>
117117+ </Toggle.LabelText>
118118+ </Toggle.Item>
119119+ <Toggle.Item
120120+ label={_(msg`Only followers who I follow`)}
121121+ name="mutuals"
122122+ style={[a.flex_row, a.py_xs, a.gap_sm]}>
123123+ <Toggle.Radio />
124124+ <Toggle.LabelText style={[t.atoms.text, a.font_normal, a.text_md]}>
125125+ <Trans>Only followers who I follow</Trans>
126126+ </Toggle.LabelText>
127127+ </Toggle.Item>
128128+ <Toggle.Item
129129+ label={_(msg`No one`)}
130130+ name="none"
131131+ style={[a.flex_row, a.py_xs, a.gap_sm]}>
132132+ <Toggle.Radio />
133133+ <Toggle.LabelText style={[t.atoms.text, a.font_normal, a.text_md]}>
134134+ <Trans>No one</Trans>
135135+ </Toggle.LabelText>
136136+ </Toggle.Item>
137137+ </View>
138138+ </Toggle.Group>
139139+ )
140140+}
+3-3
src/screens/Settings/AppPasswords.tsx
···77 LinearTransition,
88 StretchOutY,
99} from 'react-native-reanimated'
1010-import {ComAtprotoServerListAppPasswords} from '@atproto/api'
1010+import {type ComAtprotoServerListAppPasswords} from '@atproto/api'
1111import {msg, Trans} from '@lingui/macro'
1212import {useLingui} from '@lingui/react'
1313-import {NativeStackScreenProps} from '@react-navigation/native-stack'
1313+import {type NativeStackScreenProps} from '@react-navigation/native-stack'
14141515-import {CommonNavigatorParams} from '#/lib/routes/types'
1515+import {type CommonNavigatorParams} from '#/lib/routes/types'
1616import {cleanError} from '#/lib/strings/errors'
1717import {isWeb} from '#/platform/detection'
1818import {
+6-3
src/screens/Settings/AppearanceSettings.tsx
···11-import React, {useCallback} from 'react'
11+import {useCallback} from 'react'
22import Animated, {
33 FadeInUp,
44 FadeOutUp,
···99import {useLingui} from '@lingui/react'
10101111import {IS_INTERNAL} from '#/lib/app-info'
1212-import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
1212+import {
1313+ type CommonNavigatorParams,
1414+ type NativeStackScreenProps,
1515+} from '#/lib/routes/types'
1316import {useGate} from '#/lib/statsig/statsig'
1417import {isNative} from '#/platform/detection'
1518import {useSetThemePrefs, useThemePrefs} from '#/state/shell'
1619import {SettingsListItem as AppIconSettingsListItem} from '#/screens/Settings/AppIconSettings/SettingsListItem'
1720import {atoms as a, native, useAlf, useTheme} from '#/alf'
1821import * as ToggleButton from '#/components/forms/ToggleButton'
1919-import {Props as SVGIconProps} from '#/components/icons/common'
2222+import {type Props as SVGIconProps} from '#/components/icons/common'
2023import {Moon_Stroke2_Corner0_Rounded as MoonIcon} from '#/components/icons/Moon'
2124import {Phone_Stroke2_Corner0_Rounded as PhoneIcon} from '#/components/icons/Phone'
2225import {TextSize_Stroke2_Corner0_Rounded as TextSize} from '#/components/icons/TextSize'
+5-2
src/screens/Settings/ExternalMediaPreferences.tsx
···22import {View} from 'react-native'
33import {Trans} from '@lingui/macro'
4455-import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
55+import {
66+ type CommonNavigatorParams,
77+ type NativeStackScreenProps,
88+} from '#/lib/routes/types'
69import {
77- EmbedPlayerSource,
1010+ type EmbedPlayerSource,
811 externalEmbedLabels,
912} from '#/lib/strings/embed-player'
1013import {
+4-1
src/screens/Settings/FollowingFeedPreferences.tsx
···11import {msg, Trans} from '@lingui/macro'
22import {useLingui} from '@lingui/react'
3344-import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
44+import {
55+ type CommonNavigatorParams,
66+ type NativeStackScreenProps,
77+} from '#/lib/routes/types'
58import {
69 usePreferencesQuery,
710 useSetFeedViewPreferencesMutation,