diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx
index 0a1d3f01cc..92315da466 100644
--- a/src/components/dms/ConvoMenu.tsx
+++ b/src/components/dms/ConvoMenu.tsx
@@ -12,6 +12,10 @@ import {
} from '#/state/queries/messages/conversation'
import {useLeaveConvo} from '#/state/queries/messages/leave-conversation'
import {useMuteConvo} from '#/state/queries/messages/mute-conversation'
+import {
+ useAwaitedProfileBlockMutation,
+ useAwaitedProfileUnblockMutation,
+} from '#/state/queries/profile'
import * as Toast from '#/view/com/util/Toast'
import {atoms as a, useTheme} from '#/alf'
import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeft} from '#/components/icons/ArrowBoxLeft'
@@ -34,10 +38,11 @@ let ConvoMenu = ({
showMarkAsRead,
hideTrigger,
triggerOpacity,
+ onUpdateConvo,
}: {
convo: ChatBskyConvoDefs.ConvoView
profile: AppBskyActorDefs.ProfileViewBasic
- onUpdateConvo?: (convo: ChatBskyConvoDefs.ConvoView) => void
+ onUpdateConvo?: () => void
control?: Menu.MenuControlProps
currentScreen: 'list' | 'conversation'
showMarkAsRead?: boolean
@@ -64,12 +69,41 @@ let ConvoMenu = ({
} else {
Toast.show(_(msg`Chat unmuted`))
}
+ onUpdateConvo?.()
},
onError: () => {
Toast.show(_(msg`Could not mute chat`))
},
})
+ const {mutateAsync: blockProfile, isPending: isBlockPending} =
+ useAwaitedProfileBlockMutation()
+ const {mutateAsync: unblockProfile, isPending: isUnblockPending} =
+ useAwaitedProfileUnblockMutation()
+ const isBlockMutationPending = isBlockPending || isUnblockPending
+ const isBlocking = Boolean(profile.viewer?.blocking)
+
+ const toggleBlock = useCallback(async () => {
+ if (isBlockMutationPending) return
+
+ if (profile.viewer?.blocking) {
+ await unblockProfile({
+ subject: profile.did,
+ blockUri: profile.viewer?.blocking,
+ })
+ } else {
+ await blockProfile({subject: profile.did})
+ }
+
+ onUpdateConvo?.()
+ }, [
+ profile,
+ isBlockMutationPending,
+ blockProfile,
+ unblockProfile,
+ onUpdateConvo,
+ ])
+
const {mutate: leaveConvo} = useLeaveConvo(convo?.id, {
onSuccess: () => {
if (currentScreen === 'conversation') {
@@ -146,18 +180,17 @@ let ConvoMenu = ({
- {/* TODO(samuel): implement this */}
{}}
- disabled>
+ disabled={isBlockMutationPending}
+ label={
+ isBlocking ? _(msg`Unblock account`) : _(msg`Block account`)
+ }
+ onPress={toggleBlock}>
- Block account
+ {isBlocking ? _(msg`Unblock account`) : _(msg`Block account`)}
-
+
) : (
diff --git a/src/screens/Messages/List/ChatListItem.tsx b/src/screens/Messages/List/ChatListItem.tsx
index 57a8e03480..080593d755 100644
--- a/src/screens/Messages/List/ChatListItem.tsx
+++ b/src/screens/Messages/List/ChatListItem.tsx
@@ -4,9 +4,11 @@ import {ChatBskyConvoDefs} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
+import {useQueryClient} from '@tanstack/react-query'
import {NavigationProp} from '#/lib/routes/types'
import {isNative} from '#/platform/detection'
+import {RQKEY as ListConvosQueryKey} from '#/state/queries/messages/list-converations'
import {useSession} from '#/state/session'
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
import {UserAvatar} from '#/view/com/util/UserAvatar'
@@ -36,6 +38,7 @@ export function ChatListItem({
const displayName = isDeletedAccount
? 'Deleted Account'
: otherUser?.displayName || otherUser?.handle
+ const queryClient = useQueryClient()
let lastMessage = _(msg`No messages yet`)
let lastMessageSentAt: string | null = null
@@ -73,6 +76,10 @@ export function ChatListItem({
})
}, [convo.id, navigation])
+ const onUpdateConvo = React.useCallback(() => {
+ queryClient.invalidateQueries({queryKey: ListConvosQueryKey})
+ }, [queryClient])
+
if (!otherUser) {
return null
}
@@ -204,6 +211,7 @@ export function ChatListItem({
triggerOpacity={
!gtMobile || showActions || menuControl.isOpen ? 1 : 0
}
+ onUpdateConvo={onUpdateConvo}
/>
diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts
index 3e25359166..de62a7eee0 100644
--- a/src/state/queries/profile.ts
+++ b/src/state/queries/profile.ts
@@ -384,6 +384,9 @@ function useProfileUnmuteMutation() {
export function useProfileBlockMutationQueue(
profile: Shadow,
+ options?: {
+ onSuccess?: () => void
+ },
) {
const queryClient = useQueryClient()
const did = profile.did
@@ -414,6 +417,7 @@ export function useProfileBlockMutationQueue(
updateProfileShadow(queryClient, did, {
blockingUri: finalBlockingUri,
})
+ options?.onSuccess?.()
},
})
@@ -478,6 +482,60 @@ function useProfileUnblockMutation() {
})
}
+export function useAwaitedProfileBlockMutation() {
+ const {currentAccount} = useSession()
+ const {getAgent} = useAgent()
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: async ({subject}: {subject: string}) => {
+ if (!currentAccount) {
+ throw new Error('Not signed in')
+ }
+ await getAgent().app.bsky.graph.block.create(
+ {repo: currentAccount.did},
+ {subject: subject, createdAt: new Date().toISOString()},
+ )
+ await whenAppViewReady(
+ getAgent,
+ subject,
+ res => !!res.data.viewer?.blocking,
+ )
+ },
+ onSuccess(_, {subject}) {
+ queryClient.invalidateQueries({queryKey: RQKEY_MY_BLOCKED()})
+ resetProfilePostsQueries(queryClient, subject, 1000)
+ },
+ })
+}
+
+export function useAwaitedProfileUnblockMutation() {
+ const {currentAccount} = useSession()
+ const {getAgent} = useAgent()
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: async ({blockUri, subject}) => {
+ if (!currentAccount) {
+ throw new Error('Not signed in')
+ }
+ const {rkey} = new AtUri(blockUri)
+ await getAgent().app.bsky.graph.block.delete({
+ repo: currentAccount.did,
+ rkey,
+ })
+ await whenAppViewReady(
+ getAgent,
+ subject,
+ res => !res.data.viewer?.blocking,
+ )
+ },
+ onSuccess(_, {subject}) {
+ resetProfilePostsQueries(queryClient, subject, 1000)
+ },
+ })
+}
+
export function precacheProfile(
queryClient: QueryClient,
profile: AppBskyActorDefs.ProfileViewBasic,