[Chat] Centralize conversation report-subject resolution (#10754)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-06-08 20:07:35 +03:00
committed by GitHub
parent 0090285fc0
commit 9b5fc19613
7 changed files with 151 additions and 148 deletions
@@ -307,20 +307,13 @@ function BaseChatItem({
isDeletedAccount ||
(convo.kind === 'group' && convo.details.lockStatus !== 'unlocked')
const {
lastMessage,
LastMessageIcon,
lastMessageSentAt,
latestReportableMessage,
} = useMemo(() => {
const {lastMessage, LastMessageIcon, lastMessageSentAt} = useMemo(() => {
let lastMessage = l`No messages yet`
let LastMessageIcon: React.ComponentType<SVGIconProps> | null = null
let lastMessageSentAt: string | null = null
let latestReportableMessage: ChatBskyConvoDefs.MessageView | undefined
// Deleted message
if (ChatBskyConvoDefs.isDeletedMessageView(convo.view.lastMessage)) {
lastMessageSentAt = convo.view.lastMessage.sentAt
@@ -340,7 +333,6 @@ function BaseChatItem({
if (info) {
lastMessage = info.message ?? lastMessage
lastMessageSentAt = info.sentAt
latestReportableMessage = info.reportableMessage
}
}
@@ -385,7 +377,6 @@ function BaseChatItem({
lastMessage,
LastMessageIcon,
lastMessageSentAt,
latestReportableMessage,
}
}, [l, convo, currentAccount?.did, isDeletedAccount, i18n])
@@ -663,7 +654,7 @@ function BaseChatItem({
{/* TODO: Allow showing menu for groups where the owner has left! */}
{showMenu && primaryProfile && (
<ConvoMenu
convo={convo.view}
convo={convo}
profile={primaryProfile}
control={menuControl}
currentScreen="list"
@@ -681,7 +672,6 @@ function BaseChatItem({
!gtMobile || showActions || menuControl.isOpen ? 1 : 0,
},
]}
latestReportableMessage={latestReportableMessage}
/>
)}
@@ -1,7 +1,7 @@
import {useCallback, useMemo} from 'react'
import {View} from 'react-native'
import {LinearGradient} from 'expo-linear-gradient'
import {ChatBskyConvoDefs, moderateProfile} from '@atproto/api'
import {moderateProfile} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
@@ -33,12 +33,6 @@ export function ChatStatusInfo({convoState}: {convoState: ActiveConvoStates}) {
// if we ever allow someone other than the owner to invite people, this will need to change
const otherUser = convoState.convo.primaryMember
const lastMessage = ChatBskyConvoDefs.isMessageView(
convoState.convo.view.lastMessage,
)
? convoState.convo.view.lastMessage
: null
if (!moderationOpts) {
return null
}
@@ -64,9 +58,9 @@ export function ChatStatusInfo({convoState}: {convoState: ActiveConvoStates}) {
<View style={[a.flex_row, a.gap_md, a.w_full, otherUser && a.pt_sm]}>
{otherUser && (
<RejectMenu
label={lastMessage ? l`Block or report` : l`Block`}
label={l`Block or report`}
icon={true}
convo={convoState.convo.view}
convo={convoState.convo}
profile={otherUser}
color="negative_subtle"
size="large"
@@ -72,7 +72,7 @@ export function IncomingRequestListItem({
<AcceptChatButton convo={convo.view} currentScreen="list" />
) : null}
<RejectMenu
convo={convo.view}
convo={convo}
profile={convo.primaryMember}
showDeleteConvo
currentScreen="list"
@@ -14,6 +14,7 @@ import {
unstableCacheProfileView,
useProfileBlockMutationQueue,
} from '#/state/queries/profile'
import {useSession} from '#/state/session'
import {
Button,
ButtonIcon,
@@ -25,7 +26,12 @@ import {
EmailDialogScreenID,
useEmailDialogControl,
} from '#/components/dialogs/EmailDialog'
import {AfterReportConversationDialog} from '#/components/dms/AfterReportConversationDialog'
import {AfterReportDialog} from '#/components/dms/AfterReportDialog'
import {
type ConvoWithDetails,
getConvoReportSubject,
} from '#/components/dms/util'
import {ArrowBoxLeft_Stroke2_Corner0_Rounded as LeaveIcon} from '#/components/icons/ArrowBoxLeft'
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
import {CircleX_Stroke2_Corner0_Rounded} from '#/components/icons/CircleX'
@@ -49,17 +55,18 @@ export function RejectMenu({
}: Omit<ButtonProps, 'onPress' | 'children' | 'label'> & {
label?: string
icon?: boolean
convo: ChatBskyConvoDefs.ConvoView
convo: ConvoWithDetails
profile: ChatBskyActorDefs.ProfileViewBasic
showDeleteConvo?: boolean
currentScreen: 'list' | 'conversation'
}) {
const {t: l} = useLingui()
const {currentAccount} = useSession()
const shadowedProfile = useProfileShadow(profile)
const navigation = useNavigation<NavigationProp>()
const queryClient = useQueryClient()
const {mutate: leaveConvo} = useLeaveConvo(convo.id, {
const {mutate: leaveConvo} = useLeaveConvo(convo.view.id, {
onMutate: () => {
if (currentScreen === 'conversation') {
navigation.dispatch(StackActions.pop())
@@ -110,9 +117,7 @@ export function RejectMenu({
const reportControl = useDialogControl()
const blockOrDeleteControl = useDialogControl()
const lastMessage = ChatBskyConvoDefs.isMessageView(convo.lastMessage)
? convo.lastMessage
: null
const reportSubject = getConvoReportSubject(convo, currentAccount?.did)
return (
<>
@@ -152,50 +157,46 @@ export function RejectMenu({
</Menu.ItemText>
<Menu.ItemIcon icon={PersonXIcon} />
</Menu.Item>
{/* note: last message will almost certainly be defined, since you can't
delete messages for other people and it's impossible for a convo on this
screen to have a message sent by you */}
{lastMessage && (
<Menu.Item
label={l`Report conversation`}
onPress={reportControl.open}>
<Menu.ItemText>
<Trans>Report conversation</Trans>
</Menu.ItemText>
<Menu.ItemIcon icon={FlagIcon} />
</Menu.Item>
)}
<Menu.Item
label={l`Report conversation`}
onPress={reportControl.open}>
<Menu.ItemText>
<Trans>Report conversation</Trans>
</Menu.ItemText>
<Menu.ItemIcon icon={FlagIcon} />
</Menu.Item>
</Menu.Group>
</Menu.Outer>
</Menu.Root>
{lastMessage && (
<>
<ReportDialog
subject={{
view: 'convo',
convoId: convo.id,
message: lastMessage,
}}
control={reportControl}
onAfterSubmit={() => {
const sender = convo.members.find(
member => member.did === lastMessage.sender.did,
)
if (sender) {
unstableCacheProfileView(queryClient, sender)
}
blockOrDeleteControl.open()
}}
/>
<AfterReportDialog
control={blockOrDeleteControl}
currentScreen={currentScreen}
params={{
convoId: convo.id,
did: lastMessage.sender.did,
}}
/>
</>
{reportSubject && (
<ReportDialog
subject={reportSubject}
control={reportControl}
onAfterSubmit={() => {
unstableCacheProfileView(queryClient, profile)
blockOrDeleteControl.open()
}}
/>
)}
{convo.kind === 'group' ? (
<AfterReportConversationDialog
control={blockOrDeleteControl}
currentScreen={currentScreen}
params={{
convoId: convo.view.id,
did: profile.did,
}}
/>
) : (
<AfterReportDialog
control={blockOrDeleteControl}
currentScreen={currentScreen}
params={{
convoId: convo.view.id,
did: profile.did,
}}
/>
)}
</>
)