diff --git a/src/components/dms/MessagesListPendingAcceptFooter.tsx b/src/components/dms/MessagesListPendingAcceptFooter.tsx new file mode 100644 index 0000000000..7b1a133a6e --- /dev/null +++ b/src/components/dms/MessagesListPendingAcceptFooter.tsx @@ -0,0 +1,75 @@ +import {View} from 'react-native' +import {AppBskyActorDefs} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import {useDialogControl} from '#/components/Dialog' +import {Divider} from '#/components/Divider' +import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt' +import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt' +import {Text} from '#/components/Typography' + +export function MessagesListPendingAcceptFooter({ + // recipient: initialRecipient, + convoId, + hasMessages, +}: { + recipient: AppBskyActorDefs.ProfileViewBasic + convoId: string + hasMessages: boolean +}) { + const t = useTheme() + const {_} = useLingui() + // const recipient = useProfileShadow(initialRecipient) + + const leaveConvoControl = useDialogControl() + const reportControl = useDialogControl() + + return ( + + + + {/* {isBlocking ? ( + You have blocked this user + ) : ( + This user has blocked you + )} */} + + + + + + + + + + + + ) +} diff --git a/src/screens/Messages/Conversation.tsx b/src/screens/Messages/Conversation.tsx index b8b0bfe0d3..0abbc8c2b9 100644 --- a/src/screens/Messages/Conversation.tsx +++ b/src/screens/Messages/Conversation.tsx @@ -23,9 +23,11 @@ import {useDialogControl} from '#/components/Dialog' import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog' import {MessagesListBlockedFooter} from '#/components/dms/MessagesListBlockedFooter' import {MessagesListHeader} from '#/components/dms/MessagesListHeader' +import {MessagesListPendingAcceptFooter} from '#/components/dms/MessagesListPendingAcceptFooter' import {Error} from '#/components/Error' import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' +import {ChatDisabled} from './components/ChatDisabled' type Props = NativeStackScreenProps< CommonNavigatorParams, @@ -193,14 +195,23 @@ function InnerReady({ 0} - blockInfo={blockInfo} - /> + convoState.status === ConvoStatus.Disabled ? ( + + ) : moderation?.blocked ? ( + 0} + blockInfo={blockInfo} + /> + ) : convoState.pendingAcceptance ? ( + 0} + /> + ) : null } /> )} diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx index 071ce1cd71..27013d0d9c 100644 --- a/src/screens/Messages/components/MessagesList.tsx +++ b/src/screens/Messages/components/MessagesList.tsx @@ -31,7 +31,6 @@ import { EmojiPickerState, } from '#/view/com/composer/text-input/web/EmojiPicker.web' import {List, ListMethods} from '#/view/com/util/List' -import {ChatDisabled} from '#/screens/Messages/components/ChatDisabled' import {MessageInput} from '#/screens/Messages/components/MessageInput' import {MessageListError} from '#/screens/Messages/components/MessageListError' import {ChatEmptyPill} from '#/components/dms/ChatEmptyPill' @@ -78,13 +77,11 @@ function onScrollToIndexFailed() { export function MessagesList({ hasScrolled, setHasScrolled, - blocked, footer, }: { hasScrolled: boolean setHasScrolled: React.Dispatch> - blocked?: boolean - footer?: React.ReactNode + footer: React.ReactNode | null }) { const convoState = useConvoActive() const agent = useAgent() @@ -427,11 +424,7 @@ export function MessagesList({ /> - {convoState.status === ConvoStatus.Disabled ? ( - - ) : blocked ? ( - footer - ) : ( + {footer || ( <> {isConvoActive(convoState) && !convoState.isFetchingHistory && diff --git a/src/state/messages/convo/agent.ts b/src/state/messages/convo/agent.ts index 53d77046a2..32a9c41751 100644 --- a/src/state/messages/convo/agent.ts +++ b/src/state/messages/convo/agent.ts @@ -78,10 +78,13 @@ export class Convo { private emitter = new EventEmitter<{event: [ConvoEvent]}>() + private onMarkAsRead: () => void + convoId: string convo: ChatBskyConvoDefs.ConvoView | undefined sender: AppBskyActorDefs.ProfileViewBasic | undefined recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined = undefined + pendingAcceptance: boolean | undefined snapshot: ConvoState | undefined constructor(params: ConvoParams) { @@ -89,7 +92,8 @@ export class Convo { this.convoId = params.convoId this.agent = params.agent this.events = params.events - this.senderUserDid = params.agent.session?.did! + this.senderUserDid = params.agent.session!.did + this.onMarkAsRead = params.onMarkAsRead this.subscribe = this.subscribe.bind(this) this.getSnapshot = this.getSnapshot.bind(this) @@ -139,6 +143,7 @@ export class Convo { deleteMessage: undefined, sendMessage: undefined, fetchMessageHistory: undefined, + pendingAcceptance: undefined, } } case ConvoStatus.Disabled: @@ -156,6 +161,7 @@ export class Convo { deleteMessage: this.deleteMessage, sendMessage: this.sendMessage, fetchMessageHistory: this.fetchMessageHistory, + pendingAcceptance: this.pendingAcceptance, } } case ConvoStatus.Error: { @@ -170,6 +176,7 @@ export class Convo { deleteMessage: undefined, sendMessage: undefined, fetchMessageHistory: undefined, + pendingAcceptance: undefined, } } default: { @@ -184,6 +191,7 @@ export class Convo { deleteMessage: undefined, sendMessage: undefined, fetchMessageHistory: undefined, + pendingAcceptance: undefined, } } } @@ -246,11 +254,13 @@ export class Convo { case ConvoDispatchEvent.Resume: { this.refreshConvo() this.requestPollInterval(ACTIVE_POLL_INTERVAL) + this.markAsRead() break } case ConvoDispatchEvent.Background: { this.status = ConvoStatus.Backgrounded this.requestPollInterval(BACKGROUND_POLL_INTERVAL) + this.markAsRead() break } case ConvoDispatchEvent.Suspend: { @@ -513,6 +523,7 @@ export class Convo { convo: ChatBskyConvoDefs.ConvoView sender: AppBskyActorDefs.ProfileViewBasic | undefined recipients: AppBskyActorDefs.ProfileViewBasic[] + pendingAcceptance: boolean }> | undefined async fetchConvo() { @@ -522,10 +533,11 @@ export class Convo { convo: ChatBskyConvoDefs.ConvoView sender: AppBskyActorDefs.ProfileViewBasic | undefined recipients: AppBskyActorDefs.ProfileViewBasic[] + pendingAcceptance: boolean }>(async (resolve, reject) => { try { const response = await networkRetry(2, () => { - return this.agent.api.chat.bsky.convo.getConvo( + return this.agent.chat.bsky.convo.getConvo( { convoId: this.convoId, }, @@ -539,6 +551,7 @@ export class Convo { convo, sender: convo.members.find(m => m.did === this.senderUserDid), recipients: convo.members.filter(m => m.did !== this.senderUserDid), + pendingAcceptance: !convo.opened, }) } catch (e) { reject(e) @@ -552,11 +565,13 @@ export class Convo { async refreshConvo() { try { - const {convo, sender, recipients} = await this.fetchConvo() + const {convo, sender, recipients, pendingAcceptance} = + await this.fetchConvo() // throw new Error('UNCOMMENT TO TEST REFRESH FAILURE') this.convo = convo || this.convo this.sender = sender || this.sender this.recipients = recipients || this.recipients + this.pendingAcceptance = pendingAcceptance ?? this.pendingAcceptance } catch (e: any) { logger.error(e, {context: `Convo: failed to refresh convo`}) } @@ -592,7 +607,7 @@ export class Convo { const nextCursor = this.oldestRev // for TS const response = await networkRetry(2, () => { - return this.agent.api.chat.bsky.convo.getMessages( + return this.agent.chat.bsky.convo.getMessages( { cursor: nextCursor, convoId: this.convoId, @@ -793,7 +808,7 @@ export class Convo { const {id, message} = pendingMessage - const response = await this.agent.api.chat.bsky.convo.sendMessage( + const response = await this.agent.chat.bsky.convo.sendMessage( { convoId: this.convoId, message, @@ -888,7 +903,7 @@ export class Convo { ) try { - const {data} = await this.agent.api.chat.bsky.convo.sendMessageBatch( + const {data} = await this.agent.chat.bsky.convo.sendMessageBatch( { items: messageArray.map(({message}) => ({ convoId: this.convoId, @@ -935,7 +950,7 @@ export class Convo { try { await networkRetry(2, () => { - return this.agent.api.chat.bsky.convo.deleteMessageForSelf( + return this.agent.chat.bsky.convo.deleteMessageForSelf( { convoId: this.convoId, messageId, @@ -1104,4 +1119,21 @@ export class Convo { return item }) } + + async acceptChat() { + await this.agent.chat.bsky.convo.updateRead({ + convoId: this.convoId, + }) + this.pendingAcceptance = false + this.onMarkAsRead() + } + + markAsRead() { + if (!this.pendingAcceptance) { + this.agent.chat.bsky.convo.updateRead({ + convoId: this.convoId, + }) + this.onMarkAsRead() + } + } } diff --git a/src/state/messages/convo/index.tsx b/src/state/messages/convo/index.tsx index 10ec2a348a..52eea47d74 100644 --- a/src/state/messages/convo/index.tsx +++ b/src/state/messages/convo/index.tsx @@ -3,6 +3,7 @@ import {useFocusEffect} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' import {useAppState} from '#/lib/hooks/useAppState' +import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {Convo} from '#/state/messages/convo/agent' import { ConvoParams, @@ -14,8 +15,10 @@ import { } from '#/state/messages/convo/types' import {isConvoActive} from '#/state/messages/convo/util' import {useMessagesEventBus} from '#/state/messages/events' -import {useMarkAsReadMutation} from '#/state/queries/messages/conversation' -import {RQKEY as ListConvosQueryKey} from '#/state/queries/messages/list-conversations' +import { + RQKEY as ListConvosQueryKey, + useOnMarkAsRead, +} from '#/state/queries/messages/list-conversations' import {RQKEY as createProfileQueryKey} from '#/state/queries/profile' import {useAgent} from '#/state/session' @@ -60,16 +63,20 @@ export function ConvoProvider({ const queryClient = useQueryClient() const agent = useAgent() const events = useMessagesEventBus() + const onMarkAsRead = useOnMarkAsRead() + const stableMAR = useNonReactiveCallback(() => { + onMarkAsRead(convoId) + }) const [convo] = useState( () => new Convo({ convoId, agent, events, + onMarkAsRead: stableMAR, }), ) const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot) - const {mutate: markAsRead} = useMarkAsReadMutation() const appState = useAppState() const isActive = appState === 'active' @@ -77,14 +84,12 @@ export function ConvoProvider({ React.useCallback(() => { if (isActive) { convo.resume() - markAsRead({convoId}) return () => { convo.background() - markAsRead({convoId}) } } - }, [isActive, convo, convoId, markAsRead]), + }, [isActive, convo]), ) React.useEffect(() => { diff --git a/src/state/messages/convo/types.ts b/src/state/messages/convo/types.ts index 21772262ea..0ab095c618 100644 --- a/src/state/messages/convo/types.ts +++ b/src/state/messages/convo/types.ts @@ -11,6 +11,7 @@ export type ConvoParams = { convoId: string agent: BskyAgent events: MessagesEventBus + onMarkAsRead: () => void } export enum ConvoStatus { @@ -150,6 +151,7 @@ export type ConvoStateUninitialized = { deleteMessage: undefined sendMessage: undefined fetchMessageHistory: undefined + pendingAcceptance: undefined } export type ConvoStateInitializing = { status: ConvoStatus.Initializing @@ -162,6 +164,7 @@ export type ConvoStateInitializing = { deleteMessage: undefined sendMessage: undefined fetchMessageHistory: undefined + pendingAcceptance: undefined } export type ConvoStateReady = { status: ConvoStatus.Ready @@ -174,6 +177,7 @@ export type ConvoStateReady = { deleteMessage: DeleteMessage sendMessage: SendMessage fetchMessageHistory: FetchMessageHistory + pendingAcceptance: boolean | undefined } export type ConvoStateBackgrounded = { status: ConvoStatus.Backgrounded @@ -186,6 +190,7 @@ export type ConvoStateBackgrounded = { deleteMessage: DeleteMessage sendMessage: SendMessage fetchMessageHistory: FetchMessageHistory + pendingAcceptance: boolean | undefined } export type ConvoStateSuspended = { status: ConvoStatus.Suspended @@ -198,6 +203,7 @@ export type ConvoStateSuspended = { deleteMessage: DeleteMessage sendMessage: SendMessage fetchMessageHistory: FetchMessageHistory + pendingAcceptance: boolean | undefined } export type ConvoStateError = { status: ConvoStatus.Error @@ -210,6 +216,7 @@ export type ConvoStateError = { deleteMessage: undefined sendMessage: undefined fetchMessageHistory: undefined + pendingAcceptance: undefined } export type ConvoStateDisabled = { status: ConvoStatus.Disabled @@ -222,6 +229,7 @@ export type ConvoStateDisabled = { deleteMessage: DeleteMessage sendMessage: SendMessage fetchMessageHistory: FetchMessageHistory + pendingAcceptance: boolean | undefined } export type ConvoState = | ConvoStateUninitialized