diff --git a/src/Navigation.tsx b/src/Navigation.tsx index f68f8ed6..7abfaec0 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -464,7 +464,10 @@ function MessagesTabNavigator() { MessagesScreen} - options={{requireAuth: true}} + options={({route}) => ({ + requireAuth: true, + animationTypeForReplace: route.params?.animation ?? 'push', + })} /> {commonScreens(MessagesTab as typeof HomeTab)} diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx index 92e848e8..d05cab5a 100644 --- a/src/components/Prompt.tsx +++ b/src/components/Prompt.tsx @@ -172,6 +172,7 @@ export function Basic({ confirmButtonCta, onConfirm, confirmButtonColor, + showCancel = true, }: React.PropsWithChildren<{ control: Dialog.DialogOuterProps['control'] title: string @@ -187,6 +188,7 @@ export function Basic({ */ onConfirm: () => void confirmButtonColor?: ButtonColor + showCancel?: boolean }>) { return ( @@ -199,7 +201,7 @@ export function Basic({ color={confirmButtonColor} testID="confirmBtn" /> - + {showCancel && } ) diff --git a/src/components/dms/BlockedByListDialog.tsx b/src/components/dms/BlockedByListDialog.tsx new file mode 100644 index 00000000..a2770160 --- /dev/null +++ b/src/components/dms/BlockedByListDialog.tsx @@ -0,0 +1,62 @@ +import React from 'react' +import {View} from 'react-native' +import {ModerationCause} from '@atproto/api' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {listUriToHref} from 'lib/strings/url-helpers' +import {atoms as a, useTheme} from '#/alf' +import * as Dialog from '#/components/Dialog' +import {DialogControlProps} from '#/components/Dialog' +import {InlineLinkText} from '#/components/Link' +import * as Prompt from '#/components/Prompt' +import {Text} from '#/components/Typography' + +export function BlockedByListDialog({ + control, + listBlocks, +}: { + control: DialogControlProps + listBlocks: ModerationCause[] +}) { + const {_} = useLingui() + const t = useTheme() + + return ( + + {_(msg`User blocked by list`)} + + + + {_( + msg`This account is blocked by one or more of your moderation lists. To unblock, please visit the lists directly and remove this user.`, + )}{' '} + + + + {_(msg`Lists blocking this user:`)}{' '} + {listBlocks.map((block, i) => + block.source.type === 'list' ? ( + + {i === 0 ? null : ', '} + + {block.source.list.name} + + + ) : null, + )} + + + + + {}} /> + + + + + ) +} diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx index cf1dbc17..0e5cd12b 100644 --- a/src/components/dms/ConvoMenu.tsx +++ b/src/components/dms/ConvoMenu.tsx @@ -3,25 +3,25 @@ import {Keyboard, Pressable, View} from 'react-native' import { AppBskyActorDefs, ChatBskyConvoDefs, - ModerationDecision, + ModerationCause, } from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {NavigationProp} from '#/lib/routes/types' -import {listUriToHref} from '#/lib/strings/url-helpers' import {Shadow} from '#/state/cache/types' import { useConvoQuery, useMarkAsReadMutation, } from '#/state/queries/messages/conversation' -import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' import {useMuteConvo} from '#/state/queries/messages/mute-conversation' import {useProfileBlockMutationQueue} from '#/state/queries/profile' import * as Toast from '#/view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' -import * as Dialog from '#/components/Dialog' +import {BlockedByListDialog} from '#/components/dms/BlockedByListDialog' +import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt' +import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt' import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeft} from '#/components/icons/ArrowBoxLeft' import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' @@ -30,10 +30,8 @@ import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Perso import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheck} from '#/components/icons/PersonCheck' import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/PersonX' import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' -import {InlineLinkText} from '#/components/Link' import * as Menu from '#/components/Menu' import * as Prompt from '#/components/Prompt' -import {Text} from '#/components/Typography' import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '../icons/Bubble' let ConvoMenu = ({ @@ -44,7 +42,7 @@ let ConvoMenu = ({ showMarkAsRead, hideTrigger, triggerOpacity, - moderation, + blockInfo, }: { convo: ChatBskyConvoDefs.ConvoView profile: Shadow @@ -53,7 +51,10 @@ let ConvoMenu = ({ showMarkAsRead?: boolean hideTrigger?: boolean triggerOpacity?: number - moderation: ModerationDecision + blockInfo: { + listBlocks: ModerationCause[] + userBlock?: ModerationCause + } }): React.ReactNode => { const navigation = useNavigation() const {_} = useLingui() @@ -62,17 +63,9 @@ let ConvoMenu = ({ const reportControl = Prompt.usePromptControl() const blockedByListControl = Prompt.usePromptControl() const {mutate: markAsRead} = useMarkAsReadMutation() - const modui = moderation.ui('profileView') - const {listBlocks, userBlock} = React.useMemo(() => { - const blocks = modui.alerts.filter(alert => alert.type === 'blocking') - const listBlocks = blocks.filter(alert => alert.source.type === 'list') - const userBlock = blocks.find(alert => alert.source.type === 'user') - return { - listBlocks, - userBlock, - } - }, [modui]) - const isBlocking = !!userBlock || !!listBlocks.length + + const {listBlocks, userBlock} = blockInfo + const isBlocking = userBlock || !!listBlocks.length const {data: convo} = useConvoQuery(initialConvo) @@ -108,17 +101,6 @@ let ConvoMenu = ({ } }, [userBlock, listBlocks, blockedByListControl, queueBlock, queueUnblock]) - const {mutate: leaveConvo} = useLeaveConvo(convo?.id, { - onSuccess: () => { - if (currentScreen === 'conversation') { - navigation.replace('Messages') - } - }, - onError: () => { - Toast.show(_(msg`Could not leave chat`)) - }, - }) - return ( <> @@ -218,67 +200,19 @@ let ConvoMenu = ({ - leaveConvo()} + convoId={convo.id} + currentScreen={currentScreen} /> - - + - - - {_(msg`User blocked by list`)} - - - - {_( - msg`This account is blocked by one or more of your moderation lists. To unblock, please visit the lists directly and remove this user.`, - )}{' '} - - - - {_(msg`Lists blocking this user:`)}{' '} - {listBlocks.map((block, i) => - block.source.type === 'list' ? ( - - {i === 0 ? null : ', '} - - {block.source.list.name} - - - ) : null, - )} - - - - - - - - - ) } ConvoMenu = React.memo(ConvoMenu) export {ConvoMenu} - -function noop() {} diff --git a/src/components/dms/LeaveConvoPrompt.tsx b/src/components/dms/LeaveConvoPrompt.tsx new file mode 100644 index 00000000..1c42dbca --- /dev/null +++ b/src/components/dms/LeaveConvoPrompt.tsx @@ -0,0 +1,55 @@ +import React from 'react' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' + +import {NavigationProp} from 'lib/routes/types' +import {isNative} from 'platform/detection' +import {useLeaveConvo} from 'state/queries/messages/leave-conversation' +import * as Toast from 'view/com/util/Toast' +import {DialogOuterProps} from '#/components/Dialog' +import * as Prompt from '#/components/Prompt' + +export function LeaveConvoPrompt({ + control, + convoId, + currentScreen, +}: { + control: DialogOuterProps['control'] + convoId: string + currentScreen: 'list' | 'conversation' +}) { + const {_} = useLingui() + const navigation = useNavigation() + + const {mutate: leaveConvo} = useLeaveConvo(convoId, { + onSuccess: () => { + if (currentScreen === 'conversation') { + navigation.replace( + 'Messages', + isNative + ? { + animation: 'pop', + } + : {}, + ) + } + }, + onError: () => { + Toast.show(_(msg`Could not leave chat`)) + }, + }) + + return ( + + ) +} diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx index f456fa47..c5ff8109 100644 --- a/src/components/dms/MessageItem.tsx +++ b/src/components/dms/MessageItem.tsx @@ -75,7 +75,7 @@ let MessageItem = ({ }, [message.text, message.facets]) return ( - + { + if (listBlocks.length) { + blockedByListControl.open() + } else { + queueUnblock() + } + }, [blockedByListControl, listBlocks, queueUnblock]) + + return ( + + + + {isBlocking ? ( + You have blocked this user + ) : ( + This user has blocked you + )} + + + + + + {isBlocking && gtMobile && ( + + )} + + {isBlocking && !gtMobile && ( + + + + )} + + + + + + + + ) +} diff --git a/src/components/dms/MessagesListHeader.tsx b/src/components/dms/MessagesListHeader.tsx new file mode 100644 index 00000000..a6dff403 --- /dev/null +++ b/src/components/dms/MessagesListHeader.tsx @@ -0,0 +1,194 @@ +import React, {useCallback} from 'react' +import {TouchableOpacity, View} from 'react-native' +import { + AppBskyActorDefs, + ModerationCause, + ModerationDecision, +} from '@atproto/api' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' + +import {BACK_HITSLOP} from 'lib/constants' +import {makeProfileLink} from 'lib/routes/links' +import {NavigationProp} from 'lib/routes/types' +import {sanitizeDisplayName} from 'lib/strings/display-names' +import {isWeb} from 'platform/detection' +import {useProfileShadow} from 'state/cache/profile-shadow' +import {isConvoActive, useConvo} from 'state/messages/convo' +import {PreviewableUserAvatar} from 'view/com/util/UserAvatar' +import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' +import {ConvoMenu} from '#/components/dms/ConvoMenu' +import {Link} from '#/components/Link' +import {Text} from '#/components/Typography' + +const PFP_SIZE = isWeb ? 40 : 34 + +export let MessagesListHeader = ({ + profile, + moderation, + blockInfo, +}: { + profile?: AppBskyActorDefs.ProfileViewBasic + moderation?: ModerationDecision + blockInfo?: { + listBlocks: ModerationCause[] + userBlock?: ModerationCause + } +}): React.ReactNode => { + const t = useTheme() + const {_} = useLingui() + const {gtTablet} = useBreakpoints() + const navigation = useNavigation() + + const onPressBack = useCallback(() => { + if (isWeb) { + navigation.replace('Messages', {}) + } else { + navigation.goBack() + } + }, [navigation]) + + return ( + + {!gtTablet && ( + + + + )} + + {profile && moderation && blockInfo ? ( + + ) : ( + <> + + + + + + + + + + + )} + + ) +} +MessagesListHeader = React.memo(MessagesListHeader) + +function HeaderReady({ + profile: profileUnshadowed, + moderation, + blockInfo, +}: { + profile: AppBskyActorDefs.ProfileViewBasic + moderation: ModerationDecision + blockInfo: { + listBlocks: ModerationCause[] + userBlock?: ModerationCause + } +}) { + const t = useTheme() + const convoState = useConvo() + const profile = useProfileShadow(profileUnshadowed) + + const isDeletedAccount = profile?.handle === 'missing.invalid' + const displayName = isDeletedAccount + ? 'Deleted Account' + : sanitizeDisplayName( + profile.displayName || profile.handle, + moderation.ui('displayName'), + ) + + return ( + <> + + + + + {displayName} + + {!isDeletedAccount && ( + + @{profile.handle} + + )} + + + + {isConvoActive(convoState) && ( + + )} + + ) +} diff --git a/src/components/dms/ReportConversationPrompt.tsx b/src/components/dms/ReportConversationPrompt.tsx new file mode 100644 index 00000000..610cfbcf --- /dev/null +++ b/src/components/dms/ReportConversationPrompt.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {DialogControlProps} from '#/components/Dialog' +import * as Prompt from '#/components/Prompt' + +export function ReportConversationPrompt({ + control, +}: { + control: DialogControlProps +}) { + const {_} = useLingui() + + return ( + {}} + showCancel={false} + /> + ) +} diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 31133cb1..5011aafd 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -72,7 +72,7 @@ export type MyProfileTabNavigatorParams = CommonNavigatorParams & { } export type MessagesTabNavigatorParams = CommonNavigatorParams & { - Messages: {pushToConversation?: string} + Messages: {pushToConversation?: string; animation?: 'push' | 'pop'} } export type FlatNavigatorParams = CommonNavigatorParams & { @@ -81,7 +81,7 @@ export type FlatNavigatorParams = CommonNavigatorParams & { Feeds: undefined Notifications: undefined Hashtag: {tag: string; author?: string} - Messages: {pushToConversation?: string} + Messages: {pushToConversation?: string; animation?: 'push' | 'pop'} } export type AllNavigatorParams = CommonNavigatorParams & { @@ -96,7 +96,7 @@ export type AllNavigatorParams = CommonNavigatorParams & { MyProfileTab: undefined Hashtag: {tag: string; author?: string} MessagesTab: undefined - Messages: undefined + Messages: {animation?: 'push' | 'pop'} } // NOTE diff --git a/src/screens/Messages/Conversation/MessagesList.tsx b/src/screens/Messages/Conversation/MessagesList.tsx index d36fac8a..ef0cc55d 100644 --- a/src/screens/Messages/Conversation/MessagesList.tsx +++ b/src/screens/Messages/Conversation/MessagesList.tsx @@ -23,7 +23,7 @@ import {isWeb} from 'platform/detection' import {List} from 'view/com/util/List' import {MessageInput} from '#/screens/Messages/Conversation/MessageInput' import {MessageListError} from '#/screens/Messages/Conversation/MessageListError' -import {atoms as a, useBreakpoints} from '#/alf' +import {atoms as a} from '#/alf' import {MessageItem} from '#/components/dms/MessageItem' import {NewMessagesPill} from '#/components/dms/NewMessagesPill' import {Loader} from '#/components/Loader' @@ -66,12 +66,17 @@ function onScrollToIndexFailed() { export function MessagesList({ hasScrolled, setHasScrolled, + blocked, + footer, }: { hasScrolled: boolean setHasScrolled: React.Dispatch> + blocked?: boolean + footer?: React.ReactNode }) { - const convo = useConvoActive() + const convoState = useConvoActive() const {getAgent} = useAgent() + const flatListRef = useAnimatedRef() const [showNewMessagesPill, setShowNewMessagesPill] = React.useState(false) @@ -81,7 +86,7 @@ export function MessagesList({ // the bottom. const isAtBottom = useSharedValue(true) - // This will be used on web to assist in determing if we need to maintain the content offset + // This will be used on web to assist in determining if we need to maintain the content offset const isAtTop = useSharedValue(true) // Used to keep track of the current content height. We'll need this in `onScroll` so we know when to start allowing @@ -126,11 +131,11 @@ export function MessagesList({ if ( hasScrolled && height - contentHeight.value > layoutHeight.value - 50 && - convo.items.length - prevItemCount.current > 1 + convoState.items.length - prevItemCount.current > 1 ) { newOffset = contentHeight.value - 50 setShowNewMessagesPill(true) - } else if (!hasScrolled && !convo.isFetchingHistory) { + } else if (!hasScrolled && !convoState.isFetchingHistory) { setHasScrolled(true) } @@ -141,12 +146,12 @@ export function MessagesList({ isMomentumScrolling.value = true } contentHeight.value = height - prevItemCount.current = convo.items.length + prevItemCount.current = convoState.items.length }, [ hasScrolled, - convo.items.length, - convo.isFetchingHistory, + convoState.items.length, + convoState.isFetchingHistory, setHasScrolled, // all of these are stable contentHeight, @@ -161,9 +166,9 @@ export function MessagesList({ const onStartReached = useCallback(() => { if (hasScrolled) { - convo.fetchMessageHistory() + convoState.fetchMessageHistory() } - }, [convo, hasScrolled]) + }, [convoState, hasScrolled]) const onSendMessage = useCallback( async (text: string) => { @@ -182,12 +187,12 @@ export function MessagesList({ return true }) - convo.sendMessage({ + convoState.sendMessage({ text: rt.text, facets: rt.facets, }) }, - [convo, getAgent], + [convoState, getAgent], ) const onScroll = React.useCallback( @@ -225,11 +230,9 @@ export function MessagesList({ // -- Keyboard animation handling const animatedKeyboard = useAnimatedKeyboard() - const {gtMobile} = useBreakpoints() const {bottom: bottomInset} = useSafeAreaInsets() const nativeBottomBarHeight = isIOS ? 42 : 60 - const bottomOffset = - isWeb && gtMobile ? 0 : bottomInset + nativeBottomBarHeight + const bottomOffset = isWeb ? 0 : bottomInset + nativeBottomBarHeight // On web, we don't want to do anything. // On native, we want to scroll the list to the bottom every frame that the keyboard is opening. `scrollTo` runs @@ -268,11 +271,10 @@ export function MessagesList({ + } /> - + {!blocked ? ( + + ) : ( + footer + )} {showNewMessagesPill && } ) diff --git a/src/screens/Messages/Conversation/index.tsx b/src/screens/Messages/Conversation/index.tsx index 2c42ed16..0fe4138b 100644 --- a/src/screens/Messages/Conversation/index.tsx +++ b/src/screens/Messages/Conversation/index.tsx @@ -1,35 +1,28 @@ import React, {useCallback} from 'react' -import {TouchableOpacity, View} from 'react-native' +import {View} from 'react-native' import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useFocusEffect, useNavigation} from '@react-navigation/native' +import {useFocusEffect} from '@react-navigation/native' import {NativeStackScreenProps} from '@react-navigation/native-stack' -import {makeProfileLink} from '#/lib/routes/links' -import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types' +import {CommonNavigatorParams} from '#/lib/routes/types' import {useGate} from '#/lib/statsig/statsig' -import {useProfileShadow} from '#/state/cache/profile-shadow' import {useCurrentConvoId} from '#/state/messages/current-convo-id' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useProfileQuery} from '#/state/queries/profile' -import {BACK_HITSLOP} from 'lib/constants' -import {sanitizeDisplayName} from 'lib/strings/display-names' import {isWeb} from 'platform/detection' +import {useProfileShadow} from 'state/cache/profile-shadow' import {ConvoProvider, isConvoActive, useConvo} from 'state/messages/convo' import {ConvoStatus} from 'state/messages/convo/types' import {useSetMinimalShellMode} from 'state/shell' -import {PreviewableUserAvatar} from 'view/com/util/UserAvatar' import {CenteredView} from 'view/com/util/Views' import {MessagesList} from '#/screens/Messages/Conversation/MessagesList' -import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' -import {ConvoMenu} from '#/components/dms/ConvoMenu' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {MessagesListBlockedFooter} from '#/components/dms/MessagesListBlockedFooter' +import {MessagesListHeader} from '#/components/dms/MessagesListHeader' import {Error} from '#/components/Error' -import {Link} from '#/components/Link' -import {ListMaybePlaceholder} from '#/components/Lists' import {Loader} from '#/components/Loader' -import {Text} from '#/components/Typography' import {ClipClopGate} from '../gate' type Props = NativeStackScreenProps< @@ -73,6 +66,11 @@ function Inner() { const convoState = useConvo() const {_} = useLingui() + const moderationOpts = useModerationOpts() + const {data: recipient} = useProfileQuery({ + did: convoState.recipients?.[0].did, + }) + // Because we want to give the list a chance to asynchronously scroll to the end before it is visible to the user, // we use `hasScrolled` to determine when to render. With that said however, there is a chance that the chat will be // empty. So, we also check for that possible state as well and render once we can. @@ -86,7 +84,7 @@ function Inner() { if (convoState.status === ConvoStatus.Error) { return ( -
+ -
+ {!readyToShow && } - {isConvoActive(convoState) ? ( - ) : ( - + <> + + )} {!readyToShow && ( { - const t = useTheme() - const {_} = useLingui() - const {gtTablet} = useBreakpoints() - const navigation = useNavigation() - const moderationOpts = useModerationOpts() - const {data: profile} = useProfileQuery({did: initialProfile?.did}) - - const onPressBack = useCallback(() => { - if (isWeb) { - navigation.replace('Messages') - } else { - navigation.goBack() - } - }, [navigation]) - - return ( - - {!gtTablet && ( - - - - )} - - {profile && moderationOpts ? ( - - ) : ( - <> - - - - - - - - - - - )} - - ) -} -Header = React.memo(Header) - -function HeaderReady({ - profile: profileUnshadowed, +function InnerReady({ moderationOpts, + recipient: recipientUnshadowed, + hasScrolled, + setHasScrolled, }: { - profile: AppBskyActorDefs.ProfileViewBasic moderationOpts: ModerationOpts + recipient: AppBskyActorDefs.ProfileViewBasic + hasScrolled: boolean + setHasScrolled: React.Dispatch> }) { - const t = useTheme() const convoState = useConvo() - const profile = useProfileShadow(profileUnshadowed) - const moderation = React.useMemo( - () => moderateProfile(profile, moderationOpts), - [profile, moderationOpts], - ) + const recipient = useProfileShadow(recipientUnshadowed) - const isDeletedAccount = profile?.handle === 'missing.invalid' - const displayName = isDeletedAccount - ? 'Deleted Account' - : sanitizeDisplayName( - profile.displayName || profile.handle, - moderation.ui('displayName'), - ) + const moderation = React.useMemo(() => { + return moderateProfile(recipient, moderationOpts) + }, [recipient, moderationOpts]) + + const blockInfo = React.useMemo(() => { + const modui = moderation.ui('profileView') + const blocks = modui.alerts.filter(alert => alert.type === 'blocking') + const listBlocks = blocks.filter(alert => alert.source.type === 'list') + const userBlock = blocks.find(alert => alert.source.type === 'user') + return { + listBlocks, + userBlock, + } + }, [moderation]) return ( <> - - - - - {displayName} - - {!isDeletedAccount && ( - - @{profile.handle} - - )} - - - + {isConvoActive(convoState) && ( - 0} + blockInfo={blockInfo} + /> + } /> )} diff --git a/src/screens/Messages/List/ChatListItem.tsx b/src/screens/Messages/List/ChatListItem.tsx index a7b7e068..791dc82c 100644 --- a/src/screens/Messages/List/ChatListItem.tsx +++ b/src/screens/Messages/List/ChatListItem.tsx @@ -65,6 +65,17 @@ function ChatListItemReady({ [profile, moderationOpts], ) + const blockInfo = React.useMemo(() => { + const modui = moderation.ui('profileView') + const blocks = modui.alerts.filter(alert => alert.type === 'blocking') + const listBlocks = blocks.filter(alert => alert.source.type === 'list') + const userBlock = blocks.find(alert => alert.source.type === 'user') + return { + listBlocks, + userBlock, + } + }, [moderation]) + const isDeletedAccount = profile.handle === 'missing.invalid' const displayName = isDeletedAccount ? 'Deleted Account' @@ -241,7 +252,7 @@ function ChatListItemReady({ triggerOpacity={ !gtMobile || showActions || menuControl.isOpen ? 1 : 0 } - moderation={moderation} + blockInfo={blockInfo} />