Initial working external store

This commit is contained in:
Eric Bailey
2024-05-02 15:39:25 -05:00
parent a20fe4c9a0
commit 10b9de4e94
4 changed files with 72 additions and 54 deletions
@@ -106,7 +106,7 @@ export function MessagesList() {
text, text,
}) })
}, },
[chat.service], [chat],
) )
const onScroll = React.useCallback( const onScroll = React.useCallback(
+6 -10
View File
@@ -1,7 +1,6 @@
import React, {useCallback} from 'react' import React, {useCallback} from 'react'
import {TouchableOpacity, View} from 'react-native' import {TouchableOpacity, View} from 'react-native'
import {AppBskyActorDefs} from '@atproto/api' import {AppBskyActorDefs} from '@atproto/api'
import {ChatBskyConvoDefs} from '@atproto-labs/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -78,7 +77,7 @@ let Header = ({
const {_} = useLingui() const {_} = useLingui()
const {gtTablet} = useBreakpoints() const {gtTablet} = useBreakpoints()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {service} = useChat() const {state} = useChat()
const onPressBack = useCallback(() => { const onPressBack = useCallback(() => {
if (isWeb) { if (isWeb) {
@@ -88,12 +87,9 @@ let Header = ({
} }
}, [navigation]) }, [navigation])
const onUpdateConvo = useCallback( const onUpdateConvo = useCallback(() => {
(convo: ChatBskyConvoDefs.ConvoView) => { // TODO eric update muted state
service.convo = convo }, [])
},
[service],
)
return ( return (
<View <View
@@ -133,9 +129,9 @@ let Header = ({
<PreviewableUserAvatar size={32} profile={profile} /> <PreviewableUserAvatar size={32} profile={profile} />
<Text style={[a.text_lg, a.font_bold]}>{profile.displayName}</Text> <Text style={[a.text_lg, a.font_bold]}>{profile.displayName}</Text>
</View> </View>
{service.convo ? ( {state.status === ConvoStatus.Ready && state.convo ? (
<ConvoMenu <ConvoMenu
convo={service.convo} convo={state.convo}
profile={profile} profile={profile}
onUpdateConvo={onUpdateConvo} onUpdateConvo={onUpdateConvo}
currentScreen="conversation" currentScreen="conversation"
+59 -20
View File
@@ -4,7 +4,6 @@ import {
ChatBskyConvoDefs, ChatBskyConvoDefs,
ChatBskyConvoSendMessage, ChatBskyConvoSendMessage,
} from '@atproto-labs/api' } from '@atproto-labs/api'
import {EventEmitter} from 'eventemitter3'
import {nanoid} from 'nanoid/non-secure' import {nanoid} from 'nanoid/non-secure'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
@@ -69,6 +68,17 @@ export type ConvoState =
status: ConvoStatus.Destroyed status: ConvoStatus.Destroyed
} }
export type ConvoInterface = {
state: ConvoState
service: {
deleteMessage: (messageId: string) => void
sendMessage: (
message: ChatBskyConvoSendMessage.InputSchema['message'],
) => void
fetchMessageHistory: () => void
}
}
export function isConvoItemMessage( export function isConvoItemMessage(
item: ConvoItem, item: ConvoItem,
): item is ConvoItem & {type: 'message'} { ): item is ConvoItem & {type: 'message'} {
@@ -116,10 +126,40 @@ export class Convo {
this.convoId = params.convoId this.convoId = params.convoId
this.agent = params.agent this.agent = params.agent
this.__tempFromUserDid = params.__tempFromUserDid 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() { async initialize() {
if (this.status !== 'uninitialized') return if (this.status !== ConvoStatus.Uninitialized) return
this.status = ConvoStatus.Initializing this.status = ConvoStatus.Initializing
try { try {
@@ -545,51 +585,50 @@ export class Convo {
this.commit() this.commit()
} }
get state(): ConvoState { private commit() {
switch (this.status) { switch (this.status) {
case ConvoStatus.Initializing: { case ConvoStatus.Initializing: {
return { this.snapshot.state = {
status: ConvoStatus.Initializing, status: ConvoStatus.Initializing,
} }
break
} }
case ConvoStatus.Ready: { case ConvoStatus.Ready: {
return { this.snapshot.state = {
status: ConvoStatus.Ready, status: ConvoStatus.Ready,
items: this.items, items: this.items,
convo: this.convo!, convo: this.convo!,
isFetchingHistory: this.isFetchingHistory, isFetchingHistory: this.isFetchingHistory,
} }
break
} }
case ConvoStatus.Error: { case ConvoStatus.Error: {
return { this.snapshot.state = {
status: ConvoStatus.Error, status: ConvoStatus.Error,
error: this.error, error: this.error,
} }
break
} }
case ConvoStatus.Destroyed: { case ConvoStatus.Destroyed: {
return { this.snapshot.state = {
status: ConvoStatus.Destroyed, status: ConvoStatus.Destroyed,
} }
break
} }
default: { default: {
return { this.snapshot.state = {
status: ConvoStatus.Uninitialized, status: ConvoStatus.Uninitialized,
} }
break
} }
} }
this.snapshot = Object.assign({}, this.snapshot)
this.alertSubscribers()
} }
private _emitter = new EventEmitter() private alertSubscribers() {
this.subscribers.forEach(subscriber => subscriber())
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)
} }
} }
+6 -23
View File
@@ -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 {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 {useAgent} from '#/state/session'
import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage' import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage'
const ChatContext = React.createContext<{ const ChatContext = React.createContext<ConvoInterface | null>(null)
service: Convo
state: Convo['state']
} | null>(null)
export function useChat() { export function useChat() {
const ctx = useContext(ChatContext) const ctx = useContext(ChatContext)
@@ -24,7 +21,7 @@ export function ChatProvider({
}: Pick<ConvoParams, 'convoId'> & {children: React.ReactNode}) { }: Pick<ConvoParams, 'convoId'> & {children: React.ReactNode}) {
const {serviceUrl} = useDmServiceUrlStorage() const {serviceUrl} = useDmServiceUrlStorage()
const {getAgent} = useAgent() const {getAgent} = useAgent()
const [service] = useState( const [convo] = useState(
() => () =>
new Convo({ new Convo({
convoId, convoId,
@@ -34,21 +31,7 @@ export function ChatProvider({
__tempFromUserDid: getAgent().session?.did!, __tempFromUserDid: getAgent().session?.did!,
}), }),
) )
const [state, setState] = useState(service.state) const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot)
useEffect(() => { return <ChatContext.Provider value={service}>{children}</ChatContext.Provider>
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 <ChatContext.Provider value={value}>{children}</ChatContext.Provider>
} }