Mount message dialogs once at the list level (#10435)

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
DS Boyce
2026-06-03 09:28:11 -07:00
committed by GitHub
parent b6650d8d5c
commit 1b4c72f516
9 changed files with 420 additions and 278 deletions
+3 -1
View File
@@ -224,6 +224,7 @@ export function Basic({
cancelButtonCta, cancelButtonCta,
confirmButtonCta, confirmButtonCta,
onConfirm, onConfirm,
onClose,
confirmButtonColor, confirmButtonColor,
showCancel = true, showCancel = true,
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
@@ -240,11 +241,12 @@ export function Basic({
* should NOT close the dialog as a side effect of this method. * should NOT close the dialog as a side effect of this method.
*/ */
onConfirm: (e: GestureResponderEvent) => void onConfirm: (e: GestureResponderEvent) => void
onClose?: () => void
confirmButtonColor?: ButtonColor confirmButtonColor?: ButtonColor
showCancel?: boolean showCancel?: boolean
}>) { }>) {
return ( return (
<Outer control={control} testID="confirmModal"> <Outer control={control} testID="confirmModal" onClose={onClose}>
<Content> <Content>
<TitleText>{title}</TitleText> <TitleText>{title}</TitleText>
{description && <DescriptionText>{description}</DescriptionText>} {description && <DescriptionText>{description}</DescriptionText>}
+6 -1
View File
@@ -33,14 +33,19 @@ export const AfterReportDialog = memo(function BlockOrDeleteDialogInner({
control, control,
params, params,
currentScreen, currentScreen,
onClose,
}: { }: {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
params: ReportDialogParams params: ReportDialogParams
currentScreen: 'list' | 'conversation' currentScreen: 'list' | 'conversation'
onClose?: () => void
}): React.ReactNode { }): React.ReactNode {
const {t: l} = useLingui() const {t: l} = useLingui()
return ( return (
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> <Dialog.Outer
control={control}
onClose={onClose}
nativeOptions={{preventExpansion: true}}>
<Dialog.Handle /> <Dialog.Handle />
<Dialog.ScrollableInner <Dialog.ScrollableInner
label={l`Would you like to block this user and/or delete this conversation?`} label={l`Would you like to block this user and/or delete this conversation?`}
+5 -53
View File
@@ -1,5 +1,5 @@
import {memo, useCallback} from 'react' import {memo, useCallback} from 'react'
import {LayoutAnimation, Platform} from 'react-native' import {Platform} from 'react-native'
import * as Clipboard from 'expo-clipboard' import * as Clipboard from 'expo-clipboard'
import { import {
type ChatBskyConvoDefs, type ChatBskyConvoDefs,
@@ -7,26 +7,21 @@ import {
RichText, RichText,
} from '@atproto/api' } from '@atproto/api'
import {useLingui} from '@lingui/react/macro' import {useLingui} from '@lingui/react/macro'
import {useQueryClient} from '@tanstack/react-query'
import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate' import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate'
import {richTextToString} from '#/lib/strings/rich-text-helpers' import {richTextToString} from '#/lib/strings/rich-text-helpers'
import {useMaybeProfileShadow} from '#/state/cache/profile-shadow' import {useMaybeProfileShadow} from '#/state/cache/profile-shadow'
import {useConvoActive} from '#/state/messages/convo' import {useConvoActive} from '#/state/messages/convo'
import {useLanguagePrefs} from '#/state/preferences' import {useLanguagePrefs} from '#/state/preferences'
import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {atoms as a} from '#/alf' import {atoms as a} from '#/alf'
import * as ContextMenu from '#/components/ContextMenu' import * as ContextMenu from '#/components/ContextMenu'
import {type TriggerProps} from '#/components/ContextMenu/types' import {type TriggerProps} from '#/components/ContextMenu/types'
import {AfterReportDialog} from '#/components/dms/AfterReportDialog' import {useMessageDialogs} from '#/components/dms/MessageOverlays'
import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard'
import {Flag_Stroke2_Corner0_Rounded as FlagIcon} from '#/components/icons/Flag' import {Flag_Stroke2_Corner0_Rounded as FlagIcon} from '#/components/icons/Flag'
import {Language_Stroke2_Corner2_Rounded as LanguageIcon} from '#/components/icons/Language' import {Language_Stroke2_Corner2_Rounded as LanguageIcon} from '#/components/icons/Language'
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
import {ReportDialog} from '#/components/moderation/ReportDialog'
import * as Prompt from '#/components/Prompt'
import {usePromptControl} from '#/components/Prompt'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env' import {IS_NATIVE} from '#/env'
@@ -48,11 +43,8 @@ export let MessageContextMenu = ({
const {t: l, i18n} = useLingui() const {t: l, i18n} = useLingui()
const ax = useAnalytics() const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const queryClient = useQueryClient()
const convo = useConvoActive() const convo = useConvoActive()
const deleteControl = usePromptControl() const {openDeleteMessage, openReportMessage} = useMessageDialogs()
const reportControl = usePromptControl()
const blockOrDeleteControl = usePromptControl()
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
const translate = useGoogleTranslate() const translate = useGoogleTranslate()
@@ -93,14 +85,6 @@ export let MessageContextMenu = ({
}) })
}, [ax, langPrefs.primaryLanguage, message.text, translate]) }, [ax, langPrefs.primaryLanguage, message.text, translate])
const onDelete = useCallback(() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
convo
.deleteMessage(message.id)
.then(() => Toast.show(l({message: 'Message deleted', context: 'toast'})))
.catch(() => Toast.show(l`Failed to delete message`))
}, [l, convo, message.id])
const onEmojiSelect = useCallback( const onEmojiSelect = useCallback(
(emoji: string) => { (emoji: string) => {
if ( if (
@@ -128,7 +112,6 @@ export let MessageContextMenu = ({
const sender = senderProfile const sender = senderProfile
return ( return (
<>
<ContextMenu.Root> <ContextMenu.Root>
{IS_NATIVE && reactionsAvailable && ( {IS_NATIVE && reactionsAvailable && (
<ContextMenu.AuxiliaryView <ContextMenu.AuxiliaryView
@@ -179,7 +162,7 @@ export let MessageContextMenu = ({
destructive destructive
testID="messageDropdownDeleteBtn" testID="messageDropdownDeleteBtn"
label={l`Delete message for me`} label={l`Delete message for me`}
onPress={() => deleteControl.open()}> onPress={() => openDeleteMessage(message)}>
<ContextMenu.ItemIcon icon={TrashIcon} position="left" /> <ContextMenu.ItemIcon icon={TrashIcon} position="left" />
<ContextMenu.ItemText>{l`Delete for me`}</ContextMenu.ItemText> <ContextMenu.ItemText>{l`Delete for me`}</ContextMenu.ItemText>
</ContextMenu.Item> </ContextMenu.Item>
@@ -188,44 +171,13 @@ export let MessageContextMenu = ({
destructive destructive
testID="messageDropdownReportBtn" testID="messageDropdownReportBtn"
label={l`Report message`} label={l`Report message`}
onPress={() => reportControl.open()}> onPress={() => openReportMessage(message, senderProfile)}>
<ContextMenu.ItemIcon icon={FlagIcon} position="left" /> <ContextMenu.ItemIcon icon={FlagIcon} position="left" />
<ContextMenu.ItemText>{l`Report`}</ContextMenu.ItemText> <ContextMenu.ItemText>{l`Report`}</ContextMenu.ItemText>
</ContextMenu.Item> </ContextMenu.Item>
)} )}
</ContextMenu.Outer> </ContextMenu.Outer>
</ContextMenu.Root> </ContextMenu.Root>
<ReportDialog
control={reportControl}
subject={{
view: 'message',
convoId: convo.convo.view.id,
message,
}}
onAfterSubmit={() => {
if (sender) {
unstableCacheProfileView(queryClient, sender)
}
blockOrDeleteControl.open()
}}
/>
<AfterReportDialog
control={blockOrDeleteControl}
currentScreen="conversation"
params={{
convoId: convo.convo.view.id,
did: message.sender.did,
}}
/>
<Prompt.Basic
control={deleteControl}
title={l`Delete message`}
description={l`Are you sure you want to delete this message? The message will be deleted for you, but not for the other participants.`}
confirmButtonCta={l`Delete`}
confirmButtonColor="negative"
onConfirm={onDelete}
/>
</>
) )
} }
MessageContextMenu = memo(MessageContextMenu) MessageContextMenu = memo(MessageContextMenu)
+8 -39
View File
@@ -40,8 +40,8 @@ import {useSession} from '#/state/session'
import {atoms as a, native, platform, useTheme} from '#/alf' import {atoms as a, native, platform, useTheme} from '#/alf'
import {isOnlyEmoji} from '#/alf/typography' import {isOnlyEmoji} from '#/alf/typography'
import {Button} from '#/components/Button' import {Button} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import {ActionsWrapper} from '#/components/dms/ActionsWrapper' import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
import {useMessageDialogs} from '#/components/dms/MessageOverlays'
import {InlineLinkText, Link} from '#/components/Link' import {InlineLinkText, Link} from '#/components/Link'
import * as ProfileCard from '#/components/ProfileCard' import * as ProfileCard from '#/components/ProfileCard'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
@@ -49,7 +49,7 @@ import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {DateDivider} from './DateDivider' import {DateDivider} from './DateDivider'
import {MessageItemEmbed} from './MessageItemEmbed' import {MessageItemEmbed} from './MessageItemEmbed'
import {ReactionsDialog} from './ReactionsDialog' import {groupReactions} from './ReactionsDialog'
import {CLUSTERED_MESSAGE_THRESHOLD_MS, MESSAGE_GAP_THRESHOLD_MS} from './util' import {CLUSTERED_MESSAGE_THRESHOLD_MS, MESSAGE_GAP_THRESHOLD_MS} from './util'
const AVATAR_SIZE = 28 const AVATAR_SIZE = 28
@@ -118,7 +118,7 @@ let MessageItem = ({
const {message} = item const {message} = item
const profile = useMaybeProfileShadow(relatedProfiles.get(message.sender.did)) const profile = useMaybeProfileShadow(relatedProfiles.get(message.sender.did))
const reactionsControl = useDialogControl() const {openReactions} = useMessageDialogs()
const isPending = item.type === 'pending-message' const isPending = item.type === 'pending-message'
@@ -243,34 +243,10 @@ let MessageItem = ({
<ProfileCard.AvatarPlaceholder size={AVATAR_SIZE} /> <ProfileCard.AvatarPlaceholder size={AVATAR_SIZE} />
) )
const groupedReactions = useMemo(() => { const groupedReactions = useMemo(
const reactions = message.reactions ?? [] () => groupReactions(message.reactions),
const grouped = new Map< [message.reactions],
string, )
{
key: string
value: string
senders: ChatBskyConvoDefs.ReactionViewSender[]
count: number
}
>()
for (const reaction of reactions) {
if (!reaction) continue
const existing = grouped.get(reaction.value)
if (existing) {
existing.senders.push(reaction.sender)
existing.count++
} else {
grouped.set(reaction.value, {
key: reaction.value,
value: reaction.value,
senders: [reaction.sender],
count: 1,
})
}
}
return Array.from(grouped.values())
}, [message.reactions])
const reactions = useMemo(() => message.reactions ?? [], [message.reactions]) const reactions = useMemo(() => message.reactions ?? [], [message.reactions])
@@ -336,7 +312,7 @@ let MessageItem = ({
transform: [{translateY: -8}], transform: [{translateY: -8}],
}, },
]} ]}
onPress={isGroupChat ? reactionsControl.open : undefined}> onPress={isGroupChat ? () => openReactions(message) : undefined}>
{groupedReactions.map(group => ( {groupedReactions.map(group => (
<Animated.View <Animated.View
entering={native(ZoomIn.springify(200).delay(400))} entering={native(ZoomIn.springify(200).delay(400))}
@@ -377,13 +353,6 @@ let MessageItem = ({
</Pressable> </Pressable>
</View> </View>
) : null} ) : null}
<ReactionsDialog
control={reactionsControl}
relatedProfiles={relatedProfiles}
message={message}
reactions={message.reactions}
groupedReactions={groupedReactions}
/>
</LayoutAnimationConfig> </LayoutAnimationConfig>
) )
+175
View File
@@ -0,0 +1,175 @@
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react'
import {LayoutAnimation} from 'react-native'
import {type ChatBskyConvoDefs} from '@atproto/api'
import {useLingui} from '@lingui/react/macro'
import {useQueryClient} from '@tanstack/react-query'
import {useConvoActive} from '#/state/messages/convo'
import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache'
import {useDialogControl} from '#/components/Dialog'
import {AfterReportDialog} from '#/components/dms/AfterReportDialog'
import {ReactionsDialog} from '#/components/dms/ReactionsDialog'
import {ReportDialog} from '#/components/moderation/ReportDialog'
import * as Prompt from '#/components/Prompt'
import {usePromptControl} from '#/components/Prompt'
import * as Toast from '#/components/Toast'
import type * as bsky from '#/types/bsky'
type MessageDialogsContextType = {
openDeleteMessage: (message: ChatBskyConvoDefs.MessageView) => void
openReportMessage: (
message: ChatBskyConvoDefs.MessageView,
senderProfile: bsky.profile.AnyProfileView | undefined,
) => void
openReactions: (message: ChatBskyConvoDefs.MessageView) => void
}
const Context = createContext<MessageDialogsContextType | null>(null)
export function useMessageDialogs() {
const ctx = useContext(Context)
if (!ctx) {
throw new Error('useMessageDialogs must be used within a MessageOverlays')
}
return ctx
}
export function MessageOverlays({children}: {children: React.ReactNode}) {
const {t: l} = useLingui()
const queryClient = useQueryClient()
const convo = useConvoActive()
const deleteControl = usePromptControl()
const reportControl = usePromptControl()
const afterReportControl = usePromptControl()
const reactionsControl = useDialogControl()
const [deleteTarget, setDeleteTarget] =
useState<ChatBskyConvoDefs.MessageView | null>(null)
const [reportTarget, setReportTarget] = useState<{
message: ChatBskyConvoDefs.MessageView
senderProfile: bsky.profile.AnyProfileView | undefined
} | null>(null)
const [afterReportTarget, setAfterReportTarget] =
useState<ChatBskyConvoDefs.MessageView | null>(null)
const [reactionsTarget, setReactionsTarget] =
useState<ChatBskyConvoDefs.MessageView | null>(null)
const openDeleteMessage = useCallback(
(message: ChatBskyConvoDefs.MessageView) => {
setDeleteTarget(message)
deleteControl.open()
},
[deleteControl],
)
const openReportMessage = useCallback(
(
message: ChatBskyConvoDefs.MessageView,
senderProfile: bsky.profile.AnyProfileView | undefined,
) => {
setReportTarget({message, senderProfile})
reportControl.open()
},
[reportControl],
)
const openReactions = useCallback(
(message: ChatBskyConvoDefs.MessageView) => {
setReactionsTarget(message)
},
[],
)
// These dialogs are conditionally mounted, so we can't open them in the same
// tick that we set their targets - the control refs aren't attached yet. Open
// in an effect after the dialog has mounted.
useEffect(() => {
if (reactionsTarget) {
reactionsControl.open()
}
}, [reactionsTarget, reactionsControl])
useEffect(() => {
if (afterReportTarget) {
afterReportControl.open()
}
}, [afterReportTarget, afterReportControl])
const onConfirmDelete = useCallback(() => {
if (!deleteTarget) return
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
convo
.deleteMessage(deleteTarget.id)
.then(() => Toast.show(l({message: 'Message deleted', context: 'toast'})))
.catch(() => Toast.show(l`Failed to delete message`))
}, [l, convo, deleteTarget])
const onAfterReportSubmit = useCallback(() => {
if (!reportTarget) return
if (reportTarget.senderProfile) {
unstableCacheProfileView(queryClient, reportTarget.senderProfile)
}
setAfterReportTarget(reportTarget.message)
}, [queryClient, reportTarget])
const ctx = useMemo<MessageDialogsContextType>(
() => ({openDeleteMessage, openReportMessage, openReactions}),
[openDeleteMessage, openReportMessage, openReactions],
)
const reportSubject = reportTarget
? ({
view: 'message',
convoId: convo.convo.view.id,
message: reportTarget.message,
} as const)
: undefined
return (
<Context.Provider value={ctx}>
{children}
<ReportDialog
control={reportControl}
subject={reportSubject}
onAfterSubmit={onAfterReportSubmit}
onClose={() => setReportTarget(null)}
/>
{afterReportTarget && (
<AfterReportDialog
control={afterReportControl}
currentScreen="conversation"
params={{
convoId: convo.convo.view.id,
did: afterReportTarget.sender.did,
}}
onClose={() => setAfterReportTarget(null)}
/>
)}
{reactionsTarget && (
<ReactionsDialog
control={reactionsControl}
relatedProfiles={convo.relatedProfiles}
message={reactionsTarget}
onClose={() => setReactionsTarget(null)}
/>
)}
<Prompt.Basic
control={deleteControl}
title={l`Delete message`}
description={l`Are you sure you want to delete this message? The message will be deleted for you, but not for the other participants.`}
confirmButtonCta={l`Delete`}
confirmButtonColor="negative"
onConfirm={onConfirmDelete}
onClose={() => setDeleteTarget(null)}
/>
</Context.Provider>
)
}
+32 -6
View File
@@ -1,4 +1,4 @@
import {useRef, useState} from 'react' import {useMemo, useRef, useState} from 'react'
import { import {
LayoutAnimation, LayoutAnimation,
Pressable, Pressable,
@@ -37,14 +37,12 @@ export function ReactionsDialog({
control, control,
relatedProfiles, relatedProfiles,
message, message,
reactions, onClose,
groupedReactions,
}: { }: {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic> relatedProfiles: Map<string, ChatBskyActorDefs.ProfileViewBasic>
message: ChatBskyConvoDefs.MessageView message: ChatBskyConvoDefs.MessageView
reactions?: ChatBskyConvoDefs.ReactionView[] onClose?: () => void
groupedReactions?: Reaction[]
}) { }) {
const {t: l} = useLingui() const {t: l} = useLingui()
@@ -54,6 +52,9 @@ export function ReactionsDialog({
const [selected, setSelected] = useState('all') const [selected, setSelected] = useState('all')
const reactions = message.reactions
const groupedReactions = useMemo(() => groupReactions(reactions), [reactions])
const filteredReactions = reactions?.filter( const filteredReactions = reactions?.filter(
r => selected === 'all' || r.value === selected, r => selected === 'all' || r.value === selected,
) )
@@ -78,7 +79,10 @@ export function ReactionsDialog({
return ( return (
<Dialog.Outer <Dialog.Outer
control={control} control={control}
onClose={() => setSelected('all')} onClose={() => {
setSelected('all')
onClose?.()
}}
nativeOptions={{ nativeOptions={{
preventExpansion: true, preventExpansion: true,
minHeight: screenHeight / 2, minHeight: screenHeight / 2,
@@ -388,3 +392,25 @@ function ReactionTab({
</Pressable> </Pressable>
) )
} }
export function groupReactions(
reactions: ChatBskyConvoDefs.ReactionView[] | undefined,
): Reaction[] {
const grouped = new Map<string, Reaction>()
for (const reaction of reactions ?? []) {
if (!reaction) continue
const existing = grouped.get(reaction.value)
if (existing) {
existing.senders.push(reaction.sender)
existing.count++
} else {
grouped.set(reaction.value, {
key: reaction.value,
value: reaction.value,
senders: [reaction.sender],
count: 1,
})
}
}
return Array.from(grouped.values())
}
@@ -75,9 +75,11 @@ export function ReportDialog(
() => (props.subject ? parseReportSubject(props.subject) : undefined), () => (props.subject ? parseReportSubject(props.subject) : undefined),
[props.subject], [props.subject],
) )
const propsOnClose = props.onClose
const onClose = useCallback(() => { const onClose = useCallback(() => {
ax.metric('reportDialog:close', {}) ax.metric('reportDialog:close', {})
}, [ax]) propsOnClose?.()
}, [ax, propsOnClose])
return ( return (
<Dialog.Outer control={props.control} onClose={onClose}> <Dialog.Outer control={props.control} onClose={onClose}>
<Dialog.Handle /> <Dialog.Handle />
@@ -88,4 +88,8 @@ export type ReportDialogProps = {
* Called if the report was successfully submitted. * Called if the report was successfully submitted.
*/ */
onAfterSubmit?: () => void onAfterSubmit?: () => void
/**
* Called after the dialog finishes closing.
*/
onClose?: () => void
} }
@@ -56,6 +56,7 @@ import {MessageListError} from '#/screens/Messages/components/MessageListError'
import {atoms as a, platform, tokens, useTheme, web} from '#/alf' import {atoms as a, platform, tokens, useTheme, web} from '#/alf'
import {DateDivider} from '#/components/dms/DateDivider' import {DateDivider} from '#/components/dms/DateDivider'
import {MessageItem} from '#/components/dms/MessageItem' import {MessageItem} from '#/components/dms/MessageItem'
import {MessageOverlays} from '#/components/dms/MessageOverlays'
import {NewMessagesPill} from '#/components/dms/NewMessagesPill' import {NewMessagesPill} from '#/components/dms/NewMessagesPill'
import {SystemMessageGroup} from '#/components/dms/SystemMessageGroup' import {SystemMessageGroup} from '#/components/dms/SystemMessageGroup'
import {SystemMessageItem} from '#/components/dms/SystemMessageItem' import {SystemMessageItem} from '#/components/dms/SystemMessageItem'
@@ -498,6 +499,7 @@ export function MessagesList({
return ( return (
<InviteLinkDialogProvider convo={convoState.convo}> <InviteLinkDialogProvider convo={convoState.convo}>
<MessageOverlays>
<KeyboardGestureArea <KeyboardGestureArea
interpolator="ios" interpolator="ios"
// HACKFIX: https://github.com/kirillzyusko/react-native-keyboard-controller/issues/1419 // HACKFIX: https://github.com/kirillzyusko/react-native-keyboard-controller/issues/1419
@@ -585,7 +587,9 @@ export function MessagesList({
convoState={convoState} convoState={convoState}
hasAcceptOverride={hasAcceptOverride}> hasAcceptOverride={hasAcceptOverride}>
{({loading}) => {({loading}) =>
ax.features.enabled(ax.features.DmsNewMessageComposerEnable) ? ( ax.features.enabled(
ax.features.DmsNewMessageComposerEnable,
) ? (
<MessageComposer <MessageComposer
textInputId={textInputId} textInputId={textInputId}
onSendMessage={(message: string) => onSendMessage={(message: string) =>
@@ -618,7 +622,10 @@ export function MessagesList({
</KeyboardStickyView> </KeyboardStickyView>
</KeyboardGestureArea> </KeyboardGestureArea>
{newMessagesPill.show && <NewMessagesPill onPress={scrollToEndOnPress} />} {newMessagesPill.show && (
<NewMessagesPill onPress={scrollToEndOnPress} />
)}
</MessageOverlays>
</InviteLinkDialogProvider> </InviteLinkDialogProvider>
) )
} }