Compare commits

...

10 Commits

Author SHA1 Message Date
Eric Bailey e3e35dd633 Remove debug code 2025-06-17 19:48:12 -05:00
Eric Bailey 699e237b6f Swap in approved stuff 2025-06-17 19:42:53 -05:00
Eric Bailey f0d7c38e03 Remove prev NUX, add new gating util 2025-06-17 18:36:04 -05:00
Eric Bailey 0c5b05e668 Clean up debug code 2025-06-17 18:25:06 -05:00
Eric Bailey 0e4bbc1a0a Prep for NUX placement update 2025-06-17 18:18:23 -05:00
Eric Bailey 093732329a Rough draft 2025-06-17 18:18:23 -05:00
Eric Bailey 03f7693204 Gate browser consent dialog just to be clear 2025-06-17 15:27:29 -05:00
Eric Bailey 5617ed2d8f Do the same on web 2025-06-17 15:25:20 -05:00
Eric Bailey 3e97409925 Ok leave old ModalsContainer alone for now 2025-06-17 15:22:16 -05:00
Eric Bailey b780fc3dcf Move all dialogs within NavigationContent on native 2025-06-17 14:52:45 -05:00
10 changed files with 245 additions and 32 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

-2
View File
@@ -64,7 +64,6 @@ import {Shell} from '#/view/shell'
import {ThemeProvider as Alf} from '#/alf'
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
import {Provider as ContextMenuProvider} from '#/components/ContextMenu'
import {NuxDialogs} from '#/components/dialogs/nuxs'
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
import {Provider as PortalProvider} from '#/components/Portal'
@@ -157,7 +156,6 @@ function InnerApp() {
<IntentDialogProvider>
<TestCtrls />
<Shell />
<NuxDialogs />
</IntentDialogProvider>
</GestureHandlerRootView>
</HideBottomBarBorderProvider>
-2
View File
@@ -54,7 +54,6 @@ import {Shell} from '#/view/shell/index'
import {ThemeProvider as Alf} from '#/alf'
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
import {Provider as ContextMenuProvider} from '#/components/ContextMenu'
import {NuxDialogs} from '#/components/dialogs/nuxs'
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
import {Provider as PortalProvider} from '#/components/Portal'
@@ -135,7 +134,6 @@ function InnerApp() {
<HideBottomBarBorderProvider>
<IntentDialogProvider>
<Shell />
<NuxDialogs />
</IntentDialogProvider>
</HideBottomBarBorderProvider>
</ServiceConfigProvider>
@@ -0,0 +1,192 @@
import {useCallback} from 'react'
import {View} from 'react-native'
import {Image} from 'expo-image'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {type NavigationProp} from '#/lib/routes/types'
import {isWeb} from '#/platform/detection'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {useNuxDialogContext} from '#/components/dialogs/nuxs'
import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons/Sparkle'
import {Text} from '#/components/Typography'
export function GranularNotificationsSettings() {
const t = useTheme()
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
const nuxDialogs = useNuxDialogContext()
const control = Dialog.useDialogControl()
const nav = useNavigation<NavigationProp>()
Dialog.useAutoOpen(control)
const onClose = useCallback(() => {
nuxDialogs.dismissActiveNux()
}, [nuxDialogs])
return (
<Dialog.Outer control={control} onClose={onClose}>
<Dialog.Handle />
<Dialog.ScrollableInner
label={_(msg`Introducing fine-grained notification settings.`)}
style={[
gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full,
]}
contentContainerStyle={[
{
paddingTop: 0,
paddingLeft: 0,
paddingRight: 0,
},
]}>
<View
style={[
a.align_center,
a.overflow_hidden,
t.atoms.bg_contrast_25,
{
gap: isWeb ? 16 : 24,
paddingTop: isWeb ? 24 : 48,
borderTopLeftRadius: a.rounded_md.borderRadius,
borderTopRightRadius: a.rounded_md.borderRadius,
},
]}>
<View
style={[
a.pl_sm,
a.pr_md,
a.py_sm,
a.rounded_full,
a.flex_row,
a.align_center,
a.gap_xs,
{
backgroundColor: t.palette.primary_100,
},
]}>
<SparkleIcon fill={t.palette.primary_800} size="sm" />
<Text
style={[
a.font_bold,
{
color: t.palette.primary_800,
},
]}>
<Trans>New Feature</Trans>
</Text>
</View>
<View
style={[
a.relative,
a.w_full,
a.overflow_hidden,
{
height: isWeb ? 240 : 400,
},
]}>
<View
style={[
a.absolute,
t.atoms.bg_contrast_25,
t.atoms.shadow_md,
{
top: 5,
bottom: 0,
left: '20%',
right: '20%',
width: '60%',
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
},
]}
/>
<Image
accessibilityIgnoresInvertColors
source={require('../../../../assets/images/notification_settings_announcement.png')}
style={[
a.w_full,
{
aspectRatio: 380 / 421,
},
]}
alt={_(
msg`A screenshot of the new notification settings screen, showing many options to configure the notifications you care about.`,
)}
/>
</View>
</View>
<View
style={[
a.px_xl,
isWeb ? [a.pt_xl, a.gap_xl, a.pb_sm] : [a.pt_4xl, a.gap_3xl],
]}>
<View style={[a.gap_sm, a.align_center]}>
<Text
style={[
a.text_3xl,
a.leading_tight,
a.font_heavy,
a.text_center,
]}>
<Trans>More control over your notifications</Trans>
</Text>
<Text
style={[
a.text_md,
a.leading_snug,
a.text_center,
{
maxWidth: 340,
},
]}>
<Trans>
Now you can choose exactly what you want to be notified about.
Visit your notification settings to fine-tune your preferences.
</Trans>
</Text>
</View>
<View style={[a.gap_md, isWeb && a.align_center]}>
{!isWeb && (
<Button
label={_(msg`Close`)}
size="large"
variant="solid"
color="primary"
onPress={() => {
control.close()
}}>
<ButtonText>
<Trans>Close</Trans>
</ButtonText>
</Button>
)}
<Button
label={_(msg`Open settings`)}
size={isWeb ? 'small' : 'large'}
variant="solid"
color={isWeb ? 'primary' : 'secondary'}
style={[a.justify_center]}
onPress={() => {
control.close(() => {
nav.navigate('NotificationSettings')
})
}}>
<ButtonText>
<Trans>Open settings</Trans>
</ButtonText>
</Button>
</View>
</View>
<Dialog.Close />
</Dialog.ScrollableInner>
</Dialog.Outer>
)
}
+12 -8
View File
@@ -11,12 +11,12 @@ import {
import {useProfileQuery} from '#/state/queries/profile'
import {type SessionAccount, useSession} from '#/state/session'
import {useOnboardingState} from '#/state/shell'
import {InitialVerificationAnnouncement} from '#/components/dialogs/nuxs/InitialVerificationAnnouncement'
import {GranularNotificationsSettings} from '#/components/dialogs/nuxs/GranularNotificationsSettings'
/*
* NUXs
*/
import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
import {isDaysOld} from '#/components/dialogs/nuxs/utils'
import {isExistingUserAsOf} from '#/components/dialogs/nuxs/utils'
type Context = {
activeNux: Nux | undefined
@@ -33,9 +33,13 @@ const queuedNuxs: {
}) => boolean
}[] = [
{
id: Nux.InitialVerificationAnnouncement,
id: Nux.GranularNotificationsSettings,
enabled: ({currentProfile}) => {
return isDaysOld(2, currentProfile.createdAt)
// TODO just filler rn
return isExistingUserAsOf(
'2025-06-17T00:00:00.000Z',
currentProfile.createdAt,
)
},
},
]
@@ -111,7 +115,7 @@ function Inner({
}
React.useEffect(() => {
if (snoozed) return
if (snoozed) return // comment this out to test
if (!nuxs) return
for (const {id, enabled} of queuedNuxs) {
@@ -119,7 +123,7 @@ function Inner({
// check if completed first
if (nux && nux.completed) {
continue
continue // comment this out to test
}
// then check gate (track exposure)
@@ -172,8 +176,8 @@ function Inner({
return (
<Context.Provider value={ctx}>
{/*For example, activeNux === Nux.NeueTypography && <NeueTypography />*/}
{activeNux === Nux.InitialVerificationAnnouncement && (
<InitialVerificationAnnouncement />
{activeNux === Nux.GranularNotificationsSettings && (
<GranularNotificationsSettings />
)}
</Context.Provider>
)
+15
View File
@@ -16,3 +16,18 @@ export function isDaysOld(days: number, createdAt?: string) {
if (isOldEnough) return true
return false
}
export function isExistingUserAsOf(date: string, createdAt?: string) {
/*
* Should never happen because we gate NUXs to only accounts with a valid
* profile and a `createdAt` (see `nuxs/index.tsx`). But if it ever did, the
* account is either old enough to be pre-onboarding, or some failure happened
* during account creation. Fail closed. - esb
*/
if (!createdAt) return false
const threshold = Date.parse(date)
const then = new Date(createdAt).getTime()
return then < threshold
}
+6
View File
@@ -6,6 +6,7 @@ export enum Nux {
NeueTypography = 'NeueTypography',
ExploreInterestsCard = 'ExploreInterestsCard',
InitialVerificationAnnouncement = 'InitialVerificationAnnouncement',
GranularNotificationsSettings = 'GranularNotificationsSettings',
}
export const nuxNames = new Set(Object.values(Nux))
@@ -23,10 +24,15 @@ export type AppNux = BaseNux<
id: Nux.InitialVerificationAnnouncement
data: undefined
}
| {
id: Nux.GranularNotificationsSettings
data: undefined
}
>
export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = {
[Nux.NeueTypography]: undefined,
[Nux.ExploreInterestsCard]: undefined,
[Nux.InitialVerificationAnnouncement]: undefined,
[Nux.GranularNotificationsSettings]: undefined,
}
@@ -40,6 +40,13 @@ import {Onboarding} from '#/screens/Onboarding'
import {SignupQueued} from '#/screens/SignupQueued'
import {Takendown} from '#/screens/Takendown'
import {atoms as a, useLayoutBreakpoints} from '#/alf'
import {EmailDialog} from '#/components/dialogs/EmailDialog'
import {InAppBrowserConsentDialog} from '#/components/dialogs/InAppBrowserConsent'
import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
import {NuxDialogs} from '#/components/dialogs/nuxs'
import {SigninDialog} from '#/components/dialogs/Signin'
import {Outlet as PortalOutlet} from '#/components/Portal'
import {BottomSheetOutlet} from '#/../modules/bottom-sheet'
import {BottomBarWeb} from './bottom-bar/BottomBarWeb'
import {DesktopLeftNav} from './desktop/LeftNav'
import {DesktopRightNav} from './desktop/RightNav'
@@ -167,6 +174,19 @@ function NativeStackNavigator({
{!isMobile && <DesktopRightNav routeName={activeRoute.name} />}
</>
)}
{/* Start: individual dialogs and outlets */}
<MutedWordsDialog />
<SigninDialog />
<EmailDialog />
{!isWeb && <InAppBrowserConsentDialog />}
<PortalOutlet />
<BottomSheetOutlet />
{/* End: individual dialogs and outlets */}
{/* Start: dialog controllers */}
<NuxDialogs />
{/* End: dialog controllers */}
</NavigationContent>
)
}
-12
View File
@@ -25,13 +25,7 @@ import {ModalsContainer} from '#/view/com/modals/Modal'
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
import {atoms as a, select, useTheme} from '#/alf'
import {setSystemUITheme} from '#/alf/util/systemUI'
import {EmailDialog} from '#/components/dialogs/EmailDialog'
import {InAppBrowserConsentDialog} from '#/components/dialogs/InAppBrowserConsent'
import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
import {SigninDialog} from '#/components/dialogs/Signin'
import {Outlet as PortalOutlet} from '#/components/Portal'
import {RoutesContainer, TabsNavigator} from '#/Navigation'
import {BottomSheetOutlet} from '../../../modules/bottom-sheet'
import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView'
import {Composer} from './Composer'
import {DrawerContent} from './Drawer'
@@ -151,13 +145,7 @@ function ShellInner() {
</View>
<Composer winHeight={winDim.height} />
<ModalsContainer />
<MutedWordsDialog />
<SigninDialog />
<EmailDialog />
<InAppBrowserConsentDialog />
<Lightbox />
<PortalOutlet />
<BottomSheetOutlet />
</>
)
}
-8
View File
@@ -17,10 +17,6 @@ import {Lightbox} from '#/view/com/lightbox/Lightbox'
import {ModalsContainer} from '#/view/com/modals/Modal'
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
import {atoms as a, select, useTheme} from '#/alf'
import {EmailDialog} from '#/components/dialogs/EmailDialog'
import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
import {SigninDialog} from '#/components/dialogs/Signin'
import {Outlet as PortalOutlet} from '#/components/Portal'
import {FlatNavigator, RoutesContainer} from '#/Navigation'
import {Composer} from './Composer.web'
import {DrawerContent} from './Drawer'
@@ -66,11 +62,7 @@ function ShellInner() {
</ErrorBoundary>
<Composer winHeight={0} />
<ModalsContainer />
<MutedWordsDialog />
<SigninDialog />
<EmailDialog />
<Lightbox />
<PortalOutlet />
{showDrawerDelayedExit && (
<>