Add ability to add members to an existing group clip clops (#10344)
This commit is contained in:
@@ -20,6 +20,7 @@ import {type Shadow} from '#/state/cache/types'
|
|||||||
import {ConvoProvider, useConvo} from '#/state/messages/convo'
|
import {ConvoProvider, useConvo} from '#/state/messages/convo'
|
||||||
import {ConvoStatus} from '#/state/messages/convo/types'
|
import {ConvoStatus} from '#/state/messages/convo/types'
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
|
import {useAddGroupMembers} from '#/state/queries/messages/add-group-members'
|
||||||
import {useEditGroupName} from '#/state/queries/messages/edit-group-name'
|
import {useEditGroupName} from '#/state/queries/messages/edit-group-name'
|
||||||
import {useGetConvoAvailabilityQuery} from '#/state/queries/messages/get-convo-availability'
|
import {useGetConvoAvailabilityQuery} from '#/state/queries/messages/get-convo-availability'
|
||||||
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
|
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
|
||||||
@@ -82,7 +83,7 @@ type Item =
|
|||||||
| {
|
| {
|
||||||
type: 'CHAT_MEMBER'
|
type: 'CHAT_MEMBER'
|
||||||
profile: Shadow<bsky.profile.AnyProfileView>
|
profile: Shadow<bsky.profile.AnyProfileView>
|
||||||
status: 'owner' | 'member' | 'invited'
|
status: 'owner' | 'standard' | 'invited'
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<
|
type Props = NativeStackScreenProps<
|
||||||
@@ -136,7 +137,6 @@ function SettingsInner({convoId}: {convoId: string}) {
|
|||||||
const isOwner = !!primaryMember && primaryMember.did === currentAccount?.did
|
const isOwner = !!primaryMember && primaryMember.did === currentAccount?.did
|
||||||
|
|
||||||
const data: bsky.profile.AnyProfileView[] = convo?.members ?? []
|
const data: bsky.profile.AnyProfileView[] = convo?.members ?? []
|
||||||
const invites: string[] = []
|
|
||||||
|
|
||||||
const {data: joinRequestsData, hasNextPage: hasMoreRequests} =
|
const {data: joinRequestsData, hasNextPage: hasMoreRequests} =
|
||||||
useListJoinRequestsQuery({
|
useListJoinRequestsQuery({
|
||||||
@@ -169,12 +169,7 @@ function SettingsInner({convoId}: {convoId: string}) {
|
|||||||
.map(profile => ({
|
.map(profile => ({
|
||||||
type: 'CHAT_MEMBER',
|
type: 'CHAT_MEMBER',
|
||||||
profile,
|
profile,
|
||||||
status:
|
status: primaryMember?.did === profile.did ? 'owner' : 'standard',
|
||||||
primaryMember?.did === profile.did
|
|
||||||
? 'owner'
|
|
||||||
: invites.includes(profile.did)
|
|
||||||
? 'invited'
|
|
||||||
: 'member',
|
|
||||||
})),
|
})),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -190,7 +185,7 @@ function SettingsInner({convoId}: {convoId: string}) {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'ADD_MEMBERS_LINK':
|
case 'ADD_MEMBERS_LINK':
|
||||||
return <AddMembersLink isOwner={isOwner} />
|
return <AddMembersLink convo={convo} isOwner={isOwner} />
|
||||||
case 'CHAT_MEMBER':
|
case 'CHAT_MEMBER':
|
||||||
return (
|
return (
|
||||||
<Member
|
<Member
|
||||||
@@ -292,12 +287,26 @@ function MembersAndRequests({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function AddMembersLink({isOwner}: {isOwner: boolean}) {
|
function AddMembersLink({
|
||||||
|
convo,
|
||||||
|
isOwner,
|
||||||
|
}: {
|
||||||
|
convo: ConvoWithDetails | null
|
||||||
|
isOwner: boolean
|
||||||
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {t: l} = useLingui()
|
const {t: l} = useLingui()
|
||||||
|
|
||||||
const addMembersControl = Dialog.useDialogControl()
|
const addMembersControl = Dialog.useDialogControl()
|
||||||
|
|
||||||
|
const convoId = convo?.view.id
|
||||||
|
const {mutate: addGroupMembers} = useAddGroupMembers(convoId, {
|
||||||
|
onError: e => {
|
||||||
|
logger.error('Failed to add group chat members', {message: e})
|
||||||
|
Toast.show(l`Failed to add members`, {type: 'error'})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
if (!isOwner) {
|
if (!isOwner) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -371,8 +380,8 @@ function AddMembersLink({isOwner}: {isOwner: boolean}) {
|
|||||||
<Dialog.Handle />
|
<Dialog.Handle />
|
||||||
<AddMembersFlow
|
<AddMembersFlow
|
||||||
title={l`Add members`}
|
title={l`Add members`}
|
||||||
onAddMembers={(_dids: string[]) => {
|
onAddMembers={members => {
|
||||||
// TODO Add members here
|
addGroupMembers({members})
|
||||||
addMembersControl.close()
|
addMembersControl.close()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -387,7 +396,7 @@ function Member({
|
|||||||
isOwner,
|
isOwner,
|
||||||
}: {
|
}: {
|
||||||
profile: Shadow<bsky.profile.AnyProfileView>
|
profile: Shadow<bsky.profile.AnyProfileView>
|
||||||
status: 'owner' | 'member' | 'invited'
|
status: 'owner' | 'standard' | 'invited'
|
||||||
isOwner: boolean
|
isOwner: boolean
|
||||||
}) {
|
}) {
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
@@ -533,7 +542,7 @@ function MemberMenu({
|
|||||||
isOwner,
|
isOwner,
|
||||||
}: {
|
}: {
|
||||||
profile: Shadow<bsky.profile.AnyProfileView>
|
profile: Shadow<bsky.profile.AnyProfileView>
|
||||||
type: 'owner' | 'member' | 'invited'
|
type: 'owner' | 'standard' | 'invited'
|
||||||
isOwner: boolean
|
isOwner: boolean
|
||||||
}) {
|
}) {
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
@@ -688,7 +697,7 @@ function MemberMenu({
|
|||||||
</Menu.Group>
|
</Menu.Group>
|
||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
<Menu.Group>
|
<Menu.Group>
|
||||||
{type === 'owner' || type === 'member' ? (
|
{type === 'owner' || type === 'standard' ? (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
label={
|
label={
|
||||||
profile.viewer?.blocking
|
profile.viewer?.blocking
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
|
import {logger} from '#/logger'
|
||||||
import {type ConvoState} from '#/state/messages/convo/types'
|
import {type ConvoState} from '#/state/messages/convo/types'
|
||||||
|
import {useAddGroupMembers} from '#/state/queries/messages/add-group-members'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {AvatarBubbles} from '#/components/AvatarBubbles'
|
import {AvatarBubbles} from '#/components/AvatarBubbles'
|
||||||
@@ -10,6 +12,7 @@ import * as Dialog from '#/components/Dialog'
|
|||||||
import {AddMembersFlow} from '#/components/dms/AddMembersFlow'
|
import {AddMembersFlow} from '#/components/dms/AddMembersFlow'
|
||||||
import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
|
import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
|
||||||
import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
|
import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
|
||||||
|
import * as Toast from '#/components/Toast'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
export function MessagesListInfoPanel({convoState}: {convoState: ConvoState}) {
|
export function MessagesListInfoPanel({convoState}: {convoState: ConvoState}) {
|
||||||
@@ -20,6 +23,14 @@ export function MessagesListInfoPanel({convoState}: {convoState: ConvoState}) {
|
|||||||
|
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
|
|
||||||
|
const convoId = convoState.convo?.id
|
||||||
|
const {mutate: addGroupMembers} = useAddGroupMembers(convoId, {
|
||||||
|
onError: e => {
|
||||||
|
logger.error('Failed to add group chat members', {message: e})
|
||||||
|
Toast.show(l`Failed to add members`, {type: 'error'})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const isOwner =
|
const isOwner =
|
||||||
currentAccount?.did == null
|
currentAccount?.did == null
|
||||||
? false
|
? false
|
||||||
@@ -124,8 +135,8 @@ export function MessagesListInfoPanel({convoState}: {convoState: ConvoState}) {
|
|||||||
<Dialog.Handle />
|
<Dialog.Handle />
|
||||||
<AddMembersFlow
|
<AddMembersFlow
|
||||||
title={l`Add people`}
|
title={l`Add people`}
|
||||||
onAddMembers={(_dids: string[]) => {
|
onAddMembers={(members: string[]) => {
|
||||||
// TODO Add members here
|
addGroupMembers({members})
|
||||||
addMembersControl.close()
|
addMembersControl.close()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ export class Convo {
|
|||||||
this.getGroupInfo = this.getGroupInfo.bind(this)
|
this.getGroupInfo = this.getGroupInfo.bind(this)
|
||||||
this.getPrimaryMember = this.getPrimaryMember.bind(this)
|
this.getPrimaryMember = this.getPrimaryMember.bind(this)
|
||||||
this.updateGroupName = this.updateGroupName.bind(this)
|
this.updateGroupName = this.updateGroupName.bind(this)
|
||||||
|
this.updateGroupMembers = this.updateGroupMembers.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private commit() {
|
private commit() {
|
||||||
@@ -959,6 +960,18 @@ export class Convo {
|
|||||||
this.commit()
|
this.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateGroupMembers(members: ChatBskyActorDefs.ProfileViewBasic[]) {
|
||||||
|
if (this.convo) {
|
||||||
|
this.convo = {
|
||||||
|
...this.convo,
|
||||||
|
members,
|
||||||
|
}
|
||||||
|
this.sender = members.find(m => m.did === this.senderUserDid)
|
||||||
|
this.recipients = members.filter(m => m.did !== this.senderUserDid)
|
||||||
|
}
|
||||||
|
this.commit()
|
||||||
|
}
|
||||||
|
|
||||||
async processPendingMessages() {
|
async processPendingMessages() {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`processing messages (${this.pendingMessages.size} remaining)`,
|
`processing messages (${this.pendingMessages.size} remaining)`,
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ import {useAgent} from '#/state/session'
|
|||||||
|
|
||||||
export * from '#/state/messages/convo/util'
|
export * from '#/state/messages/convo/util'
|
||||||
|
|
||||||
|
function membersChanged(
|
||||||
|
a: ChatBskyConvoDefs.ConvoView['members'],
|
||||||
|
b: ChatBskyConvoDefs.ConvoView['members'],
|
||||||
|
) {
|
||||||
|
if (a.length !== b.length) return true
|
||||||
|
const aDids = new Set(a.map(m => m.did))
|
||||||
|
return b.some(m => !aDids.has(m.did))
|
||||||
|
}
|
||||||
|
|
||||||
const ChatContext = createContext<ConvoState | null>(null)
|
const ChatContext = createContext<ConvoState | null>(null)
|
||||||
ChatContext.displayName = 'ChatContext'
|
ChatContext.displayName = 'ChatContext'
|
||||||
|
|
||||||
@@ -139,6 +148,13 @@ export function ConvoProvider({
|
|||||||
) {
|
) {
|
||||||
convo.updateGroupName(data.kind.name)
|
convo.updateGroupName(data.kind.name)
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
data &&
|
||||||
|
convo.convo &&
|
||||||
|
membersChanged(data.members, convo.convo.members)
|
||||||
|
) {
|
||||||
|
convo.updateGroupMembers(data.members)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [convo, convoId, queryClient])
|
}, [convo, convoId, queryClient])
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import {
|
||||||
|
type ChatBskyConvoDefs,
|
||||||
|
type ChatBskyConvoListConvos,
|
||||||
|
type ChatBskyGroupAddMembers,
|
||||||
|
} from '@atproto/api'
|
||||||
|
import {
|
||||||
|
type InfiniteData,
|
||||||
|
useMutation,
|
||||||
|
useQueryClient,
|
||||||
|
} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {DM_SERVICE_HEADERS} from '#/lib/constants'
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {useAgent} from '#/state/session'
|
||||||
|
import {RQKEY as CONVO_KEY} from './conversation'
|
||||||
|
import {RQKEY_ROOT as CONVO_LIST_KEY} from './list-conversations'
|
||||||
|
|
||||||
|
export function useAddGroupMembers(
|
||||||
|
convoId: string | undefined,
|
||||||
|
{
|
||||||
|
onSuccess,
|
||||||
|
onError,
|
||||||
|
}: {
|
||||||
|
onSuccess?: (data: ChatBskyGroupAddMembers.OutputSchema) => void
|
||||||
|
onError?: (error: Error) => void
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const agent = useAgent()
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({members}: {members: string[]}) => {
|
||||||
|
if (!convoId) throw new Error('No convoId provided')
|
||||||
|
const {data} = await agent.chat.bsky.group.addMembers(
|
||||||
|
{convoId, members},
|
||||||
|
{headers: DM_SERVICE_HEADERS, encoding: 'application/json'},
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
onMutate: () => {
|
||||||
|
if (!convoId) return
|
||||||
|
|
||||||
|
const prevConvo = queryClient.getQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||||
|
CONVO_KEY(convoId),
|
||||||
|
)
|
||||||
|
const prevListEntries = queryClient.getQueriesData<
|
||||||
|
InfiniteData<ChatBskyConvoListConvos.OutputSchema>
|
||||||
|
>({queryKey: [CONVO_LIST_KEY]})
|
||||||
|
|
||||||
|
return {prevConvo, prevListEntries}
|
||||||
|
},
|
||||||
|
onSuccess: data => {
|
||||||
|
if (convoId) {
|
||||||
|
queryClient.setQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||||
|
CONVO_KEY(convoId),
|
||||||
|
data.convo,
|
||||||
|
)
|
||||||
|
|
||||||
|
queryClient.setQueriesData<
|
||||||
|
InfiniteData<ChatBskyConvoListConvos.OutputSchema>
|
||||||
|
>({queryKey: [CONVO_LIST_KEY]}, prev => {
|
||||||
|
if (!prev?.pages) return
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
pages: prev.pages.map(page => ({
|
||||||
|
...page,
|
||||||
|
convos: page.convos.map(convo =>
|
||||||
|
convo.id === convoId ? data.convo : convo,
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onSuccess?.(data)
|
||||||
|
},
|
||||||
|
onError: (e, _variables, context) => {
|
||||||
|
logger.error(e)
|
||||||
|
if (context?.prevConvo && convoId) {
|
||||||
|
queryClient.setQueryData(CONVO_KEY(convoId), context.prevConvo)
|
||||||
|
}
|
||||||
|
if (context?.prevListEntries) {
|
||||||
|
for (const [key, data] of context.prevListEntries) {
|
||||||
|
queryClient.setQueryData(key, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onError?.(e)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user