diff --git a/src/components/ReportDialog/SubmitView.tsx b/src/components/ReportDialog/SubmitView.tsx
index ef4a9b7fbc..36bd1d4667 100644
--- a/src/components/ReportDialog/SubmitView.tsx
+++ b/src/components/ReportDialog/SubmitView.tsx
@@ -16,6 +16,7 @@ import * as Dialog from '#/components/Dialog'
import * as Toggle from '#/components/forms/Toggle'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
+import {PaperPlane_Stroke2_Corner0_Rounded as SendIcon} from '#/components/icons/PaperPlane'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import {ReportDialogProps} from './types'
@@ -223,7 +224,7 @@ export function SubmitView({
Send report
- {submitting && }
+
{/* Maybe fix this later -h */}
diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx
index ba1d4ee543..5b4b68149c 100644
--- a/src/components/dms/ConvoMenu.tsx
+++ b/src/components/dms/ConvoMenu.tsx
@@ -227,6 +227,7 @@ let ConvoMenu = ({
/>
{latestReportableMessage ? (
()
const {mutate: leaveConvo} = useLeaveConvo(convoId, {
- onSuccess: () => {
+ onMutate: () => {
if (currentScreen === 'conversation') {
- navigation.replace(
- 'Messages',
- isNative
- ? {
- animation: 'pop',
- }
- : {},
+ navigation.dispatch(
+ StackActions.replace('Messages', isNative ? {animation: 'pop'} : {}),
)
}
},
diff --git a/src/components/dms/MessageMenu.tsx b/src/components/dms/MessageMenu.tsx
index 90ee5b9793..fb5474dd10 100644
--- a/src/components/dms/MessageMenu.tsx
+++ b/src/components/dms/MessageMenu.tsx
@@ -138,6 +138,7 @@ export let MessageMenu = ({
diff --git a/src/components/dms/ReportDialog.tsx b/src/components/dms/ReportDialog.tsx
index 06d69ff4be..c9383ff6db 100644
--- a/src/components/dms/ReportDialog.tsx
+++ b/src/components/dms/ReportDialog.tsx
@@ -1,27 +1,39 @@
import React, {memo, useMemo, useState} from 'react'
import {View} from 'react-native'
import {
+ AppBskyActorDefs,
ChatBskyConvoDefs,
ComAtprotoModerationCreateReport,
RichText as RichTextAPI,
} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+import {StackActions, useNavigation} from '@react-navigation/native'
import {useMutation} from '@tanstack/react-query'
import {ReportOption} from '#/lib/moderation/useReportOptions'
+import {NavigationProp} from '#/lib/routes/types'
+import {isNative} from '#/platform/detection'
+import {useProfileShadow} from '#/state/cache/profile-shadow'
+import {useLeaveConvo} from '#/state/queries/messages/leave-conversation'
+import {
+ useProfileBlockMutationQueue,
+ useProfileQuery,
+} from '#/state/queries/profile'
import {useAgent} from '#/state/session'
import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
import * as Toast from '#/view/com/util/Toast'
-import {atoms as a, useBreakpoints, useTheme} from '#/alf'
+import {atoms as a, platform, useBreakpoints, useTheme, web} from '#/alf'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
-import {Button, ButtonIcon, ButtonText} from '../Button'
-import {Divider} from '../Divider'
-import {ChevronLeft_Stroke2_Corner0_Rounded as Chevron} from '../icons/Chevron'
-import {Loader} from '../Loader'
-import {SelectReportOptionView} from '../ReportDialog/SelectReportOptionView'
-import {RichText} from '../RichText'
-import {Text} from '../Typography'
+import {Divider} from '#/components/Divider'
+import * as Toggle from '#/components/forms/Toggle'
+import {ChevronLeft_Stroke2_Corner0_Rounded as Chevron} from '#/components/icons/Chevron'
+import {PaperPlane_Stroke2_Corner0_Rounded as SendIcon} from '#/components/icons/PaperPlane'
+import {Loader} from '#/components/Loader'
+import {SelectReportOptionView} from '#/components/ReportDialog/SelectReportOptionView'
+import {RichText} from '#/components/RichText'
+import {Text} from '#/components/Typography'
import {MessageItemMetadata} from './MessageItem'
type ReportDialogParams = {
@@ -33,16 +45,18 @@ type ReportDialogParams = {
let ReportDialog = ({
control,
params,
+ currentScreen,
}: {
control: Dialog.DialogControlProps
params: ReportDialogParams
+ currentScreen: 'list' | 'conversation'
}): React.ReactNode => {
const {_} = useLingui()
return (
-
+
@@ -51,14 +65,44 @@ let ReportDialog = ({
ReportDialog = memo(ReportDialog)
export {ReportDialog}
-function DialogInner({params}: {params: ReportDialogParams}) {
+function DialogInner({
+ params,
+ currentScreen,
+}: {
+ params: ReportDialogParams
+ currentScreen: 'list' | 'conversation'
+}) {
+ const {data: profile, isError} = useProfileQuery({
+ did: params.message.sender.did,
+ })
const [reportOption, setReportOption] = useState(null)
+ const [done, setDone] = useState(false)
+ const control = Dialog.useDialogContext()
- return reportOption ? (
+ return done ? (
+ profile ? (
+
+ ) : (
+
+
+
+ )
+ ) : reportOption ? (
setReportOption(null)}
+ onComplete={() => {
+ if (isError) {
+ control.close()
+ } else {
+ setDone(true)
+ }
+ }}
/>
) : (
@@ -89,16 +133,17 @@ function SubmitStep({
params,
reportOption,
goBack,
+ onComplete,
}: {
params: ReportDialogParams
reportOption: ReportOption
goBack: () => void
+ onComplete: () => void
}) {
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
const t = useTheme()
const [details, setDetails] = useState('')
- const control = Dialog.useDialogContext()
const agent = useAgent()
const {
@@ -124,11 +169,7 @@ function SubmitStep({
await agent.createModerationReport(report)
}
},
- onSuccess: () => {
- control.close(() => {
- Toast.show(_(msg`Thank you. Your report has been sent.`))
- })
- },
+ onSuccess: onComplete,
})
const copy = useMemo(() => {
@@ -181,11 +222,11 @@ function SubmitStep({
Send report
- {submitting && }
+
+
+
+
+ )
+}
+
+function DoneStep({
+ convoId,
+ currentScreen,
+ profile,
+}: {
+ convoId: string
+ currentScreen: 'list' | 'conversation'
+ profile: AppBskyActorDefs.ProfileViewBasic
+}) {
+ const {_} = useLingui()
+ const navigation = useNavigation()
+ const control = Dialog.useDialogContext()
+ const {gtMobile} = useBreakpoints()
+ const t = useTheme()
+ const [actions, setActions] = useState(['block', 'leave'])
+ const shadow = useProfileShadow(profile)
+ const [queueBlock] = useProfileBlockMutationQueue(shadow)
+
+ const {mutate: leaveConvo} = useLeaveConvo(convoId, {
+ onMutate: () => {
+ if (currentScreen === 'conversation') {
+ navigation.dispatch(
+ StackActions.replace('Messages', isNative ? {animation: 'pop'} : {}),
+ )
+ }
+ },
+ onError: () => {
+ Toast.show(_(msg`Could not leave chat`), 'xmark')
+ },
+ })
+
+ const onPressPrimaryAction = () => {
+ control.close(() => {
+ if (actions.includes('block')) {
+ queueBlock()
+ }
+ if (actions.includes('leave')) {
+ leaveConvo()
+ }
+ })
+ }
+
+ let btnText = _(msg`Done`)
+ if (actions.includes('leave') && actions.includes('block')) {
+ btnText = _(msg`Block and Delete`)
+ } else if (actions.includes('leave')) {
+ btnText = _(msg`Delete Conversation`)
+ } else if (actions.includes('block')) {
+ btnText = _(msg`Block User`)
+ }
+
+ return (
+
+
+
+ Report submitted
+
+
+ Our moderation team has recieved your report.
+
+
+
+
+
+
+
+ Block user
+
+
+
+
+
+ Delete conversation
+
+
+
+
+
+
+
+
diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx
index ac62851123..178e94dd4b 100644
--- a/src/screens/Messages/ChatList.tsx
+++ b/src/screens/Messages/ChatList.tsx
@@ -14,6 +14,7 @@ import {logger} from '#/logger'
import {isNative} from '#/platform/detection'
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 {List} from '#/view/com/util/List'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
@@ -94,12 +95,19 @@ export function MessagesScreen({navigation, route}: Props) {
useRefreshOnFocus(refetch)
+ const leftConvos = useLeftConvos()
+
const conversations = useMemo(() => {
if (data?.pages) {
- return data.pages.flatMap(page => page.convos)
+ return (
+ data.pages
+ .flatMap(page => page.convos)
+ // filter out convos that are actively being left
+ .filter(convo => !leftConvos.includes(convo.id))
+ )
}
return []
- }, [data])
+ }, [data, leftConvos])
const onRefresh = useCallback(async () => {
setIsPTRing(true)
diff --git a/src/state/queries/messages/leave-conversation.ts b/src/state/queries/messages/leave-conversation.ts
index faeb926964..21cd1f18ce 100644
--- a/src/state/queries/messages/leave-conversation.ts
+++ b/src/state/queries/messages/leave-conversation.ts
@@ -1,17 +1,28 @@
import {ChatBskyConvoLeaveConvo, ChatBskyConvoListConvos} from '@atproto/api'
-import {useMutation, useQueryClient} from '@tanstack/react-query'
+import {
+ useMutation,
+ useMutationState,
+ useQueryClient,
+} from '@tanstack/react-query'
import {logger} from '#/logger'
import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const'
import {useAgent} from '#/state/session'
import {RQKEY as CONVO_LIST_KEY} from './list-conversations'
+const RQKEY_ROOT = 'leave-convo'
+export function RQKEY(convoId: string | undefined) {
+ return [RQKEY_ROOT, convoId]
+}
+
export function useLeaveConvo(
convoId: string | undefined,
{
onSuccess,
+ onMutate,
onError,
}: {
+ onMutate?: () => void
onSuccess?: (data: ChatBskyConvoLeaveConvo.OutputSchema) => void
onError?: (error: Error) => void
},
@@ -20,6 +31,7 @@ export function useLeaveConvo(
const agent = useAgent()
return useMutation({
+ mutationKey: RQKEY(convoId),
mutationFn: async () => {
if (!convoId) throw new Error('No convoId provided')
@@ -51,6 +63,7 @@ export function useLeaveConvo(
}
},
)
+ onMutate?.()
return {prevPages}
},
onSuccess: data => {
@@ -77,3 +90,20 @@ export function useLeaveConvo(
},
})
}
+
+/**
+ * Gets currently pending and successful leave convo mutations
+ *
+ * @returns Array of `convoId`
+ */
+export function useLeftConvos() {
+ const pending = useMutationState({
+ filters: {mutationKey: [RQKEY_ROOT], status: 'pending'},
+ select: mutation => mutation.options.mutationKey?.[1] as string | undefined,
+ })
+ const success = useMutationState({
+ filters: {mutationKey: [RQKEY_ROOT], status: 'success'},
+ select: mutation => mutation.options.mutationKey?.[1] as string | undefined,
+ })
+ return [...pending, ...success].filter(id => id !== undefined)
+}