diff --git a/src/components/dialogs/nuxs/InviteFriendsAnnouncement.tsx b/src/components/dialogs/nuxs/InviteFriendsAnnouncement.tsx index 70f4057d50..1ea4cd5899 100644 --- a/src/components/dialogs/nuxs/InviteFriendsAnnouncement.tsx +++ b/src/components/dialogs/nuxs/InviteFriendsAnnouncement.tsx @@ -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 && ( + + )} + + + ) +} + +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 ( <> diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx index 444f050406..41daa8b9f0 100644 --- a/src/components/dialogs/nuxs/index.tsx +++ b/src/components/dialogs/nuxs/index.tsx @@ -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({ activeNux: undefined, dismissActiveNux: () => {}, - openInviteFriends: () => {}, }) Context.displayName = 'NuxDialogContext' @@ -115,7 +105,6 @@ function Inner({ const [activeNux, setActiveNux] = useState() 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 ( {/*For example, activeNux === Nux.NeueTypography && */} {activeNux === Nux.DraftsAnnouncement && } - {activeNux === Nux.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. */} - + ) }