Colocate invite dialog handoff in InviteFriendsAnnouncement instead of NUX context

This commit is contained in:
vineyardbovines
2026-06-09 16:25:35 -04:00
parent 19f04777c9
commit dad13d3c0a
2 changed files with 41 additions and 30 deletions
@@ -4,6 +4,7 @@ import {Image} from 'expo-image'
import {Trans, useLingui} from '@lingui/react/macro'
import {useCallOnce} from '#/lib/once'
import {Nux} from '#/state/queries/nuxs'
import {atoms as a, useTheme, web} from '#/alf'
import {themes} from '#/alf/themes'
import {Button, ButtonText} from '#/components/Button'
@@ -13,6 +14,7 @@ import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_E2E, IS_NATIVE, IS_WEB} from '#/env'
import {InviteFriendsDialog} from '#/features/inviteFriends'
import {createIsEnabledCheck} from './utils'
export const enabled = createIsEnabledCheck(() => {
@@ -22,6 +24,30 @@ export const enabled = createIsEnabledCheck(() => {
})
export function InviteFriendsAnnouncement() {
const {activeNux} = useNuxDialogContext()
/*
* The invite dialog's control lives here (outside the activeNux
* conditional) so the dialog survives the announcement being dismissed
* during the "Try it" handoff - the native bottom sheet cannot hand off to
* a second sheet that unmounts along with the first.
*/
const inviteFriendsControl = Dialog.useDialogControl()
return (
<>
{activeNux === Nux.InviteFriendsAnnouncement && (
<Announcement inviteFriendsControl={inviteFriendsControl} />
)}
<InviteFriendsDialog control={inviteFriendsControl} />
</>
)
}
function Announcement({
inviteFriendsControl,
}: {
inviteFriendsControl: Dialog.DialogControlProps
}) {
const t = useTheme()
const {t: l} = useLingui()
const ax = useAnalytics()
@@ -43,15 +69,18 @@ export function InviteFriendsAnnouncement() {
const onPressTryIt = useCallback(() => {
ax.metric('invite:nux:tryItPressed', {})
// Close this announcement (which dismisses + unmounts the NUX), then open
// the invite dialog. The invite dialog is mounted persistently by NuxDialogs
// (not here) so it survives the dismissal - the native bottom sheet cannot
// hand off to a second sheet mounted in this same subtree. Defer the open to
// the next frame so the announcement's teardown completes first.
// Close this announcement (which dismisses the NUX and unmounts this
// component), then open the invite dialog. Its control lives in the
// parent, which stays mounted, so the dialog survives the dismissal.
// Defer the open to the next frame so the announcement's teardown
// completes first.
control.close(() => {
requestAnimationFrame(() => nuxDialogs.openInviteFriends())
requestAnimationFrame(() => {
ax.metric('invite:dialog:open', {logContext: 'NuxAnnouncement'})
inviteFriendsControl.open()
})
})
}, [ax, control, nuxDialogs])
}, [ax, control, inviteFriendsControl])
return (
<>
+5 -23
View File
@@ -18,7 +18,6 @@ import {
import {useProfileQuery} from '#/state/queries/profile'
import {type SessionAccount, useSession} from '#/state/session'
import {useOnboardingState} from '#/state/shell'
import * as Dialog from '#/components/Dialog'
import {
DraftsAnnouncement,
enabled as isDraftsAnnouncementEnabled,
@@ -30,19 +29,11 @@ import {
import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
import {type EnabledCheckProps} from '#/components/dialogs/nuxs/utils'
import {useAnalytics} from '#/analytics'
import {InviteFriendsDialog} from '#/features/inviteFriends'
import {useGeolocation} from '#/geolocation'
type Context = {
activeNux: Nux | undefined
dismissActiveNux: () => void
/**
* Opens the invite-friends dialog. It is mounted persistently by NuxDialogs
* (not inside the announcement NUX) so it survives the announcement being
* dismissed - the native bottom sheet cannot hand off between two sheets
* mounted in the same subtree.
*/
openInviteFriends: () => void
}
const queuedNuxs: {
@@ -62,7 +53,6 @@ const queuedNuxs: {
const Context = createContext<Context>({
activeNux: undefined,
dismissActiveNux: () => {},
openInviteFriends: () => {},
})
Context.displayName = 'NuxDialogContext'
@@ -115,7 +105,6 @@ function Inner({
const [activeNux, setActiveNux] = useState<Nux | undefined>()
const {mutateAsync: saveNux} = useSaveNux()
const {mutate: resetNuxs} = useResetNuxs()
const inviteFriendsControl = Dialog.useDialogControl()
const snoozeNuxDialog = useCallback(() => {
snooze()
@@ -199,26 +188,19 @@ function Inner({
return {
activeNux,
dismissActiveNux,
openInviteFriends: () => {
ax.metric('invite:dialog:open', {logContext: 'NuxAnnouncement'})
inviteFriendsControl.open()
},
}
}, [ax, activeNux, dismissActiveNux, inviteFriendsControl])
}, [activeNux, dismissActiveNux])
return (
<Context.Provider value={ctx}>
{/*For example, activeNux === Nux.NeueTypography && <NeueTypography />*/}
{activeNux === Nux.DraftsAnnouncement && <DraftsAnnouncement />}
{activeNux === Nux.InviteFriendsAnnouncement && (
<InviteFriendsAnnouncement />
)}
{/*
Mounted persistently (not inside the announcement) so the invite dialog
survives the announcement NUX being dismissed during the "Try it"
handoff.
Mounted unconditionally: it gates the announcement on `activeNux`
internally, so it can keep the invite-friends dialog mounted across
the announcement's dismissal during the "Try it" handoff.
*/}
<InviteFriendsDialog control={inviteFriendsControl} />
<InviteFriendsAnnouncement />
</Context.Provider>
)
}