add announcement nux

This commit is contained in:
Samuel Newman
2025-12-10 16:25:01 +02:00
parent bbbeb85a73
commit 7868597729
4 changed files with 142 additions and 15 deletions
@@ -50,7 +50,7 @@ export function FindContactsBannerNUX() {
]}> ]}>
<Image <Image
source={require('../../../assets/images/find_friends_illustration_small.webp')} source={require('../../../assets/images/find_friends_illustration_small.webp')}
accessibilityIgnoresInvertColors={false} accessibilityIgnoresInvertColors
style={[ style={[
{height: 70, aspectRatio: 573 / 286}, {height: 70, aspectRatio: 573 / 286},
a.self_end, a.self_end,
@@ -0,0 +1,114 @@
import {useCallback} from 'react'
import {View} from 'react-native'
import {Image} from 'expo-image'
import {LinearGradient} from 'expo-linear-gradient'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {isWeb} from '#/platform/detection'
import {atoms as a, useTheme, web} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {useNuxDialogContext} from '#/components/dialogs/nuxs'
import {Text} from '#/components/Typography'
import {navigate} from '#/Navigation'
export function FindContactsAnnouncement() {
const t = useTheme()
const {_} = useLingui()
const nuxDialogs = useNuxDialogContext()
const control = Dialog.useDialogControl()
Dialog.useAutoOpen(control)
const onClose = useCallback(() => {
nuxDialogs.dismissActiveNux()
}, [nuxDialogs])
return (
<Dialog.Outer
control={control}
onClose={onClose}
nativeOptions={{preventExpansion: true}}>
<Dialog.Handle />
<Dialog.ScrollableInner
label={_(msg`Introducing finding friends via contacts`)}
style={[web({maxWidth: 440})]}
contentContainerStyle={[
{
paddingTop: 0,
paddingLeft: 0,
paddingRight: 0,
},
]}>
<View style={[a.align_center, a.pt_3xl]}>
<LinearGradient
colors={[t.palette.primary_200, t.atoms.bg.backgroundColor]}
locations={[0, 1]}
start={{x: 0, y: 0}}
end={{x: 0, y: 1}}
style={[a.absolute, a.inset_0]}
/>
<View style={[a.w_full, a.pt_sm, a.px_5xl, a.pb_4xl]}>
<Image
accessibilityIgnoresInvertColors
source={require('../../../../assets/images/find_friends_illustration.webp')}
style={[a.w_full, {aspectRatio: 1278 / 661}]}
alt={_(
msg`An illustration depicting user avatars flowing from a contact book into the Bluesky app`,
)}
/>
</View>
</View>
<View style={[a.align_center, a.px_xl, a.gap_5xl]}>
<View style={[a.gap_sm, a.align_center]}>
<Text
style={[
a.text_4xl,
a.leading_tight,
a.font_bold,
a.text_center,
{
fontSize: isWeb ? 28 : 32,
maxWidth: 300,
},
]}>
<Trans>Find your friends</Trans>
</Text>
<Text
style={[
a.text_md,
t.atoms.text_contrast_medium,
a.leading_snug,
a.text_center,
{maxWidth: 340},
]}>
<Trans>
Bluesky is more fun with friends! Import your contacts to see
who’s already here.
</Trans>
</Text>
</View>
<Button
label={_(msg`Import Contacts`)}
size="large"
color="primary"
onPress={() => {
control.close(() => {
navigate('FindContactsFlow')
})
}}
style={[a.w_full]}>
<ButtonText>
<Trans>Import Contacts</Trans>
</ButtonText>
</Button>
</View>
<Dialog.Close />
</Dialog.ScrollableInner>
</Dialog.Outer>
)
}
+23 -14
View File
@@ -1,4 +1,11 @@
import React from 'react' import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react'
import {type AppBskyActorDefs} from '@atproto/api' import {type AppBskyActorDefs} from '@atproto/api'
import {useGate} from '#/lib/statsig/statsig' import {useGate} from '#/lib/statsig/statsig'
@@ -12,11 +19,11 @@ import {
import {useProfileQuery} from '#/state/queries/profile' import {useProfileQuery} from '#/state/queries/profile'
import {type SessionAccount, useSession} from '#/state/session' import {type SessionAccount, useSession} from '#/state/session'
import {useOnboardingState} from '#/state/shell' import {useOnboardingState} from '#/state/shell'
import {BookmarksAnnouncement} from '#/components/dialogs/nuxs/BookmarksAnnouncement' import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
/* /*
* NUXs * NUXs
*/ */
import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing' import {FindContactsAnnouncement} from './FindContactsAnnouncement'
import {isExistingUserAsOf} from './utils' import {isExistingUserAsOf} from './utils'
type Context = { type Context = {
@@ -34,24 +41,24 @@ const queuedNuxs: {
}) => boolean }) => boolean
}[] = [ }[] = [
{ {
id: Nux.BookmarksAnnouncement, id: Nux.FindContactsAnnouncement,
enabled: ({currentProfile}) => { enabled: ({currentProfile}) => {
return isExistingUserAsOf( return isExistingUserAsOf(
'2025-09-08T00:00:00.000Z', '2025-12-16T00:00:00.000Z',
currentProfile.createdAt, currentProfile.createdAt,
) )
}, },
}, },
] ]
const Context = React.createContext<Context>({ const Context = createContext<Context>({
activeNux: undefined, activeNux: undefined,
dismissActiveNux: () => {}, dismissActiveNux: () => {},
}) })
Context.displayName = 'NuxDialogContext' Context.displayName = 'NuxDialogContext'
export function useNuxDialogContext() { export function useNuxDialogContext() {
return React.useContext(Context) return useContext(Context)
} }
export function NuxDialogs() { export function NuxDialogs() {
@@ -92,19 +99,19 @@ function Inner({
}) { }) {
const gate = useGate() const gate = useGate()
const {nuxs} = useNuxs() const {nuxs} = useNuxs()
const [snoozed, setSnoozed] = React.useState(() => { const [snoozed, setSnoozed] = useState(() => {
return isSnoozed() return isSnoozed()
}) })
const [activeNux, setActiveNux] = React.useState<Nux | undefined>() const [activeNux, setActiveNux] = useState<Nux | undefined>()
const {mutateAsync: saveNux} = useSaveNux() const {mutateAsync: saveNux} = useSaveNux()
const {mutate: resetNuxs} = useResetNuxs() const {mutate: resetNuxs} = useResetNuxs()
const snoozeNuxDialog = React.useCallback(() => { const snoozeNuxDialog = useCallback(() => {
snooze() snooze()
setSnoozed(true) setSnoozed(true)
}, [setSnoozed]) }, [setSnoozed])
const dismissActiveNux = React.useCallback(() => { const dismissActiveNux = useCallback(() => {
if (!activeNux) return if (!activeNux) return
setActiveNux(undefined) setActiveNux(undefined)
}, [activeNux, setActiveNux]) }, [activeNux, setActiveNux])
@@ -118,7 +125,7 @@ function Inner({
} }
} }
React.useEffect(() => { useEffect(() => {
if (snoozed) return // comment this out to test if (snoozed) return // comment this out to test
if (!nuxs) return if (!nuxs) return
@@ -170,7 +177,7 @@ function Inner({
preferences, preferences,
]) ])
const ctx = React.useMemo(() => { const ctx = useMemo(() => {
return { return {
activeNux, activeNux,
dismissActiveNux, dismissActiveNux,
@@ -180,7 +187,9 @@ function Inner({
return ( return (
<Context.Provider value={ctx}> <Context.Provider value={ctx}>
{/*For example, activeNux === Nux.NeueTypography && <NeueTypography />*/} {/*For example, activeNux === Nux.NeueTypography && <NeueTypography />*/}
{activeNux === Nux.BookmarksAnnouncement && <BookmarksAnnouncement />} {activeNux === Nux.FindContactsAnnouncement && (
<FindContactsAnnouncement />
)}
</Context.Provider> </Context.Provider>
) )
} }
+4
View File
@@ -54,6 +54,10 @@ export type AppNux = BaseNux<
id: Nux.BookmarksAnnouncement id: Nux.BookmarksAnnouncement
data: undefined data: undefined
} }
| {
id: Nux.FindContactsAnnouncement
data: undefined
}
| { | {
id: Nux.FindContactsDismissibleBanner id: Nux.FindContactsDismissibleBanner
data: undefined data: undefined