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,