Support group chats across other surfaces (#10266)

This commit is contained in:
Samuel Newman
2026-04-16 09:55:30 -07:00
committed by GitHub
parent cc8f22887f
commit f51602b3fe
11 changed files with 436 additions and 161 deletions
+16 -1
View File
@@ -77,6 +77,15 @@ export function NewChat({
[control, createGroupChat],
)
const onSelectExistingChat = useCallback(
(chatId: string) => {
control.close(() => {
onNewChat(chatId)
})
},
[control, onNewChat],
)
const onPress = useCallback(() => {
control.open()
}, [control])
@@ -112,7 +121,13 @@ export function NewChat({
) : (
<SearchablePeopleList
title={l`Start a new chat`}
onSelectChat={onCreateChat}
onSelectChat={chat => {
if (chat.kind === 'user') {
onCreateChat(chat.did)
} else {
onSelectExistingChat(chat.id)
}
}}
sortByMessageDeclaration
/>
)}
@@ -53,6 +53,13 @@ function SendViaChatDialogInner({
},
})
const onSelectExistingChat = useCallback(
(chatId: string) => {
control.close(() => onSelectChat(chatId))
},
[control, onSelectChat],
)
const onCreateChat = useCallback(
(did: string) => {
control.close(() => createChat([did]))
@@ -63,7 +70,13 @@ function SendViaChatDialogInner({
return (
<SearchablePeopleList
title={_(msg`Send post to...`)}
onSelectChat={onCreateChat}
onSelectChat={chat => {
if (chat.kind === 'user') {
onCreateChat(chat.did)
} else {
onSelectExistingChat(chat.id)
}
}}
showRecentConvos
sortByMessageDeclaration
/>
-1
View File
@@ -1 +0,0 @@
export {BottomSheetTextInput as TextInput} from '@discord/bottom-sheet/src'
@@ -1 +0,0 @@
export {TextInput} from 'react-native'
+99 -2
View File
@@ -1,7 +1,8 @@
import {type ChatBskyConvoDefs} from '@atproto/api'
import {type $Typed, ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api'
import {EMOJI_REACTION_LIMIT} from '#/lib/constants'
import type * as bsky from '#/types/bsky'
import {logger} from '#/logger'
import * as bsky from '#/types/bsky'
export function canBeMessaged(profile: bsky.profile.AnyProfileView) {
switch (profile.associated?.chat?.allowIncoming) {
@@ -54,3 +55,99 @@ export function hasReachedReactionLimit(
)
return myReactions.length >= EMOJI_REACTION_LIMIT
}
type GroupConvoMember = ChatBskyActorDefs.ProfileViewBasic & {
// can be missing if account deleted
kind?: $Typed<ChatBskyActorDefs.GroupConvoMember>
}
type DirectConvoMember = ChatBskyActorDefs.ProfileViewBasic & {
kind: $Typed<ChatBskyActorDefs.DirectConvoMember>
}
export type ConvoWithDetails = {view: ChatBskyConvoDefs.ConvoView} & (
| {
kind: 'group'
details: ChatBskyConvoDefs.GroupConvo
primaryMember: GroupConvoMember // the owner
members: Array<GroupConvoMember>
}
| {
kind: 'direct'
details: ChatBskyConvoDefs.DirectConvo
primaryMember: DirectConvoMember // the other user
members: Array<DirectConvoMember>
}
)
/**
* Converts a raw convoView into something easier to use (i.e. extracts chat owner)
* and enforces the correct type for convo members.
*/
export function parseConvoView(
convoView: ChatBskyConvoDefs.ConvoView,
ownDid: string | undefined,
): ConvoWithDetails | null {
if (
bsky.dangerousIsType<ChatBskyConvoDefs.GroupConvo>(
convoView.kind,
ChatBskyConvoDefs.isGroupConvo,
)
) {
let owner: GroupConvoMember | undefined = undefined
for (const member of convoView.members) {
if (
bsky.dangerousIsType<ChatBskyActorDefs.GroupConvoMember>(
member.kind,
ChatBskyActorDefs.isGroupConvoMember,
)
) {
if (member.kind.role === 'owner') {
// have to do a type assertion here
// this works: {...member, kind: member.kind}
// however that's creating a new object for no good reason
owner = member as GroupConvoMember
}
} else {
throw new Error(
'Expected a GroupConvoMember, got an unknown kind of member',
)
}
}
if (!owner) {
throw new Error('No owner found in group convo')
}
return {
view: convoView,
kind: 'group',
details: convoView.kind,
primaryMember: owner,
members: convoView.members as Array<GroupConvoMember>,
}
} else if (
bsky.dangerousIsType<ChatBskyConvoDefs.DirectConvo>(
convoView.kind,
ChatBskyConvoDefs.isDirectConvo,
)
) {
const otherUser = convoView.members.find(m => m.did !== ownDid)
if (!otherUser) {
throw new Error('No other user found in direct convo')
}
return {
view: convoView,
kind: 'direct',
details: convoView.kind,
primaryMember: otherUser as DirectConvoMember,
members: convoView.members as Array<DirectConvoMember>,
}
} else {
logger.warn('Unknown convo kind: ' + JSON.stringify(convoView.kind))
return null
}
}