From a889a1b081ea486e7cf0f1f892742a8b3cf0ad71 Mon Sep 17 00:00:00 2001
From: DS Boyce <260543580+ds-boyce@users.noreply.github.com>
Date: Fri, 8 May 2026 12:05:05 -0700
Subject: [PATCH] Update presentation of chat requests (#10417)
---
src/components/KnownFollowers.tsx | 15 +-
src/components/dms/LeaveConvoPrompt.tsx | 17 +--
.../Messages/components/ChatStatusInfo.tsx | 94 ++++++++++--
.../Messages/components/RequestButtons.tsx | 134 +++++++++---------
.../Messages/components/RequestListItem.tsx | 2 +-
5 files changed, 160 insertions(+), 102 deletions(-)
diff --git a/src/components/KnownFollowers.tsx b/src/components/KnownFollowers.tsx
index b26216b2f7..502b08252f 100644
--- a/src/components/KnownFollowers.tsx
+++ b/src/components/KnownFollowers.tsx
@@ -5,9 +5,7 @@ import {
moderateProfile,
type ModerationOpts,
} from '@atproto/api'
-import {msg} from '@lingui/core/macro'
-import {useLingui} from '@lingui/react'
-import {Plural, Trans} from '@lingui/react/macro'
+import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {makeProfileLink} from '#/lib/routes/links'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
@@ -93,7 +91,7 @@ function KnownFollowersInner({
showIfEmpty?: boolean
}) {
const t = useTheme()
- const {_} = useLingui()
+ const {t: l} = useLingui()
const textStyle = [a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]
@@ -124,9 +122,7 @@ function KnownFollowersInner({
return (
{slice.length >= 2 ? (
// 2-n followers, including blocks
- serverCount > 2 ? ( // only 2
+ // only 2
+ serverCount > 2 ? (
Followed by{' '}
@@ -254,7 +251,7 @@ function EmptyFallback({show}: {show?: boolean}) {
return (
- Not followed by anyone you're following
+ Not followed by anyone you’re following
)
}
diff --git a/src/components/dms/LeaveConvoPrompt.tsx b/src/components/dms/LeaveConvoPrompt.tsx
index b000040455..f4e7ea843a 100644
--- a/src/components/dms/LeaveConvoPrompt.tsx
+++ b/src/components/dms/LeaveConvoPrompt.tsx
@@ -1,5 +1,4 @@
-import {msg} from '@lingui/core/macro'
-import {useLingui} from '@lingui/react'
+import {useLingui} from '@lingui/react/macro'
import {StackActions, useNavigation} from '@react-navigation/native'
import {type NavigationProp} from '#/lib/routes/types'
@@ -20,7 +19,7 @@ export function LeaveConvoPrompt({
currentScreen: 'list' | 'conversation'
hasMessages?: boolean
}) {
- const {_} = useLingui()
+ const {t: l} = useLingui()
const navigation = useNavigation()
const {mutate: leaveConvo} = useLeaveConvo(convoId, {
@@ -32,7 +31,7 @@ export function LeaveConvoPrompt({
}
},
onError: () => {
- Toast.show(_(msg`Could not leave chat`), {
+ Toast.show(l`Could not leave chat`, {
type: 'error',
})
},
@@ -41,15 +40,13 @@ export function LeaveConvoPrompt({
return (
leaveConvo()}
/>
diff --git a/src/screens/Messages/components/ChatStatusInfo.tsx b/src/screens/Messages/components/ChatStatusInfo.tsx
index 8f27c55b46..43158e4b1a 100644
--- a/src/screens/Messages/components/ChatStatusInfo.tsx
+++ b/src/screens/Messages/components/ChatStatusInfo.tsx
@@ -1,19 +1,26 @@
-import {useCallback} from 'react'
+import {useCallback, useMemo} from 'react'
import {View} from 'react-native'
-import {msg} from '@lingui/core/macro'
-import {useLingui} from '@lingui/react'
+import {LinearGradient} from 'expo-linear-gradient'
+import {ChatBskyConvoDefs, moderateProfile} from '@atproto/api'
+import {Trans, useLingui} from '@lingui/react/macro'
+import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
+import {sanitizeHandle} from '#/lib/strings/handles'
+import {useProfileShadow} from '#/state/cache/profile-shadow'
import {type ActiveConvoStates} from '#/state/messages/convo'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
-import {atoms as a, useTheme} from '#/alf'
+import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
+import {atoms as a, useTheme, web} from '#/alf'
import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt'
import {KnownFollowers} from '#/components/KnownFollowers'
import {usePromptControl} from '#/components/Prompt'
+import {Text} from '#/components/Typography'
+import type * as bsky from '#/types/bsky'
import {AcceptChatButton, DeleteChatButton, RejectMenu} from './RequestButtons'
export function ChatStatusInfo({convoState}: {convoState: ActiveConvoStates}) {
const t = useTheme()
- const {_} = useLingui()
+ const {t: l} = useLingui()
const moderationOpts = useModerationOpts()
const leaveConvoControl = usePromptControl()
@@ -25,36 +32,59 @@ 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
}
return (
-
+
+
+ {otherUser && (
+
+ )}
{otherUser && (
)}
{otherUser && (
)}
)
}
+
+function InviterHeader({
+ profile: profileUnshadowed,
+ moderationOpts,
+}: {
+ profile: bsky.profile.AnyProfileView
+ moderationOpts: NonNullable>
+}) {
+ const t = useTheme()
+ const profile = useProfileShadow(profileUnshadowed)
+ const moderation = useMemo(
+ () => moderateProfile(profile, moderationOpts),
+ [profile, moderationOpts],
+ )
+ const displayName = createSanitizedDisplayName(
+ profile,
+ true,
+ moderation.ui('displayName'),
+ )
+
+ return (
+
+
+
+
+ {displayName} added you
+
+
+ {sanitizeHandle(profile.handle, '@')}
+
+
+
+ )
+}
diff --git a/src/screens/Messages/components/RequestButtons.tsx b/src/screens/Messages/components/RequestButtons.tsx
index 5a3b92dbe9..7b0ec9d094 100644
--- a/src/screens/Messages/components/RequestButtons.tsx
+++ b/src/screens/Messages/components/RequestButtons.tsx
@@ -1,8 +1,6 @@
import {useCallback} from 'react'
import {type ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api'
-import {msg} from '@lingui/core/macro'
-import {useLingui} from '@lingui/react'
-import {Trans} from '@lingui/react/macro'
+import {Trans, useLingui} from '@lingui/react/macro'
import {StackActions, useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
@@ -16,7 +14,6 @@ import {
unstableCacheProfileView,
useProfileBlockMutationQueue,
} from '#/state/queries/profile'
-import {atoms as a} from '#/alf'
import {
Button,
ButtonIcon,
@@ -29,6 +26,8 @@ import {
useEmailDialogControl,
} from '#/components/dialogs/EmailDialog'
import {AfterReportDialog} from '#/components/dms/AfterReportDialog'
+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'
import {Flag_Stroke2_Corner0_Rounded as FlagIcon} from '#/components/icons/Flag'
import {PersonX_Stroke2_Corner0_Rounded as PersonXIcon} from '#/components/icons/Person'
@@ -40,20 +39,22 @@ import * as Toast from '#/components/Toast'
export function RejectMenu({
convo,
profile,
- size = 'tiny',
+ size = 'small',
color = 'secondary',
label,
+ icon = false,
showDeleteConvo,
currentScreen,
...props
}: Omit & {
label?: string
+ icon?: boolean
convo: ChatBskyConvoDefs.ConvoView
profile: ChatBskyActorDefs.ProfileViewBasic
showDeleteConvo?: boolean
currentScreen: 'list' | 'conversation'
}) {
- const {_} = useLingui()
+ const {t: l} = useLingui()
const shadowedProfile = useProfileShadow(profile)
const navigation = useNavigation()
const queryClient = useQueryClient()
@@ -66,12 +67,10 @@ export function RejectMenu({
},
onError: () => {
Toast.show(
- _(
- msg({
- context: 'toast',
- message: 'Failed to delete chat',
- }),
- ),
+ l({
+ context: 'toast',
+ message: 'Failed to delete chat',
+ }),
{
type: 'error',
},
@@ -82,35 +81,31 @@ export function RejectMenu({
const onPressDelete = useCallback(() => {
Toast.show(
- _(
- msg({
- context: 'toast',
- message: 'Chat deleted',
- }),
- ),
+ l({
+ context: 'toast',
+ message: 'Chat deleted',
+ }),
{
type: 'success',
},
)
leaveConvo()
- }, [leaveConvo, _])
+ }, [leaveConvo, l])
const onPressBlock = useCallback(() => {
Toast.show(
- _(
- msg({
- context: 'toast',
- message: 'Account blocked',
- }),
- ),
+ l({
+ context: 'toast',
+ message: 'Account blocked',
+ }),
{
type: 'success',
},
)
// block and also delete convo
- queueBlock()
+ void queueBlock()
leaveConvo()
- }, [queueBlock, leaveConvo, _])
+ }, [queueBlock, leaveConvo, l])
const reportControl = useDialogControl()
const blockOrDeleteControl = useDialogControl()
@@ -122,15 +117,15 @@ export function RejectMenu({
return (
<>
-
+
{({props: triggerProps}) => (
)}
{/* spacer, since you can't nest pressables */}
-
+
{/* Placeholder text so that it responds to the font height */}
Accept Request