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,
})
},
[chat.service],
[chat],
)
const onScroll = React.useCallback(
+6 -10
View File
@@ -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<NavigationProp>()
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 (
<View
@@ -133,9 +129,9 @@ let Header = ({
<PreviewableUserAvatar size={32} profile={profile} />
<Text style={[a.text_lg, a.font_bold]}>{profile.displayName}</Text>
</View>
{service.convo ? (
{state.status === ConvoStatus.Ready && state.convo ? (
<ConvoMenu
convo={service.convo}
convo={state.convo}
profile={profile}
onUpdateConvo={onUpdateConvo}
currentScreen="conversation"
+59 -20
View File
@@ -4,7 +4,6 @@ import {
ChatBskyConvoDefs,
ChatBskyConvoSendMessage,
} from '@atproto-labs/api'
import {EventEmitter} from 'eventemitter3'
import {nanoid} from 'nanoid/non-secure'
import {isNative} from '#/platform/detection'
@@ -69,6 +68,17 @@ export type ConvoState =
status: ConvoStatus.Destroyed
}
export type ConvoInterface = {
state: ConvoState
service: {
deleteMessage: (messageId: string) => 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())
}
}
+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 {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<ConvoInterface | null>(null)
export function useChat() {
const ctx = useContext(ChatContext)
@@ -24,7 +21,7 @@ export function ChatProvider({
}: Pick<ConvoParams, 'convoId'> & {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 <ChatContext.Provider value={value}>{children}</ChatContext.Provider>
return <ChatContext.Provider value={service}>{children}</ChatContext.Provider>
}