[Chat] Centralize conversation report-subject resolution (#10754)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import {memo, useCallback} from 'react'
|
import {memo, useCallback} from 'react'
|
||||||
import {Keyboard, View} from 'react-native'
|
import {Keyboard, View} from 'react-native'
|
||||||
import {ChatBskyConvoDefs, type ModerationCause} from '@atproto/api'
|
import {type ModerationCause} from '@atproto/api'
|
||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
@@ -16,13 +16,18 @@ import {
|
|||||||
unstableCacheProfileView,
|
unstableCacheProfileView,
|
||||||
useProfileBlockMutationQueue,
|
useProfileBlockMutationQueue,
|
||||||
} from '#/state/queries/profile'
|
} from '#/state/queries/profile'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
import {type ViewStyleProp} from '#/alf'
|
import {type ViewStyleProp} from '#/alf'
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {Button, ButtonIcon} from '#/components/Button'
|
import {Button, ButtonIcon} from '#/components/Button'
|
||||||
|
import {AfterReportConversationDialog} from '#/components/dms/AfterReportConversationDialog'
|
||||||
import {AfterReportDialog} from '#/components/dms/AfterReportDialog'
|
import {AfterReportDialog} from '#/components/dms/AfterReportDialog'
|
||||||
import {BlockedByListDialog} from '#/components/dms/BlockedByListDialog'
|
import {BlockedByListDialog} from '#/components/dms/BlockedByListDialog'
|
||||||
import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt'
|
import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt'
|
||||||
import {ReportConversationDialog} from '#/components/dms/ReportConversationDialog'
|
import {
|
||||||
|
type ConvoWithDetails,
|
||||||
|
getConvoReportSubject,
|
||||||
|
} from '#/components/dms/util'
|
||||||
import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeftIcon} from '#/components/icons/ArrowBoxLeft'
|
import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeftIcon} from '#/components/icons/ArrowBoxLeft'
|
||||||
import {Bubble_Stroke2_Corner2_Rounded as BubbleIcon} from '#/components/icons/Bubble'
|
import {Bubble_Stroke2_Corner2_Rounded as BubbleIcon} from '#/components/icons/Bubble'
|
||||||
import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontalIcon} from '#/components/icons/DotGrid'
|
import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontalIcon} from '#/components/icons/DotGrid'
|
||||||
@@ -39,7 +44,6 @@ import {ReportDialog} from '#/components/moderation/ReportDialog'
|
|||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import type * as bsky from '#/types/bsky'
|
import type * as bsky from '#/types/bsky'
|
||||||
import {AfterReportConversationDialog} from './AfterReportConversationDialog'
|
|
||||||
|
|
||||||
let ConvoMenu = ({
|
let ConvoMenu = ({
|
||||||
convo,
|
convo,
|
||||||
@@ -49,10 +53,9 @@ let ConvoMenu = ({
|
|||||||
showMarkAsRead,
|
showMarkAsRead,
|
||||||
hideTrigger,
|
hideTrigger,
|
||||||
blockInfo,
|
blockInfo,
|
||||||
latestReportableMessage,
|
|
||||||
style,
|
style,
|
||||||
}: {
|
}: {
|
||||||
convo: ChatBskyConvoDefs.ConvoView
|
convo: ConvoWithDetails
|
||||||
profile: Shadow<bsky.profile.AnyProfileView>
|
profile: Shadow<bsky.profile.AnyProfileView>
|
||||||
control?: Menu.MenuControlProps
|
control?: Menu.MenuControlProps
|
||||||
currentScreen: 'list' | 'conversation'
|
currentScreen: 'list' | 'conversation'
|
||||||
@@ -62,20 +65,21 @@ let ConvoMenu = ({
|
|||||||
listBlocks: ModerationCause[]
|
listBlocks: ModerationCause[]
|
||||||
userBlock?: ModerationCause
|
userBlock?: ModerationCause
|
||||||
}
|
}
|
||||||
latestReportableMessage?: ChatBskyConvoDefs.MessageView
|
|
||||||
style?: ViewStyleProp['style']
|
style?: ViewStyleProp['style']
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
|
||||||
const leaveConvoControl = Prompt.usePromptControl()
|
const leaveConvoControl = Prompt.usePromptControl()
|
||||||
const reportControl = Prompt.usePromptControl()
|
const reportControl = Prompt.usePromptControl()
|
||||||
const blockedByListControl = Prompt.usePromptControl()
|
const blockedByListControl = Prompt.usePromptControl()
|
||||||
const blockOrDeleteControl = Prompt.usePromptControl()
|
const afterReportControl = Prompt.usePromptControl()
|
||||||
const deleteControl = Prompt.usePromptControl()
|
|
||||||
|
|
||||||
const {listBlocks} = blockInfo
|
const {listBlocks} = blockInfo
|
||||||
|
|
||||||
|
const reportSubject = getConvoReportSubject(convo, currentAccount?.did)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Menu.Root control={control}>
|
<Menu.Root control={control}>
|
||||||
@@ -108,6 +112,7 @@ let ConvoMenu = ({
|
|||||||
showMarkAsRead={showMarkAsRead}
|
showMarkAsRead={showMarkAsRead}
|
||||||
blockInfo={blockInfo}
|
blockInfo={blockInfo}
|
||||||
convo={convo}
|
convo={convo}
|
||||||
|
canReport={!!reportSubject}
|
||||||
leaveConvoControl={leaveConvoControl}
|
leaveConvoControl={leaveConvoControl}
|
||||||
reportControl={reportControl}
|
reportControl={reportControl}
|
||||||
blockedByListControl={blockedByListControl}
|
blockedByListControl={blockedByListControl}
|
||||||
@@ -116,54 +121,37 @@ let ConvoMenu = ({
|
|||||||
</Menu.Root>
|
</Menu.Root>
|
||||||
<LeaveConvoPrompt
|
<LeaveConvoPrompt
|
||||||
control={leaveConvoControl}
|
control={leaveConvoControl}
|
||||||
convoId={convo.id}
|
convoId={convo.view.id}
|
||||||
currentScreen={currentScreen}
|
currentScreen={currentScreen}
|
||||||
/>
|
/>
|
||||||
{latestReportableMessage ? (
|
{reportSubject && (
|
||||||
<>
|
<ReportDialog
|
||||||
<ReportDialog
|
subject={reportSubject}
|
||||||
subject={{
|
control={reportControl}
|
||||||
view: 'convo',
|
onAfterSubmit={() => {
|
||||||
convoId: convo.id,
|
unstableCacheProfileView(queryClient, profile)
|
||||||
message: latestReportableMessage,
|
afterReportControl.open()
|
||||||
}}
|
}}
|
||||||
control={reportControl}
|
/>
|
||||||
onAfterSubmit={() => {
|
)}
|
||||||
const sender = convo.members.find(
|
{convo.kind === 'group' ? (
|
||||||
member => member.did === latestReportableMessage.sender.did,
|
<AfterReportConversationDialog
|
||||||
)
|
control={afterReportControl}
|
||||||
if (sender) {
|
currentScreen={currentScreen}
|
||||||
unstableCacheProfileView(queryClient, sender)
|
params={{
|
||||||
}
|
convoId: convo.view.id,
|
||||||
blockOrDeleteControl.open()
|
did: profile.did,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<AfterReportDialog
|
|
||||||
control={blockOrDeleteControl}
|
|
||||||
currentScreen={currentScreen}
|
|
||||||
params={{
|
|
||||||
convoId: convo.id,
|
|
||||||
did: latestReportableMessage.sender.did,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<AfterReportDialog
|
||||||
<ReportConversationDialog
|
control={afterReportControl}
|
||||||
control={reportControl}
|
currentScreen={currentScreen}
|
||||||
convoId={convo.id}
|
params={{
|
||||||
did={profile.did}
|
convoId: convo.view.id,
|
||||||
onAfterSubmit={deleteControl.open}
|
did: profile.did,
|
||||||
/>
|
}}
|
||||||
<AfterReportConversationDialog
|
/>
|
||||||
control={deleteControl}
|
|
||||||
currentScreen={currentScreen}
|
|
||||||
params={{
|
|
||||||
convoId: convo.id,
|
|
||||||
did: profile.did,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
<BlockedByListDialog
|
<BlockedByListDialog
|
||||||
control={blockedByListControl}
|
control={blockedByListControl}
|
||||||
@@ -177,14 +165,16 @@ ConvoMenu = memo(ConvoMenu)
|
|||||||
function MenuContent({
|
function MenuContent({
|
||||||
convo: initialConvo,
|
convo: initialConvo,
|
||||||
profile,
|
profile,
|
||||||
|
canReport,
|
||||||
showMarkAsRead,
|
showMarkAsRead,
|
||||||
blockInfo,
|
blockInfo,
|
||||||
leaveConvoControl,
|
leaveConvoControl,
|
||||||
reportControl,
|
reportControl,
|
||||||
blockedByListControl,
|
blockedByListControl,
|
||||||
}: {
|
}: {
|
||||||
convo: ChatBskyConvoDefs.ConvoView
|
convo: ConvoWithDetails
|
||||||
profile: Shadow<bsky.profile.AnyProfileView>
|
profile: Shadow<bsky.profile.AnyProfileView>
|
||||||
|
canReport: boolean
|
||||||
showMarkAsRead?: boolean
|
showMarkAsRead?: boolean
|
||||||
blockInfo: {
|
blockInfo: {
|
||||||
listBlocks: ModerationCause[]
|
listBlocks: ModerationCause[]
|
||||||
@@ -201,9 +191,9 @@ function MenuContent({
|
|||||||
const {listBlocks, userBlock} = blockInfo
|
const {listBlocks, userBlock} = blockInfo
|
||||||
const isBlocking = userBlock || !!listBlocks.length
|
const isBlocking = userBlock || !!listBlocks.length
|
||||||
const isDeletedAccount = profile.handle === 'missing.invalid'
|
const isDeletedAccount = profile.handle === 'missing.invalid'
|
||||||
const isGroupConvo = ChatBskyConvoDefs.isGroupConvo(initialConvo.kind)
|
const isGroupConvo = initialConvo.kind === 'group'
|
||||||
|
|
||||||
const convoId = initialConvo.id
|
const convoId = initialConvo.view.id
|
||||||
const {data: convo} = useConvoQuery({convoId})
|
const {data: convo} = useConvoQuery({convoId})
|
||||||
|
|
||||||
const onNavigateToProfile = useCallback(() => {
|
const onNavigateToProfile = useCallback(() => {
|
||||||
@@ -299,15 +289,17 @@ function MenuContent({
|
|||||||
</Menu.ItemText>
|
</Menu.ItemText>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
)}
|
)}
|
||||||
<Menu.Item
|
{canReport && (
|
||||||
destructive
|
<Menu.Item
|
||||||
label={l`Report conversation`}
|
destructive
|
||||||
onPress={reportControl.open}>
|
label={l`Report conversation`}
|
||||||
<Menu.ItemIcon icon={Flag} />
|
onPress={reportControl.open}>
|
||||||
<Menu.ItemText>
|
<Menu.ItemIcon icon={Flag} />
|
||||||
<Trans>Report conversation</Trans>
|
<Menu.ItemText>
|
||||||
</Menu.ItemText>
|
<Trans>Report conversation</Trans>
|
||||||
</Menu.Item>
|
</Menu.ItemText>
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
</Menu.Group>
|
</Menu.Group>
|
||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
<Menu.Group>
|
<Menu.Group>
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import {useMemo} from 'react'
|
import {useMemo} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {
|
import {moderateProfile, type ModerationOpts} from '@atproto/api'
|
||||||
ChatBskyConvoDefs,
|
|
||||||
moderateProfile,
|
|
||||||
type ModerationOpts,
|
|
||||||
} from '@atproto/api'
|
|
||||||
import {useLingui} from '@lingui/react/macro'
|
import {useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||||
@@ -12,7 +8,6 @@ import {makeProfileLink} from '#/lib/routes/links'
|
|||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useSession} from '#/state/session'
|
|
||||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {useIsWithinSplitView} from '#/screens/Messages/components/splitView/context'
|
import {useIsWithinSplitView} from '#/screens/Messages/components/splitView/context'
|
||||||
import {atoms as a, useTheme, web} from '#/alf'
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
@@ -88,7 +83,6 @@ function ProfileHeaderReady({
|
|||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const {currentAccount} = useSession()
|
|
||||||
const profile = useProfileShadow(convo.primaryMember)
|
const profile = useProfileShadow(convo.primaryMember)
|
||||||
|
|
||||||
const moderation = moderateProfile(profile, moderationOpts)
|
const moderation = moderateProfile(profile, moderationOpts)
|
||||||
@@ -110,12 +104,6 @@ function ProfileHeaderReady({
|
|||||||
: createSanitizedDisplayName(profile, true, moderation.ui('displayName'))
|
: createSanitizedDisplayName(profile, true, moderation.ui('displayName'))
|
||||||
const handle = isDeletedAccount ? null : sanitizeHandle(profile.handle, '@')
|
const handle = isDeletedAccount ? null : sanitizeHandle(profile.handle, '@')
|
||||||
|
|
||||||
const latestReportableMessage =
|
|
||||||
ChatBskyConvoDefs.isMessageView(convo.view.lastMessage) &&
|
|
||||||
convo.view.lastMessage.sender?.did !== currentAccount?.did
|
|
||||||
? convo.view.lastMessage
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper
|
<Wrapper
|
||||||
heading={
|
heading={
|
||||||
@@ -151,11 +139,10 @@ function ProfileHeaderReady({
|
|||||||
}
|
}
|
||||||
settings={
|
settings={
|
||||||
<ConvoMenu
|
<ConvoMenu
|
||||||
convo={convo.view}
|
convo={convo}
|
||||||
profile={profile}
|
profile={profile}
|
||||||
currentScreen="conversation"
|
currentScreen="conversation"
|
||||||
blockInfo={blockInfo}
|
blockInfo={blockInfo}
|
||||||
latestReportableMessage={latestReportableMessage}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {EMOJI_REACTION_LIMIT} from '#/lib/constants'
|
|||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {type Shadow} from '#/state/cache/profile-shadow'
|
import {type Shadow} from '#/state/cache/profile-shadow'
|
||||||
import {type ConvoState, ConvoStatus} from '#/state/messages/convo/types'
|
import {type ConvoState, ConvoStatus} from '#/state/messages/convo/types'
|
||||||
|
import {type ReportSubject} from '#/components/moderation/ReportDialog/types'
|
||||||
import * as bsky from '#/types/bsky'
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
export const MESSAGE_GAP_THRESHOLD_MS = 60 * 60 * 1000
|
export const MESSAGE_GAP_THRESHOLD_MS = 60 * 60 * 1000
|
||||||
@@ -240,3 +241,41 @@ export function parseConvoView(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the report subject for a conversation-level "Report conversation"
|
||||||
|
* action (as opposed to reporting an individual message, which always reports
|
||||||
|
* that message + its sender).
|
||||||
|
*
|
||||||
|
* - group: always report the whole convo, targeting the owner. Returns null if
|
||||||
|
* the owner has left, in which case there is nothing to report against.
|
||||||
|
* - direct: report the last reportable message if there is one (i.e. the last
|
||||||
|
* message exists and wasn't sent by us), otherwise report the whole convo
|
||||||
|
* targeting the other user.
|
||||||
|
*/
|
||||||
|
export function getConvoReportSubject(
|
||||||
|
convo: ConvoWithDetails,
|
||||||
|
ownDid: string | undefined,
|
||||||
|
): ReportSubject | null {
|
||||||
|
if (convo.kind === 'group') {
|
||||||
|
if (!convo.primaryMember) return null
|
||||||
|
return {convoId: convo.view.id, did: convo.primaryMember.did}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastMessage = convo.view.lastMessage
|
||||||
|
const reportableMessage =
|
||||||
|
ChatBskyConvoDefs.isMessageView(lastMessage) &&
|
||||||
|
lastMessage.sender?.did !== ownDid
|
||||||
|
? lastMessage
|
||||||
|
: null
|
||||||
|
|
||||||
|
if (reportableMessage) {
|
||||||
|
return {
|
||||||
|
view: 'convo',
|
||||||
|
convoId: convo.view.id,
|
||||||
|
message: reportableMessage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {convoId: convo.view.id, did: convo.primaryMember.did}
|
||||||
|
}
|
||||||
|
|||||||
@@ -307,20 +307,13 @@ function BaseChatItem({
|
|||||||
isDeletedAccount ||
|
isDeletedAccount ||
|
||||||
(convo.kind === 'group' && convo.details.lockStatus !== 'unlocked')
|
(convo.kind === 'group' && convo.details.lockStatus !== 'unlocked')
|
||||||
|
|
||||||
const {
|
const {lastMessage, LastMessageIcon, lastMessageSentAt} = useMemo(() => {
|
||||||
lastMessage,
|
|
||||||
LastMessageIcon,
|
|
||||||
lastMessageSentAt,
|
|
||||||
latestReportableMessage,
|
|
||||||
} = useMemo(() => {
|
|
||||||
let lastMessage = l`No messages yet`
|
let lastMessage = l`No messages yet`
|
||||||
|
|
||||||
let LastMessageIcon: React.ComponentType<SVGIconProps> | null = null
|
let LastMessageIcon: React.ComponentType<SVGIconProps> | null = null
|
||||||
|
|
||||||
let lastMessageSentAt: string | null = null
|
let lastMessageSentAt: string | null = null
|
||||||
|
|
||||||
let latestReportableMessage: ChatBskyConvoDefs.MessageView | undefined
|
|
||||||
|
|
||||||
// Deleted message
|
// Deleted message
|
||||||
if (ChatBskyConvoDefs.isDeletedMessageView(convo.view.lastMessage)) {
|
if (ChatBskyConvoDefs.isDeletedMessageView(convo.view.lastMessage)) {
|
||||||
lastMessageSentAt = convo.view.lastMessage.sentAt
|
lastMessageSentAt = convo.view.lastMessage.sentAt
|
||||||
@@ -340,7 +333,6 @@ function BaseChatItem({
|
|||||||
if (info) {
|
if (info) {
|
||||||
lastMessage = info.message ?? lastMessage
|
lastMessage = info.message ?? lastMessage
|
||||||
lastMessageSentAt = info.sentAt
|
lastMessageSentAt = info.sentAt
|
||||||
latestReportableMessage = info.reportableMessage
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,7 +377,6 @@ function BaseChatItem({
|
|||||||
lastMessage,
|
lastMessage,
|
||||||
LastMessageIcon,
|
LastMessageIcon,
|
||||||
lastMessageSentAt,
|
lastMessageSentAt,
|
||||||
latestReportableMessage,
|
|
||||||
}
|
}
|
||||||
}, [l, convo, currentAccount?.did, isDeletedAccount, i18n])
|
}, [l, convo, currentAccount?.did, isDeletedAccount, i18n])
|
||||||
|
|
||||||
@@ -663,7 +654,7 @@ function BaseChatItem({
|
|||||||
{/* TODO: Allow showing menu for groups where the owner has left! */}
|
{/* TODO: Allow showing menu for groups where the owner has left! */}
|
||||||
{showMenu && primaryProfile && (
|
{showMenu && primaryProfile && (
|
||||||
<ConvoMenu
|
<ConvoMenu
|
||||||
convo={convo.view}
|
convo={convo}
|
||||||
profile={primaryProfile}
|
profile={primaryProfile}
|
||||||
control={menuControl}
|
control={menuControl}
|
||||||
currentScreen="list"
|
currentScreen="list"
|
||||||
@@ -681,7 +672,6 @@ function BaseChatItem({
|
|||||||
!gtMobile || showActions || menuControl.isOpen ? 1 : 0,
|
!gtMobile || showActions || menuControl.isOpen ? 1 : 0,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
latestReportableMessage={latestReportableMessage}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {useCallback, useMemo} from 'react'
|
import {useCallback, useMemo} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {LinearGradient} from 'expo-linear-gradient'
|
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 {Trans, useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
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
|
// if we ever allow someone other than the owner to invite people, this will need to change
|
||||||
const otherUser = convoState.convo.primaryMember
|
const otherUser = convoState.convo.primaryMember
|
||||||
|
|
||||||
const lastMessage = ChatBskyConvoDefs.isMessageView(
|
|
||||||
convoState.convo.view.lastMessage,
|
|
||||||
)
|
|
||||||
? convoState.convo.view.lastMessage
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (!moderationOpts) {
|
if (!moderationOpts) {
|
||||||
return null
|
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]}>
|
<View style={[a.flex_row, a.gap_md, a.w_full, otherUser && a.pt_sm]}>
|
||||||
{otherUser && (
|
{otherUser && (
|
||||||
<RejectMenu
|
<RejectMenu
|
||||||
label={lastMessage ? l`Block or report` : l`Block`}
|
label={l`Block or report`}
|
||||||
icon={true}
|
icon={true}
|
||||||
convo={convoState.convo.view}
|
convo={convoState.convo}
|
||||||
profile={otherUser}
|
profile={otherUser}
|
||||||
color="negative_subtle"
|
color="negative_subtle"
|
||||||
size="large"
|
size="large"
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export function IncomingRequestListItem({
|
|||||||
<AcceptChatButton convo={convo.view} currentScreen="list" />
|
<AcceptChatButton convo={convo.view} currentScreen="list" />
|
||||||
) : null}
|
) : null}
|
||||||
<RejectMenu
|
<RejectMenu
|
||||||
convo={convo.view}
|
convo={convo}
|
||||||
profile={convo.primaryMember}
|
profile={convo.primaryMember}
|
||||||
showDeleteConvo
|
showDeleteConvo
|
||||||
currentScreen="list"
|
currentScreen="list"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
unstableCacheProfileView,
|
unstableCacheProfileView,
|
||||||
useProfileBlockMutationQueue,
|
useProfileBlockMutationQueue,
|
||||||
} from '#/state/queries/profile'
|
} from '#/state/queries/profile'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonIcon,
|
ButtonIcon,
|
||||||
@@ -25,7 +26,12 @@ import {
|
|||||||
EmailDialogScreenID,
|
EmailDialogScreenID,
|
||||||
useEmailDialogControl,
|
useEmailDialogControl,
|
||||||
} from '#/components/dialogs/EmailDialog'
|
} from '#/components/dialogs/EmailDialog'
|
||||||
|
import {AfterReportConversationDialog} from '#/components/dms/AfterReportConversationDialog'
|
||||||
import {AfterReportDialog} from '#/components/dms/AfterReportDialog'
|
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 {ArrowBoxLeft_Stroke2_Corner0_Rounded as LeaveIcon} from '#/components/icons/ArrowBoxLeft'
|
||||||
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
|
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
|
||||||
import {CircleX_Stroke2_Corner0_Rounded} from '#/components/icons/CircleX'
|
import {CircleX_Stroke2_Corner0_Rounded} from '#/components/icons/CircleX'
|
||||||
@@ -49,17 +55,18 @@ export function RejectMenu({
|
|||||||
}: Omit<ButtonProps, 'onPress' | 'children' | 'label'> & {
|
}: Omit<ButtonProps, 'onPress' | 'children' | 'label'> & {
|
||||||
label?: string
|
label?: string
|
||||||
icon?: boolean
|
icon?: boolean
|
||||||
convo: ChatBskyConvoDefs.ConvoView
|
convo: ConvoWithDetails
|
||||||
profile: ChatBskyActorDefs.ProfileViewBasic
|
profile: ChatBskyActorDefs.ProfileViewBasic
|
||||||
showDeleteConvo?: boolean
|
showDeleteConvo?: boolean
|
||||||
currentScreen: 'list' | 'conversation'
|
currentScreen: 'list' | 'conversation'
|
||||||
}) {
|
}) {
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
const shadowedProfile = useProfileShadow(profile)
|
const shadowedProfile = useProfileShadow(profile)
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const {mutate: leaveConvo} = useLeaveConvo(convo.id, {
|
const {mutate: leaveConvo} = useLeaveConvo(convo.view.id, {
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
if (currentScreen === 'conversation') {
|
if (currentScreen === 'conversation') {
|
||||||
navigation.dispatch(StackActions.pop())
|
navigation.dispatch(StackActions.pop())
|
||||||
@@ -110,9 +117,7 @@ export function RejectMenu({
|
|||||||
const reportControl = useDialogControl()
|
const reportControl = useDialogControl()
|
||||||
const blockOrDeleteControl = useDialogControl()
|
const blockOrDeleteControl = useDialogControl()
|
||||||
|
|
||||||
const lastMessage = ChatBskyConvoDefs.isMessageView(convo.lastMessage)
|
const reportSubject = getConvoReportSubject(convo, currentAccount?.did)
|
||||||
? convo.lastMessage
|
|
||||||
: null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -152,50 +157,46 @@ export function RejectMenu({
|
|||||||
</Menu.ItemText>
|
</Menu.ItemText>
|
||||||
<Menu.ItemIcon icon={PersonXIcon} />
|
<Menu.ItemIcon icon={PersonXIcon} />
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
{/* note: last message will almost certainly be defined, since you can't
|
<Menu.Item
|
||||||
delete messages for other people and it's impossible for a convo on this
|
label={l`Report conversation`}
|
||||||
screen to have a message sent by you */}
|
onPress={reportControl.open}>
|
||||||
{lastMessage && (
|
<Menu.ItemText>
|
||||||
<Menu.Item
|
<Trans>Report conversation</Trans>
|
||||||
label={l`Report conversation`}
|
</Menu.ItemText>
|
||||||
onPress={reportControl.open}>
|
<Menu.ItemIcon icon={FlagIcon} />
|
||||||
<Menu.ItemText>
|
</Menu.Item>
|
||||||
<Trans>Report conversation</Trans>
|
|
||||||
</Menu.ItemText>
|
|
||||||
<Menu.ItemIcon icon={FlagIcon} />
|
|
||||||
</Menu.Item>
|
|
||||||
)}
|
|
||||||
</Menu.Group>
|
</Menu.Group>
|
||||||
</Menu.Outer>
|
</Menu.Outer>
|
||||||
</Menu.Root>
|
</Menu.Root>
|
||||||
{lastMessage && (
|
|
||||||
<>
|
{reportSubject && (
|
||||||
<ReportDialog
|
<ReportDialog
|
||||||
subject={{
|
subject={reportSubject}
|
||||||
view: 'convo',
|
control={reportControl}
|
||||||
convoId: convo.id,
|
onAfterSubmit={() => {
|
||||||
message: lastMessage,
|
unstableCacheProfileView(queryClient, profile)
|
||||||
}}
|
blockOrDeleteControl.open()
|
||||||
control={reportControl}
|
}}
|
||||||
onAfterSubmit={() => {
|
/>
|
||||||
const sender = convo.members.find(
|
)}
|
||||||
member => member.did === lastMessage.sender.did,
|
{convo.kind === 'group' ? (
|
||||||
)
|
<AfterReportConversationDialog
|
||||||
if (sender) {
|
control={blockOrDeleteControl}
|
||||||
unstableCacheProfileView(queryClient, sender)
|
currentScreen={currentScreen}
|
||||||
}
|
params={{
|
||||||
blockOrDeleteControl.open()
|
convoId: convo.view.id,
|
||||||
}}
|
did: profile.did,
|
||||||
/>
|
}}
|
||||||
<AfterReportDialog
|
/>
|
||||||
control={blockOrDeleteControl}
|
) : (
|
||||||
currentScreen={currentScreen}
|
<AfterReportDialog
|
||||||
params={{
|
control={blockOrDeleteControl}
|
||||||
convoId: convo.id,
|
currentScreen={currentScreen}
|
||||||
did: lastMessage.sender.did,
|
params={{
|
||||||
}}
|
convoId: convo.view.id,
|
||||||
/>
|
did: profile.did,
|
||||||
</>
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user