An ATproto social media client -- with an independent Appview.

[🐴] Reorg convo files (#3909)

* Remove unused prop

* Reorganize

authored by

Eric Bailey and committed by
GitHub
56f71307 814ec2bd

+296 -278
+6 -6
src/components/dms/MessageMenu.tsx
··· 5 5 import {msg} from '@lingui/macro' 6 6 import {useLingui} from '@lingui/react' 7 7 8 - import {useChat} from 'state/messages' 9 - import {ConvoStatus} from 'state/messages/convo' 8 + import {useConvo} from 'state/messages/convo' 9 + import {ConvoStatus} from 'state/messages/convo/types' 10 10 import {useSession} from 'state/session' 11 11 import * as Toast from '#/view/com/util/Toast' 12 12 import {atoms as a, useTheme} from '#/alf' ··· 33 33 const {_} = useLingui() 34 34 const t = useTheme() 35 35 const {currentAccount} = useSession() 36 - const chat = useChat() 36 + const convo = useConvo() 37 37 const deleteControl = usePromptControl() 38 38 const retryDeleteControl = usePromptControl() 39 39 ··· 48 48 }, [_, message.text]) 49 49 50 50 const onDelete = React.useCallback(() => { 51 - if (chat.status !== ConvoStatus.Ready) return 51 + if (convo.status !== ConvoStatus.Ready) return 52 52 53 53 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) 54 - chat 54 + convo 55 55 .deleteMessage(message.id) 56 56 .then(() => Toast.show(_(msg`Message deleted`))) 57 57 .catch(() => retryDeleteControl.open()) 58 - }, [_, chat, message.id, retryDeleteControl]) 58 + }, [_, convo, message.id, retryDeleteControl]) 59 59 60 60 const onReport = React.useCallback(() => { 61 61 // TODO report the message
+1 -1
src/screens/Messages/Conversation/MessageListError.tsx
··· 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {ConvoItem, ConvoItemError} from '#/state/messages/convo' 6 + import {ConvoItem, ConvoItemError} from '#/state/messages/convo/types' 7 7 import {atoms as a, useTheme} from '#/alf' 8 8 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 9 9 import {InlineLinkText} from '#/components/Link'
+11 -11
src/screens/Messages/Conversation/MessagesList.tsx
··· 11 11 import {useLingui} from '@lingui/react' 12 12 13 13 import {isIOS} from '#/platform/detection' 14 - import {useChat} from '#/state/messages' 15 - import {ConvoItem, ConvoStatus} from '#/state/messages/convo' 14 + import {useConvo} from '#/state/messages/convo' 15 + import {ConvoItem, ConvoStatus} from '#/state/messages/convo/types' 16 16 import {ScrollProvider} from 'lib/ScrollContext' 17 17 import {isWeb} from 'platform/detection' 18 18 import {List} from 'view/com/util/List' ··· 86 86 } 87 87 88 88 export function MessagesList() { 89 - const chat = useChat() 89 + const convo = useConvo() 90 90 const flatListRef = useRef<FlatList>(null) 91 91 92 92 // We need to keep track of when the scroll offset is at the bottom of the list to know when to scroll as new items ··· 153 153 // The check for `hasInitiallyScrolled` prevents an initial fetch on mount. FlatList triggers `onStartReached` 154 154 // immediately on mount, since we are in fact at an offset of zero, so we have to ignore those initial calls. 155 155 const onStartReached = useCallback(() => { 156 - if (chat.status === ConvoStatus.Ready && hasInitiallyScrolled) { 157 - chat.fetchMessageHistory() 156 + if (convo.status === ConvoStatus.Ready && hasInitiallyScrolled) { 157 + convo.fetchMessageHistory() 158 158 } 159 - }, [chat, hasInitiallyScrolled]) 159 + }, [convo, hasInitiallyScrolled]) 160 160 161 161 const onSendMessage = useCallback( 162 162 (text: string) => { 163 - if (chat.status === ConvoStatus.Ready) { 164 - chat.sendMessage({ 163 + if (convo.status === ConvoStatus.Ready) { 164 + convo.sendMessage({ 165 165 text, 166 166 }) 167 167 } 168 168 }, 169 - [chat], 169 + [convo], 170 170 ) 171 171 172 172 const onScroll = React.useCallback( ··· 229 229 <ScrollProvider onScroll={onScroll} onMomentumEnd={onMomentumEnd}> 230 230 <List 231 231 ref={flatListRef} 232 - data={chat.items} 232 + data={convo.items} 233 233 renderItem={renderItem} 234 234 keyExtractor={keyExtractor} 235 235 disableVirtualization={true} ··· 248 248 onScrollToIndexFailed={onScrollToIndexFailed} 249 249 scrollEventThrottle={100} 250 250 ListHeaderComponent={ 251 - <MaybeLoader isLoading={chat.isFetchingHistory} /> 251 + <MaybeLoader isLoading={convo.isFetchingHistory} /> 252 252 } 253 253 /> 254 254 </ScrollProvider>
+14 -14
src/screens/Messages/Conversation/index.tsx
··· 13 13 import {useCurrentConvoId} from '#/state/messages/current-convo-id' 14 14 import {BACK_HITSLOP} from 'lib/constants' 15 15 import {isWeb} from 'platform/detection' 16 - import {ChatProvider, useChat} from 'state/messages' 17 - import {ConvoStatus} from 'state/messages/convo' 16 + import {ConvoProvider, useConvo} from 'state/messages/convo' 17 + import {ConvoStatus} from 'state/messages/convo/types' 18 18 import {PreviewableUserAvatar} from 'view/com/util/UserAvatar' 19 19 import {CenteredView} from 'view/com/util/Views' 20 20 import {MessagesList} from '#/screens/Messages/Conversation/MessagesList' ··· 46 46 if (!gate('dms')) return <ClipClopGate /> 47 47 48 48 return ( 49 - <ChatProvider convoId={convoId}> 49 + <ConvoProvider convoId={convoId}> 50 50 <Inner /> 51 - </ChatProvider> 51 + </ConvoProvider> 52 52 ) 53 53 } 54 54 55 55 function Inner() { 56 - const chat = useChat() 56 + const convo = useConvo() 57 57 58 58 if ( 59 - chat.status === ConvoStatus.Uninitialized || 60 - chat.status === ConvoStatus.Initializing 59 + convo.status === ConvoStatus.Uninitialized || 60 + convo.status === ConvoStatus.Initializing 61 61 ) { 62 62 return <ListMaybePlaceholder isLoading /> 63 63 } 64 64 65 - if (chat.status === ConvoStatus.Error) { 65 + if (convo.status === ConvoStatus.Error) { 66 66 // TODO 67 67 return ( 68 68 <View> ··· 71 71 <Button 72 72 label="Retry" 73 73 onPress={() => { 74 - chat.error.retry() 74 + convo.error.retry() 75 75 }}> 76 76 <ButtonText>Retry</ButtonText> 77 77 </Button> ··· 81 81 } 82 82 83 83 /* 84 - * Any other chat states (atm) are "ready" states 84 + * Any other convo states (atm) are "ready" states 85 85 */ 86 86 87 87 return ( 88 88 <KeyboardProvider> 89 89 <CenteredView style={{flex: 1}} sideBorders> 90 - <Header profile={chat.recipients[0]} /> 90 + <Header profile={convo.recipients[0]} /> 91 91 <MessagesList /> 92 92 </CenteredView> 93 93 </KeyboardProvider> ··· 103 103 const {_} = useLingui() 104 104 const {gtTablet} = useBreakpoints() 105 105 const navigation = useNavigation<NavigationProp>() 106 - const chat = useChat() 106 + const convo = useConvo() 107 107 108 108 const onPressBack = useCallback(() => { 109 109 if (isWeb) { ··· 157 157 {profile.displayName} 158 158 </Text> 159 159 </View> 160 - {chat.status === ConvoStatus.Ready ? ( 160 + {convo.status === ConvoStatus.Ready ? ( 161 161 <ConvoMenu 162 - convo={chat.convo} 162 + convo={convo.convo} 163 163 profile={profile} 164 164 onUpdateConvo={onUpdateConvo} 165 165 currentScreen="conversation"
+10 -173
src/state/messages/convo.ts src/state/messages/convo/agent.ts
··· 9 9 10 10 import {logger} from '#/logger' 11 11 import {isNative} from '#/platform/detection' 12 - 13 - export type ConvoParams = { 14 - convoId: string 15 - agent: BskyAgent 16 - __tempFromUserDid: string 17 - } 18 - 19 - export enum ConvoStatus { 20 - Uninitialized = 'uninitialized', 21 - Initializing = 'initializing', 22 - Ready = 'ready', 23 - Error = 'error', 24 - Backgrounded = 'backgrounded', 25 - Suspended = 'suspended', 26 - } 27 - 28 - export enum ConvoItemError { 29 - HistoryFailed = 'historyFailed', 30 - PollFailed = 'pollFailed', 31 - Network = 'network', 32 - } 33 - 34 - export enum ConvoErrorCode { 35 - InitFailed = 'initFailed', 36 - } 37 - 38 - export type ConvoError = { 39 - code: ConvoErrorCode 40 - exception?: Error 41 - retry: () => void 42 - } 43 - 44 - export enum ConvoDispatchEvent { 45 - Init = 'init', 46 - Ready = 'ready', 47 - Resume = 'resume', 48 - Background = 'background', 49 - Suspend = 'suspend', 50 - Error = 'error', 51 - } 52 - 53 - export type ConvoDispatch = 54 - | { 55 - event: ConvoDispatchEvent.Init 56 - } 57 - | { 58 - event: ConvoDispatchEvent.Ready 59 - } 60 - | { 61 - event: ConvoDispatchEvent.Resume 62 - } 63 - | { 64 - event: ConvoDispatchEvent.Background 65 - } 66 - | { 67 - event: ConvoDispatchEvent.Suspend 68 - } 69 - | { 70 - event: ConvoDispatchEvent.Error 71 - payload: ConvoError 72 - } 73 - 74 - export type ConvoItem = 75 - | { 76 - type: 'message' | 'pending-message' 77 - key: string 78 - message: ChatBskyConvoDefs.MessageView 79 - nextMessage: 80 - | ChatBskyConvoDefs.MessageView 81 - | ChatBskyConvoDefs.DeletedMessageView 82 - | null 83 - } 84 - | { 85 - type: 'deleted-message' 86 - key: string 87 - message: ChatBskyConvoDefs.DeletedMessageView 88 - nextMessage: 89 - | ChatBskyConvoDefs.MessageView 90 - | ChatBskyConvoDefs.DeletedMessageView 91 - | null 92 - } 93 - | { 94 - type: 'pending-retry' 95 - key: string 96 - retry: () => void 97 - } 98 - | { 99 - type: 'error-recoverable' 100 - key: string 101 - code: ConvoItemError 102 - retry: () => void 103 - } 104 - 105 - export type ConvoState = 106 - | { 107 - status: ConvoStatus.Uninitialized 108 - items: [] 109 - convo: undefined 110 - error: undefined 111 - sender: undefined 112 - recipients: undefined 113 - isFetchingHistory: false 114 - deleteMessage: undefined 115 - sendMessage: undefined 116 - fetchMessageHistory: undefined 117 - } 118 - | { 119 - status: ConvoStatus.Initializing 120 - items: [] 121 - convo: undefined 122 - error: undefined 123 - sender: undefined 124 - recipients: undefined 125 - isFetchingHistory: boolean 126 - deleteMessage: undefined 127 - sendMessage: undefined 128 - fetchMessageHistory: undefined 129 - } 130 - | { 131 - status: ConvoStatus.Ready 132 - items: ConvoItem[] 133 - convo: ChatBskyConvoDefs.ConvoView 134 - error: undefined 135 - sender: AppBskyActorDefs.ProfileViewBasic 136 - recipients: AppBskyActorDefs.ProfileViewBasic[] 137 - isFetchingHistory: boolean 138 - deleteMessage: (messageId: string) => Promise<void> 139 - sendMessage: ( 140 - message: ChatBskyConvoSendMessage.InputSchema['message'], 141 - ) => void 142 - fetchMessageHistory: () => void 143 - } 144 - | { 145 - status: ConvoStatus.Suspended 146 - items: ConvoItem[] 147 - convo: ChatBskyConvoDefs.ConvoView 148 - error: undefined 149 - sender: AppBskyActorDefs.ProfileViewBasic 150 - recipients: AppBskyActorDefs.ProfileViewBasic[] 151 - isFetchingHistory: boolean 152 - deleteMessage: (messageId: string) => Promise<void> 153 - sendMessage: ( 154 - message: ChatBskyConvoSendMessage.InputSchema['message'], 155 - ) => Promise<void> 156 - fetchMessageHistory: () => Promise<void> 157 - } 158 - | { 159 - status: ConvoStatus.Backgrounded 160 - items: ConvoItem[] 161 - convo: ChatBskyConvoDefs.ConvoView 162 - error: undefined 163 - sender: AppBskyActorDefs.ProfileViewBasic 164 - recipients: AppBskyActorDefs.ProfileViewBasic[] 165 - isFetchingHistory: boolean 166 - deleteMessage: (messageId: string) => Promise<void> 167 - sendMessage: ( 168 - message: ChatBskyConvoSendMessage.InputSchema['message'], 169 - ) => Promise<void> 170 - fetchMessageHistory: () => Promise<void> 171 - } 172 - | { 173 - status: ConvoStatus.Error 174 - items: [] 175 - convo: undefined 176 - error: any 177 - sender: undefined 178 - recipients: undefined 179 - isFetchingHistory: false 180 - deleteMessage: undefined 181 - sendMessage: undefined 182 - fetchMessageHistory: undefined 183 - } 12 + import { 13 + ConvoDispatch, 14 + ConvoDispatchEvent, 15 + ConvoErrorCode, 16 + ConvoItem, 17 + ConvoItemError, 18 + ConvoParams, 19 + ConvoState, 20 + ConvoStatus, 21 + } from '#/state/messages/convo/types' 184 22 185 23 const ACTIVE_POLL_INTERVAL = 1e3 186 24 const BACKGROUND_POLL_INTERVAL = 10e3 ··· 235 73 private headerItems: Map<string, ConvoItem> = new Map() 236 74 237 75 private isProcessingPendingMessages = false 238 - private pendingPoll: Promise<void> | undefined 239 76 private nextPoll: NodeJS.Timeout | undefined 240 77 241 78 convoId: string
+75
src/state/messages/convo/index.tsx
··· 1 + import React, {useContext, useState, useSyncExternalStore} from 'react' 2 + import {AppState} from 'react-native' 3 + import {BskyAgent} from '@atproto-labs/api' 4 + import {useFocusEffect, useIsFocused} from '@react-navigation/native' 5 + 6 + import {Convo} from '#/state/messages/convo/agent' 7 + import {ConvoParams, ConvoState} from '#/state/messages/convo/types' 8 + import {useMarkAsReadMutation} from '#/state/queries/messages/conversation' 9 + import {useAgent} from '#/state/session' 10 + import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage' 11 + 12 + const ChatContext = React.createContext<ConvoState | null>(null) 13 + 14 + export function useConvo() { 15 + const ctx = useContext(ChatContext) 16 + if (!ctx) { 17 + throw new Error('useConvo must be used within a ConvoProvider') 18 + } 19 + return ctx 20 + } 21 + 22 + export function ConvoProvider({ 23 + children, 24 + convoId, 25 + }: Pick<ConvoParams, 'convoId'> & {children: React.ReactNode}) { 26 + const isScreenFocused = useIsFocused() 27 + const {serviceUrl} = useDmServiceUrlStorage() 28 + const {getAgent} = useAgent() 29 + const [convo] = useState( 30 + () => 31 + new Convo({ 32 + convoId, 33 + agent: new BskyAgent({ 34 + service: serviceUrl, 35 + }), 36 + __tempFromUserDid: getAgent().session?.did!, 37 + }), 38 + ) 39 + const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot) 40 + const {mutate: markAsRead} = useMarkAsReadMutation() 41 + 42 + useFocusEffect( 43 + React.useCallback(() => { 44 + convo.resume() 45 + markAsRead({convoId}) 46 + 47 + return () => { 48 + convo.background() 49 + markAsRead({convoId}) 50 + } 51 + }, [convo, convoId, markAsRead]), 52 + ) 53 + 54 + React.useEffect(() => { 55 + const handleAppStateChange = (nextAppState: string) => { 56 + if (isScreenFocused) { 57 + if (nextAppState === 'active') { 58 + convo.resume() 59 + } else { 60 + convo.background() 61 + } 62 + 63 + markAsRead({convoId}) 64 + } 65 + } 66 + 67 + const sub = AppState.addEventListener('change', handleAppStateChange) 68 + 69 + return () => { 70 + sub.remove() 71 + } 72 + }, [convoId, convo, isScreenFocused, markAsRead]) 73 + 74 + return <ChatContext.Provider value={service}>{children}</ChatContext.Provider> 75 + }
+178
src/state/messages/convo/types.ts
··· 1 + import {AppBskyActorDefs} from '@atproto/api' 2 + import { 3 + BskyAgent, 4 + ChatBskyConvoDefs, 5 + ChatBskyConvoSendMessage, 6 + } from '@atproto-labs/api' 7 + 8 + export type ConvoParams = { 9 + convoId: string 10 + agent: BskyAgent 11 + __tempFromUserDid: string 12 + } 13 + 14 + export enum ConvoStatus { 15 + Uninitialized = 'uninitialized', 16 + Initializing = 'initializing', 17 + Ready = 'ready', 18 + Error = 'error', 19 + Backgrounded = 'backgrounded', 20 + Suspended = 'suspended', 21 + } 22 + 23 + export enum ConvoItemError { 24 + HistoryFailed = 'historyFailed', 25 + PollFailed = 'pollFailed', 26 + Network = 'network', 27 + } 28 + 29 + export enum ConvoErrorCode { 30 + InitFailed = 'initFailed', 31 + } 32 + 33 + export type ConvoError = { 34 + code: ConvoErrorCode 35 + exception?: Error 36 + retry: () => void 37 + } 38 + 39 + export enum ConvoDispatchEvent { 40 + Init = 'init', 41 + Ready = 'ready', 42 + Resume = 'resume', 43 + Background = 'background', 44 + Suspend = 'suspend', 45 + Error = 'error', 46 + } 47 + 48 + export type ConvoDispatch = 49 + | { 50 + event: ConvoDispatchEvent.Init 51 + } 52 + | { 53 + event: ConvoDispatchEvent.Ready 54 + } 55 + | { 56 + event: ConvoDispatchEvent.Resume 57 + } 58 + | { 59 + event: ConvoDispatchEvent.Background 60 + } 61 + | { 62 + event: ConvoDispatchEvent.Suspend 63 + } 64 + | { 65 + event: ConvoDispatchEvent.Error 66 + payload: ConvoError 67 + } 68 + 69 + export type ConvoItem = 70 + | { 71 + type: 'message' | 'pending-message' 72 + key: string 73 + message: ChatBskyConvoDefs.MessageView 74 + nextMessage: 75 + | ChatBskyConvoDefs.MessageView 76 + | ChatBskyConvoDefs.DeletedMessageView 77 + | null 78 + } 79 + | { 80 + type: 'deleted-message' 81 + key: string 82 + message: ChatBskyConvoDefs.DeletedMessageView 83 + nextMessage: 84 + | ChatBskyConvoDefs.MessageView 85 + | ChatBskyConvoDefs.DeletedMessageView 86 + | null 87 + } 88 + | { 89 + type: 'pending-retry' 90 + key: string 91 + retry: () => void 92 + } 93 + | { 94 + type: 'error-recoverable' 95 + key: string 96 + code: ConvoItemError 97 + retry: () => void 98 + } 99 + 100 + export type ConvoState = 101 + | { 102 + status: ConvoStatus.Uninitialized 103 + items: [] 104 + convo: undefined 105 + error: undefined 106 + sender: undefined 107 + recipients: undefined 108 + isFetchingHistory: false 109 + deleteMessage: undefined 110 + sendMessage: undefined 111 + fetchMessageHistory: undefined 112 + } 113 + | { 114 + status: ConvoStatus.Initializing 115 + items: [] 116 + convo: undefined 117 + error: undefined 118 + sender: undefined 119 + recipients: undefined 120 + isFetchingHistory: boolean 121 + deleteMessage: undefined 122 + sendMessage: undefined 123 + fetchMessageHistory: undefined 124 + } 125 + | { 126 + status: ConvoStatus.Ready 127 + items: ConvoItem[] 128 + convo: ChatBskyConvoDefs.ConvoView 129 + error: undefined 130 + sender: AppBskyActorDefs.ProfileViewBasic 131 + recipients: AppBskyActorDefs.ProfileViewBasic[] 132 + isFetchingHistory: boolean 133 + deleteMessage: (messageId: string) => Promise<void> 134 + sendMessage: ( 135 + message: ChatBskyConvoSendMessage.InputSchema['message'], 136 + ) => void 137 + fetchMessageHistory: () => void 138 + } 139 + | { 140 + status: ConvoStatus.Suspended 141 + items: ConvoItem[] 142 + convo: ChatBskyConvoDefs.ConvoView 143 + error: undefined 144 + sender: AppBskyActorDefs.ProfileViewBasic 145 + recipients: AppBskyActorDefs.ProfileViewBasic[] 146 + isFetchingHistory: boolean 147 + deleteMessage: (messageId: string) => Promise<void> 148 + sendMessage: ( 149 + message: ChatBskyConvoSendMessage.InputSchema['message'], 150 + ) => Promise<void> 151 + fetchMessageHistory: () => Promise<void> 152 + } 153 + | { 154 + status: ConvoStatus.Backgrounded 155 + items: ConvoItem[] 156 + convo: ChatBskyConvoDefs.ConvoView 157 + error: undefined 158 + sender: AppBskyActorDefs.ProfileViewBasic 159 + recipients: AppBskyActorDefs.ProfileViewBasic[] 160 + isFetchingHistory: boolean 161 + deleteMessage: (messageId: string) => Promise<void> 162 + sendMessage: ( 163 + message: ChatBskyConvoSendMessage.InputSchema['message'], 164 + ) => Promise<void> 165 + fetchMessageHistory: () => Promise<void> 166 + } 167 + | { 168 + status: ConvoStatus.Error 169 + items: [] 170 + convo: undefined 171 + error: any 172 + sender: undefined 173 + recipients: undefined 174 + isFetchingHistory: false 175 + deleteMessage: undefined 176 + sendMessage: undefined 177 + fetchMessageHistory: undefined 178 + }
+1 -73
src/state/messages/index.tsx
··· 1 - import React, {useContext, useState, useSyncExternalStore} from 'react' 2 - import {AppState} from 'react-native' 3 - import {BskyAgent} from '@atproto-labs/api' 4 - import {useFocusEffect, useIsFocused} from '@react-navigation/native' 1 + import React from 'react' 5 2 6 - import {Convo, ConvoParams, ConvoState} from '#/state/messages/convo' 7 3 import {CurrentConvoIdProvider} from '#/state/messages/current-convo-id' 8 4 import {MessagesEventBusProvider} from '#/state/messages/events' 9 - import {useMarkAsReadMutation} from '#/state/queries/messages/conversation' 10 - import {useAgent} from '#/state/session' 11 - import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage' 12 - 13 - const ChatContext = React.createContext<ConvoState | null>(null) 14 - 15 - export function useChat() { 16 - const ctx = useContext(ChatContext) 17 - if (!ctx) { 18 - throw new Error('useChat must be used within a ChatProvider') 19 - } 20 - return ctx 21 - } 22 - 23 - export function ChatProvider({ 24 - children, 25 - convoId, 26 - }: Pick<ConvoParams, 'convoId'> & {children: React.ReactNode}) { 27 - const isScreenFocused = useIsFocused() 28 - const {serviceUrl} = useDmServiceUrlStorage() 29 - const {getAgent} = useAgent() 30 - const [convo] = useState( 31 - () => 32 - new Convo({ 33 - convoId, 34 - agent: new BskyAgent({ 35 - service: serviceUrl, 36 - }), 37 - __tempFromUserDid: getAgent().session?.did!, 38 - }), 39 - ) 40 - const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot) 41 - const {mutate: markAsRead} = useMarkAsReadMutation() 42 - 43 - useFocusEffect( 44 - React.useCallback(() => { 45 - convo.resume() 46 - markAsRead({convoId}) 47 - 48 - return () => { 49 - convo.background() 50 - markAsRead({convoId}) 51 - } 52 - }, [convo, convoId, markAsRead]), 53 - ) 54 - 55 - React.useEffect(() => { 56 - const handleAppStateChange = (nextAppState: string) => { 57 - if (isScreenFocused) { 58 - if (nextAppState === 'active') { 59 - convo.resume() 60 - } else { 61 - convo.background() 62 - } 63 - 64 - markAsRead({convoId}) 65 - } 66 - } 67 - 68 - const sub = AppState.addEventListener('change', handleAppStateChange) 69 - 70 - return () => { 71 - sub.remove() 72 - } 73 - }, [convoId, convo, isScreenFocused, markAsRead]) 74 - 75 - return <ChatContext.Provider value={service}>{children}</ChatContext.Provider> 76 - } 77 5 78 6 export function MessagesProvider({children}: {children: React.ReactNode}) { 79 7 return (