From 10b9de4e94b0bd084291c663bc28377af93f2f14 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 2 May 2024 15:39:25 -0500 Subject: [PATCH] Initial working external store --- .../Messages/Conversation/MessagesList.tsx | 2 +- src/screens/Messages/Conversation/index.tsx | 16 ++-- src/state/messages/convo.ts | 79 ++++++++++++++----- src/state/messages/index.tsx | 29 ++----- 4 files changed, 72 insertions(+), 54 deletions(-) diff --git a/src/screens/Messages/Conversation/MessagesList.tsx b/src/screens/Messages/Conversation/MessagesList.tsx index 55e1e43c01..f6f786ee69 100644 --- a/src/screens/Messages/Conversation/MessagesList.tsx +++ b/src/screens/Messages/Conversation/MessagesList.tsx @@ -106,7 +106,7 @@ export function MessagesList() { text, }) }, - [chat.service], + [chat], ) const onScroll = React.useCallback( diff --git a/src/screens/Messages/Conversation/index.tsx b/src/screens/Messages/Conversation/index.tsx index 4e692317f1..41075b5d50 100644 --- a/src/screens/Messages/Conversation/index.tsx +++ b/src/screens/Messages/Conversation/index.tsx @@ -1,7 +1,6 @@ import React, {useCallback} from 'react' import {TouchableOpacity, View} from 'react-native' import {AppBskyActorDefs} from '@atproto/api' -import {ChatBskyConvoDefs} from '@atproto-labs/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -78,7 +77,7 @@ let Header = ({ const {_} = useLingui() const {gtTablet} = useBreakpoints() const navigation = useNavigation() - const {service} = useChat() + const {state} = useChat() const onPressBack = useCallback(() => { if (isWeb) { @@ -88,12 +87,9 @@ let Header = ({ } }, [navigation]) - const onUpdateConvo = useCallback( - (convo: ChatBskyConvoDefs.ConvoView) => { - service.convo = convo - }, - [service], - ) + const onUpdateConvo = useCallback(() => { + // TODO eric update muted state + }, []) return ( {profile.displayName} - {service.convo ? ( + {state.status === ConvoStatus.Ready && state.convo ? ( void + sendMessage: ( + message: ChatBskyConvoSendMessage.InputSchema['message'], + ) => void + fetchMessageHistory: () => void + } +} + export function isConvoItemMessage( item: ConvoItem, ): item is ConvoItem & {type: 'message'} { @@ -116,10 +126,40 @@ export class Convo { this.convoId = params.convoId this.agent = params.agent this.__tempFromUserDid = params.__tempFromUserDid + + this.subscribe = this.subscribe.bind(this) + this.getSnapshot = this.getSnapshot.bind(this) + } + + private subscribers: (() => void)[] = [] + + subscribe(subscriber: () => void) { + console.log('SUBSCRIBE') + this.subscribers.push(subscriber) + this.initialize() + return () => { + console.log('UN-SUBSCRIBE') + this.subscribers = this.subscribers.filter(s => s !== subscriber) + } + } + + snapshot: ConvoInterface = { + state: { + status: ConvoStatus.Uninitialized, + }, + service: { + deleteMessage: this.deleteMessage.bind(this), + sendMessage: this.sendMessage.bind(this), + fetchMessageHistory: this.fetchMessageHistory.bind(this), + }, + } + + getSnapshot(): ConvoInterface { + return this.snapshot } async initialize() { - if (this.status !== 'uninitialized') return + if (this.status !== ConvoStatus.Uninitialized) return this.status = ConvoStatus.Initializing try { @@ -545,51 +585,50 @@ export class Convo { this.commit() } - get state(): ConvoState { + private commit() { switch (this.status) { case ConvoStatus.Initializing: { - return { + this.snapshot.state = { status: ConvoStatus.Initializing, } + break } case ConvoStatus.Ready: { - return { + this.snapshot.state = { status: ConvoStatus.Ready, items: this.items, convo: this.convo!, isFetchingHistory: this.isFetchingHistory, } + break } case ConvoStatus.Error: { - return { + this.snapshot.state = { status: ConvoStatus.Error, error: this.error, } + break } case ConvoStatus.Destroyed: { - return { + this.snapshot.state = { status: ConvoStatus.Destroyed, } + break } default: { - return { + this.snapshot.state = { status: ConvoStatus.Uninitialized, } + break } } + + this.snapshot = Object.assign({}, this.snapshot) + + this.alertSubscribers() } - private _emitter = new EventEmitter() - - private commit() { - this._emitter.emit('update') - } - - on(event: 'update', cb: () => void) { - this._emitter.on(event, cb) - } - - off(event: 'update', cb: () => void) { - this._emitter.off(event, cb) + private alertSubscribers() { + this.subscribers.forEach(subscriber => subscriber()) } } diff --git a/src/state/messages/index.tsx b/src/state/messages/index.tsx index cdc5a4db26..12f25484a4 100644 --- a/src/state/messages/index.tsx +++ b/src/state/messages/index.tsx @@ -1,14 +1,11 @@ -import React, {useContext, useEffect, useMemo, useState} from 'react' +import React, {useContext, useState, useSyncExternalStore} from 'react' import {BskyAgent} from '@atproto-labs/api' -import {Convo, ConvoParams} from '#/state/messages/convo' +import {Convo, ConvoInterface, ConvoParams} from '#/state/messages/convo' import {useAgent} from '#/state/session' import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage' -const ChatContext = React.createContext<{ - service: Convo - state: Convo['state'] -} | null>(null) +const ChatContext = React.createContext(null) export function useChat() { const ctx = useContext(ChatContext) @@ -24,7 +21,7 @@ export function ChatProvider({ }: Pick & {children: React.ReactNode}) { const {serviceUrl} = useDmServiceUrlStorage() const {getAgent} = useAgent() - const [service] = useState( + const [convo] = useState( () => new Convo({ convoId, @@ -34,21 +31,7 @@ export function ChatProvider({ __tempFromUserDid: getAgent().session?.did!, }), ) - const [state, setState] = useState(service.state) + const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot) - useEffect(() => { - service.initialize() - }, [service]) - - useEffect(() => { - const update = () => setState(service.state) - service.on('update', update) - return () => { - service.destroy() - } - }, [service]) - - const value = useMemo(() => ({service, state}), [service, state]) - - return {children} + return {children} }