Use new listConvoRequests endpoint (#10755)

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
DS Boyce
2026-06-08 04:36:34 -07:00
committed by GitHub
parent c02f2a1eda
commit e60e31733a
12 changed files with 628 additions and 180 deletions
+40 -23
View File
@@ -1,8 +1,9 @@
import {useCallback, useMemo, useState} from 'react'
import {View} from 'react-native'
import {
type ChatBskyConvoDefs,
type ChatBskyConvoListConvos,
ChatBskyConvoDefs,
type ChatBskyConvoListConvoRequests,
ChatBskyGroupDefs,
} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {useFocusEffect, useNavigation} from '@react-navigation/native'
@@ -23,7 +24,7 @@ import {logger} from '#/logger'
import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const'
import {useMessagesEventBus} from '#/state/messages/events'
import {useLeftConvos} from '#/state/queries/messages/leave-conversation'
import {useListConvosQuery} from '#/state/queries/messages/list-conversations'
import {useListConvoRequests} from '#/state/queries/messages/list-conversation-requests'
import {useUpdateAllRead} from '#/state/queries/messages/update-all-read'
import {EmptyState} from '#/view/com/util/EmptyState'
import {List} from '#/view/com/util/List'
@@ -43,11 +44,16 @@ import {ListFooter} from '#/components/Lists'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {IS_NATIVE} from '#/env'
import {RequestListItem} from './components/RequestListItem'
import {IncomingRequestListItem} from './components/IncomingRequestListItem'
import {OutgoingRequestListItem} from './components/OutgoingRequestListItem'
import {useIsWithinSplitView} from './components/splitView/context'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesInbox'>
type RequestItem =
| {type: 'incoming'; view: ChatBskyConvoDefs.ConvoView}
| {type: 'outgoing'; view: ChatBskyGroupDefs.JoinRequestConvoView}
export function MessagesInboxScreen(props: Props) {
const {t: l} = useLingui()
const aaCopy = useAgeAssuranceCopy()
@@ -61,29 +67,36 @@ export function MessagesInboxScreen(props: Props) {
}
export function MessagesInboxScreenInner({}: Props) {
const listConvosQuery = useListConvosQuery({status: 'request'})
const listConvosQuery = useListConvoRequests()
const {data} = listConvosQuery
const leftConvos = useLeftConvos()
const conversations = useMemo(() => {
if (data?.pages) {
const convos = data.pages
.flatMap(page => page.convos)
// filter out convos that are actively being left
.filter(convo => !leftConvos.includes(convo.id))
return convos
const conversations = useMemo<RequestItem[]>(() => {
if (!data?.pages) return []
const items: RequestItem[] = []
for (const page of data.pages) {
for (const item of page.requests) {
if (ChatBskyConvoDefs.isConvoView(item)) {
// filter out convos that are actively being left
if (leftConvos.includes(item.id)) continue
items.push({type: 'incoming', view: item})
} else if (ChatBskyGroupDefs.isJoinRequestConvoView(item)) {
items.push({type: 'outgoing', view: item})
}
}
}
return []
return items
}, [data, leftConvos])
const hasUnreadConvos = useMemo(() => {
return conversations.some(
conversation =>
conversation.members.every(
item =>
item.type === 'incoming' &&
item.view.members.every(
member => member.handle !== 'missing.invalid',
) && conversation.unreadCount > 0,
) &&
item.view.unreadCount > 0,
)
}, [conversations])
@@ -111,10 +124,10 @@ function RequestList({
conversations,
}: {
listConvosQuery: UseInfiniteQueryResult<
InfiniteData<ChatBskyConvoListConvos.OutputSchema>,
InfiniteData<ChatBskyConvoListConvoRequests.OutputSchema>,
Error
>
conversations: ChatBskyConvoDefs.ConvoView[]
conversations: RequestItem[]
}) {
const {t: l} = useLingui()
const t = useTheme()
@@ -274,17 +287,21 @@ function RequestList({
windowSize={11}
desktopFixedHeight
sideBorders={false}
contentContainerStyle={[web(a.py_sm)]}
/>
</>
)
}
function keyExtractor(item: ChatBskyConvoDefs.ConvoView) {
return item.id
function keyExtractor(item: RequestItem) {
return item.type === 'incoming' ? item.view.id : item.view.convoId
}
function renderItem({item}: {item: ChatBskyConvoDefs.ConvoView}) {
return <RequestListItem convo={item} />
function renderItem({item}: {item: RequestItem}) {
if (item.type === 'incoming') {
return <IncomingRequestListItem convo={item.view} />
}
return <OutgoingRequestListItem convo={item.view} />
}
function MarkAsReadHeaderButton() {
@@ -11,7 +11,7 @@ import {Text} from '#/components/Typography'
import {ChatListItem, ChatListItemPortal} from './ChatListItem'
import {AcceptChatButton, DeleteChatButton, RejectMenu} from './RequestButtons'
export function RequestListItem({
export function IncomingRequestListItem({
convo: convoView,
}: {
convo: ChatBskyConvoDefs.ConvoView
@@ -0,0 +1,128 @@
import {View} from 'react-native'
import {
type ChatBskyGroupDefs,
ChatBskyGroupWithdrawJoinRequest,
} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {isNetworkError} from '#/lib/strings/errors'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useWithdrawJoinGroupChatRequest} from '#/state/queries/messages/withdraw-join-group-chat'
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
import {atoms as a, useTheme, web} from '#/alf'
import {AvatarBubbles} from '#/components/AvatarBubbles'
import {createStaticClick, Link} from '#/components/Link'
import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
export function OutgoingRequestListItem({
convo: convoView,
}: {
convo: ChatBskyGroupDefs.JoinRequestConvoView
}) {
const t = useTheme()
const {t: l} = useLingui()
const prompt = Prompt.usePromptControl()
const moderationOpts = useModerationOpts()
const {mutate: withdrawRequest, isPending: isWithdrawPending} =
useWithdrawJoinGroupChatRequest({
onSuccess: () => {
Toast.show(l`Join request rescinded.`)
},
onError: error => {
let errorMessage = l`Failed to rescind your request. Please try again.`
if (isNetworkError(error)) {
errorMessage = l`There was a problem with your internet connection, please try again`
} else if (
error instanceof
ChatBskyGroupWithdrawJoinRequest.InvalidJoinRequestError
) {
errorMessage = l`Invalid rescind request.`
}
Toast.show(errorMessage)
},
})
return (
<>
<Link
label={l`Rescind request to join group chat`}
{...createStaticClick(() => {
prompt.open()
})}>
{({hovered, pressed, focused}) => (
<View
style={[
a.flex_row,
a.align_center,
a.flex_1,
a.px_lg,
a.py_md,
a.gap_md,
(hovered || pressed || focused) && t.atoms.bg_contrast_25,
]}>
<AvatarBubbles
profiles={[
convoView?.owner ?? undefined,
...Array(
Math.min(3, Math.max(0, convoView.memberCount - 1)),
).fill(undefined),
]}
size={48}
moderationOpts={moderationOpts}
/>
<View style={[a.flex_1]}>
<View style={[a.w_full, a.flex_row, a.align_center, a.pb_2xs]}>
<View style={[a.flex_shrink]}>
<Text
emoji
numberOfLines={1}
style={[a.text_md, a.font_semi_bold]}>
{convoView.name}
</Text>
</View>
<View style={[a.pl_xs]}>
<TimeElapsed timestamp={convoView.requestedAt}>
{({timeElapsed}) => (
<Text
style={[
a.text_sm,
t.atoms.text_contrast_medium,
web({whiteSpace: 'preserve nowrap'}),
]}>
{timeElapsed}
</Text>
)}
</TimeElapsed>
</View>
</View>
<Text
numberOfLines={1}
style={[a.text_sm, t.atoms.text_contrast_high]}>
<Trans comment="Displayed when the user has requested to join a group chat.">
You requested to join
</Trans>
</Text>
</View>
</View>
)}
</Link>
<Prompt.Basic
control={prompt}
title={l`Rescind request`}
description={l`Are you sure you want to rescind your request to join ${convoView.name}?`}
confirmButtonCta={l`Rescind request`}
onConfirm={() => {
prompt.close(() => {
if (isWithdrawPending) return
withdrawRequest({convoId: convoView.convoId})
})
}}
/>
</>
)
}