diff options
Diffstat (limited to 'src/state')
-rw-r--r-- | src/state/messages/convo/agent.ts (renamed from src/state/messages/convo.ts) | 183 | ||||
-rw-r--r-- | src/state/messages/convo/index.tsx | 75 | ||||
-rw-r--r-- | src/state/messages/convo/types.ts | 178 | ||||
-rw-r--r-- | src/state/messages/index.tsx | 74 |
4 files changed, 264 insertions, 246 deletions
diff --git a/src/state/messages/convo.ts b/src/state/messages/convo/agent.ts index de21ef396..38a3f5e62 100644 --- a/src/state/messages/convo.ts +++ b/src/state/messages/convo/agent.ts @@ -9,178 +9,16 @@ import {nanoid} from 'nanoid/non-secure' import {logger} from '#/logger' import {isNative} from '#/platform/detection' - -export type ConvoParams = { - convoId: string - agent: BskyAgent - __tempFromUserDid: string -} - -export enum ConvoStatus { - Uninitialized = 'uninitialized', - Initializing = 'initializing', - Ready = 'ready', - Error = 'error', - Backgrounded = 'backgrounded', - Suspended = 'suspended', -} - -export enum ConvoItemError { - HistoryFailed = 'historyFailed', - PollFailed = 'pollFailed', - Network = 'network', -} - -export enum ConvoErrorCode { - InitFailed = 'initFailed', -} - -export type ConvoError = { - code: ConvoErrorCode - exception?: Error - retry: () => void -} - -export enum ConvoDispatchEvent { - Init = 'init', - Ready = 'ready', - Resume = 'resume', - Background = 'background', - Suspend = 'suspend', - Error = 'error', -} - -export type ConvoDispatch = - | { - event: ConvoDispatchEvent.Init - } - | { - event: ConvoDispatchEvent.Ready - } - | { - event: ConvoDispatchEvent.Resume - } - | { - event: ConvoDispatchEvent.Background - } - | { - event: ConvoDispatchEvent.Suspend - } - | { - event: ConvoDispatchEvent.Error - payload: ConvoError - } - -export type ConvoItem = - | { - type: 'message' | 'pending-message' - key: string - message: ChatBskyConvoDefs.MessageView - nextMessage: - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | null - } - | { - type: 'deleted-message' - key: string - message: ChatBskyConvoDefs.DeletedMessageView - nextMessage: - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | null - } - | { - type: 'pending-retry' - key: string - retry: () => void - } - | { - type: 'error-recoverable' - key: string - code: ConvoItemError - retry: () => void - } - -export type ConvoState = - | { - status: ConvoStatus.Uninitialized - items: [] - convo: undefined - error: undefined - sender: undefined - recipients: undefined - isFetchingHistory: false - deleteMessage: undefined - sendMessage: undefined - fetchMessageHistory: undefined - } - | { - status: ConvoStatus.Initializing - items: [] - convo: undefined - error: undefined - sender: undefined - recipients: undefined - isFetchingHistory: boolean - deleteMessage: undefined - sendMessage: undefined - fetchMessageHistory: undefined - } - | { - status: ConvoStatus.Ready - items: ConvoItem[] - convo: ChatBskyConvoDefs.ConvoView - error: undefined - sender: AppBskyActorDefs.ProfileViewBasic - recipients: AppBskyActorDefs.ProfileViewBasic[] - isFetchingHistory: boolean - deleteMessage: (messageId: string) => Promise<void> - sendMessage: ( - message: ChatBskyConvoSendMessage.InputSchema['message'], - ) => void - fetchMessageHistory: () => void - } - | { - status: ConvoStatus.Suspended - items: ConvoItem[] - convo: ChatBskyConvoDefs.ConvoView - error: undefined - sender: AppBskyActorDefs.ProfileViewBasic - recipients: AppBskyActorDefs.ProfileViewBasic[] - isFetchingHistory: boolean - deleteMessage: (messageId: string) => Promise<void> - sendMessage: ( - message: ChatBskyConvoSendMessage.InputSchema['message'], - ) => Promise<void> - fetchMessageHistory: () => Promise<void> - } - | { - status: ConvoStatus.Backgrounded - items: ConvoItem[] - convo: ChatBskyConvoDefs.ConvoView - error: undefined - sender: AppBskyActorDefs.ProfileViewBasic - recipients: AppBskyActorDefs.ProfileViewBasic[] - isFetchingHistory: boolean - deleteMessage: (messageId: string) => Promise<void> - sendMessage: ( - message: ChatBskyConvoSendMessage.InputSchema['message'], - ) => Promise<void> - fetchMessageHistory: () => Promise<void> - } - | { - status: ConvoStatus.Error - items: [] - convo: undefined - error: any - sender: undefined - recipients: undefined - isFetchingHistory: false - deleteMessage: undefined - sendMessage: undefined - fetchMessageHistory: undefined - } +import { + ConvoDispatch, + ConvoDispatchEvent, + ConvoErrorCode, + ConvoItem, + ConvoItemError, + ConvoParams, + ConvoState, + ConvoStatus, +} from '#/state/messages/convo/types' const ACTIVE_POLL_INTERVAL = 1e3 const BACKGROUND_POLL_INTERVAL = 10e3 @@ -235,7 +73,6 @@ export class Convo { private headerItems: Map<string, ConvoItem> = new Map() private isProcessingPendingMessages = false - private pendingPoll: Promise<void> | undefined private nextPoll: NodeJS.Timeout | undefined convoId: string diff --git a/src/state/messages/convo/index.tsx b/src/state/messages/convo/index.tsx new file mode 100644 index 000000000..c4fe71d30 --- /dev/null +++ b/src/state/messages/convo/index.tsx @@ -0,0 +1,75 @@ +import React, {useContext, useState, useSyncExternalStore} from 'react' +import {AppState} from 'react-native' +import {BskyAgent} from '@atproto-labs/api' +import {useFocusEffect, useIsFocused} from '@react-navigation/native' + +import {Convo} from '#/state/messages/convo/agent' +import {ConvoParams, ConvoState} from '#/state/messages/convo/types' +import {useMarkAsReadMutation} from '#/state/queries/messages/conversation' +import {useAgent} from '#/state/session' +import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage' + +const ChatContext = React.createContext<ConvoState | null>(null) + +export function useConvo() { + const ctx = useContext(ChatContext) + if (!ctx) { + throw new Error('useConvo must be used within a ConvoProvider') + } + return ctx +} + +export function ConvoProvider({ + children, + convoId, +}: Pick<ConvoParams, 'convoId'> & {children: React.ReactNode}) { + const isScreenFocused = useIsFocused() + const {serviceUrl} = useDmServiceUrlStorage() + const {getAgent} = useAgent() + const [convo] = useState( + () => + new Convo({ + convoId, + agent: new BskyAgent({ + service: serviceUrl, + }), + __tempFromUserDid: getAgent().session?.did!, + }), + ) + const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot) + const {mutate: markAsRead} = useMarkAsReadMutation() + + useFocusEffect( + React.useCallback(() => { + convo.resume() + markAsRead({convoId}) + + return () => { + convo.background() + markAsRead({convoId}) + } + }, [convo, convoId, markAsRead]), + ) + + React.useEffect(() => { + const handleAppStateChange = (nextAppState: string) => { + if (isScreenFocused) { + if (nextAppState === 'active') { + convo.resume() + } else { + convo.background() + } + + markAsRead({convoId}) + } + } + + const sub = AppState.addEventListener('change', handleAppStateChange) + + return () => { + sub.remove() + } + }, [convoId, convo, isScreenFocused, markAsRead]) + + return <ChatContext.Provider value={service}>{children}</ChatContext.Provider> +} diff --git a/src/state/messages/convo/types.ts b/src/state/messages/convo/types.ts new file mode 100644 index 000000000..cfbde6d7e --- /dev/null +++ b/src/state/messages/convo/types.ts @@ -0,0 +1,178 @@ +import {AppBskyActorDefs} from '@atproto/api' +import { + BskyAgent, + ChatBskyConvoDefs, + ChatBskyConvoSendMessage, +} from '@atproto-labs/api' + +export type ConvoParams = { + convoId: string + agent: BskyAgent + __tempFromUserDid: string +} + +export enum ConvoStatus { + Uninitialized = 'uninitialized', + Initializing = 'initializing', + Ready = 'ready', + Error = 'error', + Backgrounded = 'backgrounded', + Suspended = 'suspended', +} + +export enum ConvoItemError { + HistoryFailed = 'historyFailed', + PollFailed = 'pollFailed', + Network = 'network', +} + +export enum ConvoErrorCode { + InitFailed = 'initFailed', +} + +export type ConvoError = { + code: ConvoErrorCode + exception?: Error + retry: () => void +} + +export enum ConvoDispatchEvent { + Init = 'init', + Ready = 'ready', + Resume = 'resume', + Background = 'background', + Suspend = 'suspend', + Error = 'error', +} + +export type ConvoDispatch = + | { + event: ConvoDispatchEvent.Init + } + | { + event: ConvoDispatchEvent.Ready + } + | { + event: ConvoDispatchEvent.Resume + } + | { + event: ConvoDispatchEvent.Background + } + | { + event: ConvoDispatchEvent.Suspend + } + | { + event: ConvoDispatchEvent.Error + payload: ConvoError + } + +export type ConvoItem = + | { + type: 'message' | 'pending-message' + key: string + message: ChatBskyConvoDefs.MessageView + nextMessage: + | ChatBskyConvoDefs.MessageView + | ChatBskyConvoDefs.DeletedMessageView + | null + } + | { + type: 'deleted-message' + key: string + message: ChatBskyConvoDefs.DeletedMessageView + nextMessage: + | ChatBskyConvoDefs.MessageView + | ChatBskyConvoDefs.DeletedMessageView + | null + } + | { + type: 'pending-retry' + key: string + retry: () => void + } + | { + type: 'error-recoverable' + key: string + code: ConvoItemError + retry: () => void + } + +export type ConvoState = + | { + status: ConvoStatus.Uninitialized + items: [] + convo: undefined + error: undefined + sender: undefined + recipients: undefined + isFetchingHistory: false + deleteMessage: undefined + sendMessage: undefined + fetchMessageHistory: undefined + } + | { + status: ConvoStatus.Initializing + items: [] + convo: undefined + error: undefined + sender: undefined + recipients: undefined + isFetchingHistory: boolean + deleteMessage: undefined + sendMessage: undefined + fetchMessageHistory: undefined + } + | { + status: ConvoStatus.Ready + items: ConvoItem[] + convo: ChatBskyConvoDefs.ConvoView + error: undefined + sender: AppBskyActorDefs.ProfileViewBasic + recipients: AppBskyActorDefs.ProfileViewBasic[] + isFetchingHistory: boolean + deleteMessage: (messageId: string) => Promise<void> + sendMessage: ( + message: ChatBskyConvoSendMessage.InputSchema['message'], + ) => void + fetchMessageHistory: () => void + } + | { + status: ConvoStatus.Suspended + items: ConvoItem[] + convo: ChatBskyConvoDefs.ConvoView + error: undefined + sender: AppBskyActorDefs.ProfileViewBasic + recipients: AppBskyActorDefs.ProfileViewBasic[] + isFetchingHistory: boolean + deleteMessage: (messageId: string) => Promise<void> + sendMessage: ( + message: ChatBskyConvoSendMessage.InputSchema['message'], + ) => Promise<void> + fetchMessageHistory: () => Promise<void> + } + | { + status: ConvoStatus.Backgrounded + items: ConvoItem[] + convo: ChatBskyConvoDefs.ConvoView + error: undefined + sender: AppBskyActorDefs.ProfileViewBasic + recipients: AppBskyActorDefs.ProfileViewBasic[] + isFetchingHistory: boolean + deleteMessage: (messageId: string) => Promise<void> + sendMessage: ( + message: ChatBskyConvoSendMessage.InputSchema['message'], + ) => Promise<void> + fetchMessageHistory: () => Promise<void> + } + | { + status: ConvoStatus.Error + items: [] + convo: undefined + error: any + sender: undefined + recipients: undefined + isFetchingHistory: false + deleteMessage: undefined + sendMessage: undefined + fetchMessageHistory: undefined + } diff --git a/src/state/messages/index.tsx b/src/state/messages/index.tsx index 60538615a..205d17e8c 100644 --- a/src/state/messages/index.tsx +++ b/src/state/messages/index.tsx @@ -1,79 +1,7 @@ -import React, {useContext, useState, useSyncExternalStore} from 'react' -import {AppState} from 'react-native' -import {BskyAgent} from '@atproto-labs/api' -import {useFocusEffect, useIsFocused} from '@react-navigation/native' +import React from 'react' -import {Convo, ConvoParams, ConvoState} from '#/state/messages/convo' import {CurrentConvoIdProvider} from '#/state/messages/current-convo-id' import {MessagesEventBusProvider} from '#/state/messages/events' -import {useMarkAsReadMutation} from '#/state/queries/messages/conversation' -import {useAgent} from '#/state/session' -import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage' - -const ChatContext = React.createContext<ConvoState | null>(null) - -export function useChat() { - const ctx = useContext(ChatContext) - if (!ctx) { - throw new Error('useChat must be used within a ChatProvider') - } - return ctx -} - -export function ChatProvider({ - children, - convoId, -}: Pick<ConvoParams, 'convoId'> & {children: React.ReactNode}) { - const isScreenFocused = useIsFocused() - const {serviceUrl} = useDmServiceUrlStorage() - const {getAgent} = useAgent() - const [convo] = useState( - () => - new Convo({ - convoId, - agent: new BskyAgent({ - service: serviceUrl, - }), - __tempFromUserDid: getAgent().session?.did!, - }), - ) - const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot) - const {mutate: markAsRead} = useMarkAsReadMutation() - - useFocusEffect( - React.useCallback(() => { - convo.resume() - markAsRead({convoId}) - - return () => { - convo.background() - markAsRead({convoId}) - } - }, [convo, convoId, markAsRead]), - ) - - React.useEffect(() => { - const handleAppStateChange = (nextAppState: string) => { - if (isScreenFocused) { - if (nextAppState === 'active') { - convo.resume() - } else { - convo.background() - } - - markAsRead({convoId}) - } - } - - const sub = AppState.addEventListener('change', handleAppStateChange) - - return () => { - sub.remove() - } - }, [convoId, convo, isScreenFocused, markAsRead]) - - return <ChatContext.Provider value={service}>{children}</ChatContext.Provider> -} export function MessagesProvider({children}: {children: React.ReactNode}) { return ( |