Skip to content

Commit

Permalink
[🐴] Make status checks easier, fix load state (#4010)
Browse files Browse the repository at this point in the history
* Make status checks easier, fix load state

* Make naming more clear

* Split up types for easier re-use

* Replace hacky usage
  • Loading branch information
estrattonbailey authored May 14, 2024
1 parent bffb9b5 commit 1c51a48
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 101 deletions.
7 changes: 2 additions & 5 deletions src/components/dms/MessageMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import {useLingui} from '@lingui/react'

import {richTextToString} from '#/lib/strings/rich-text-helpers'
import {isWeb} from 'platform/detection'
import {useConvo} from 'state/messages/convo'
import {ConvoStatus} from 'state/messages/convo/types'
import {useConvoActive} from 'state/messages/convo'
import {useSession} from 'state/session'
import * as Toast from '#/view/com/util/Toast'
import {atoms as a, useTheme} from '#/alf'
Expand All @@ -34,7 +33,7 @@ export let MessageMenu = ({
const {_} = useLingui()
const t = useTheme()
const {currentAccount} = useSession()
const convo = useConvo()
const convo = useConvoActive()
const deleteControl = usePromptControl()
const retryDeleteControl = usePromptControl()
const reportControl = usePromptControl()
Expand All @@ -55,8 +54,6 @@ export let MessageMenu = ({
}, [_, message.text, message.facets])

const onDelete = React.useCallback(() => {
if (convo.status !== ConvoStatus.Ready) return

LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
convo
.deleteMessage(message.id)
Expand Down
18 changes: 8 additions & 10 deletions src/screens/Messages/Conversation/MessagesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {AppBskyRichtextFacet, RichText} from '@atproto/api'

import {shortenLinks} from '#/lib/strings/rich-text-manip'
import {isNative} from '#/platform/detection'
import {useConvo} from '#/state/messages/convo'
import {ConvoItem, ConvoStatus} from '#/state/messages/convo/types'
import {useConvoActive} from '#/state/messages/convo'
import {ConvoItem} from '#/state/messages/convo/types'
import {useAgent} from '#/state/session'
import {ScrollProvider} from 'lib/ScrollContext'
import {isWeb} from 'platform/detection'
Expand Down Expand Up @@ -60,7 +60,7 @@ function onScrollToIndexFailed() {
}

export function MessagesList() {
const convo = useConvo()
const convo = useConvoActive()
const {getAgent} = useAgent()
const flatListRef = useRef<FlatList>(null)

Expand Down Expand Up @@ -128,7 +128,7 @@ export function MessagesList() {
// The check for `hasInitiallyScrolled` prevents an initial fetch on mount. FlatList triggers `onStartReached`
// immediately on mount, since we are in fact at an offset of zero, so we have to ignore those initial calls.
const onStartReached = useCallback(() => {
if (convo.status === ConvoStatus.Ready && hasInitiallyScrolled.value) {
if (hasInitiallyScrolled.value) {
convo.fetchMessageHistory()
}
}, [convo, hasInitiallyScrolled])
Expand All @@ -150,12 +150,10 @@ export function MessagesList() {
return true
})

if (convo.status === ConvoStatus.Ready) {
convo.sendMessage({
text: rt.text,
facets: rt.facets,
})
}
convo.sendMessage({
text: rt.text,
facets: rt.facets,
})
},
[convo, getAgent],
)
Expand Down
14 changes: 7 additions & 7 deletions src/screens/Messages/Conversation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {useGate} from '#/lib/statsig/statsig'
import {useCurrentConvoId} from '#/state/messages/current-convo-id'
import {BACK_HITSLOP} from 'lib/constants'
import {isIOS, isWeb} from 'platform/detection'
import {ConvoProvider, useConvo} from 'state/messages/convo'
import {ConvoProvider, isConvoActive, useConvo} from 'state/messages/convo'
import {ConvoStatus} from 'state/messages/convo/types'
import {PreviewableUserAvatar} from 'view/com/util/UserAvatar'
import {CenteredView} from 'view/com/util/Views'
Expand Down Expand Up @@ -72,14 +72,14 @@ function Inner() {
React.useEffect(() => {
if (
!hasInitiallyRendered &&
convoState.status === ConvoStatus.Ready &&
isConvoActive(convoState) &&
!convoState.isFetchingHistory
) {
setTimeout(() => {
setHasInitiallyRendered(true)
}, 15)
}
}, [convoState.isFetchingHistory, convoState.status, hasInitiallyRendered])
}, [convoState, hasInitiallyRendered])

if (convoState.status === ConvoStatus.Error) {
return (
Expand Down Expand Up @@ -108,10 +108,10 @@ function Inner() {
<CenteredView style={a.flex_1} sideBorders>
<Header profile={convoState.recipients?.[0]} />
<View style={[a.flex_1]}>
{convoState.status !== ConvoStatus.Ready ? (
<ListMaybePlaceholder isLoading />
) : (
{isConvoActive(convoState) ? (
<MessagesList />
) : (
<ListMaybePlaceholder isLoading />
)}
{!hasInitiallyRendered && (
<View
Expand Down Expand Up @@ -230,7 +230,7 @@ let Header = ({
</>
)}
</View>
{convoState.status === ConvoStatus.Ready && profile ? (
{isConvoActive(convoState) && profile ? (
<ConvoMenu
convo={convoState.convo}
profile={profile}
Expand Down
32 changes: 31 additions & 1 deletion src/state/messages/convo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@ import {AppState} from 'react-native'
import {useFocusEffect, useIsFocused} from '@react-navigation/native'

import {Convo} from '#/state/messages/convo/agent'
import {ConvoParams, ConvoState} from '#/state/messages/convo/types'
import {
ConvoParams,
ConvoState,
ConvoStateBackgrounded,
ConvoStateReady,
ConvoStateSuspended,
} from '#/state/messages/convo/types'
import {isConvoActive} from '#/state/messages/convo/util'
import {useMessagesEventBus} from '#/state/messages/events'
import {useMarkAsReadMutation} from '#/state/queries/messages/conversation'
import {useAgent} from '#/state/session'

export * from '#/state/messages/convo/util'

const ChatContext = React.createContext<ConvoState | null>(null)

export function useConvo() {
Expand All @@ -18,6 +27,27 @@ export function useConvo() {
return ctx
}

/**
* This hook should only be used when the Convo is "active", meaning the chat
* is loaded and ready to be used, or its in a suspended or background state,
* and ready for resumption.
*/
export function useConvoActive() {
const ctx = useContext(ChatContext) as
| ConvoStateReady
| ConvoStateBackgrounded
| ConvoStateSuspended
if (!ctx) {
throw new Error('useConvo must be used within a ConvoProvider')
}
if (!isConvoActive(ctx)) {
throw new Error(
`useConvoActive must only be rendered when the Convo is ready.`,
)
}
return ctx
}

export function ConvoProvider({
children,
convoId,
Expand Down
162 changes: 84 additions & 78 deletions src/state/messages/convo/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,82 +107,88 @@ export type ConvoItem =
retry: () => void
}

type DeleteMessage = (messageId: string) => Promise<void>
type SendMessage = (
message: ChatBskyConvoSendMessage.InputSchema['message'],
) => Promise<void>
type FetchMessageHistory = () => Promise<void>

export type ConvoStateUninitialized = {
status: ConvoStatus.Uninitialized
items: []
convo: undefined
error: undefined
sender: undefined
recipients: undefined
isFetchingHistory: false
deleteMessage: undefined
sendMessage: undefined
fetchMessageHistory: undefined
}
export type ConvoStateInitializing = {
status: ConvoStatus.Initializing
items: []
convo: undefined
error: undefined
sender: undefined
recipients: undefined
isFetchingHistory: boolean
deleteMessage: undefined
sendMessage: undefined
fetchMessageHistory: undefined
}
export type ConvoStateReady = {
status: ConvoStatus.Ready
items: ConvoItem[]
convo: ChatBskyConvoDefs.ConvoView
error: undefined
sender: AppBskyActorDefs.ProfileViewBasic
recipients: AppBskyActorDefs.ProfileViewBasic[]
isFetchingHistory: boolean
deleteMessage: DeleteMessage
sendMessage: SendMessage
fetchMessageHistory: FetchMessageHistory
}
export type ConvoStateBackgrounded = {
status: ConvoStatus.Backgrounded
items: ConvoItem[]
convo: ChatBskyConvoDefs.ConvoView
error: undefined
sender: AppBskyActorDefs.ProfileViewBasic
recipients: AppBskyActorDefs.ProfileViewBasic[]
isFetchingHistory: boolean
deleteMessage: DeleteMessage
sendMessage: SendMessage
fetchMessageHistory: FetchMessageHistory
}
export type ConvoStateSuspended = {
status: ConvoStatus.Suspended
items: ConvoItem[]
convo: ChatBskyConvoDefs.ConvoView
error: undefined
sender: AppBskyActorDefs.ProfileViewBasic
recipients: AppBskyActorDefs.ProfileViewBasic[]
isFetchingHistory: boolean
deleteMessage: DeleteMessage
sendMessage: SendMessage
fetchMessageHistory: FetchMessageHistory
}
export type ConvoStateError = {
status: ConvoStatus.Error
items: []
convo: undefined
error: any
sender: undefined
recipients: undefined
isFetchingHistory: false
deleteMessage: undefined
sendMessage: undefined
fetchMessageHistory: undefined
}
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
}
| ConvoStateUninitialized
| ConvoStateInitializing
| ConvoStateReady
| ConvoStateBackgrounded
| ConvoStateSuspended
| ConvoStateError
22 changes: 22 additions & 0 deletions src/state/messages/convo/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
ConvoState,
ConvoStateBackgrounded,
ConvoStateReady,
ConvoStateSuspended,
ConvoStatus,
} from './types'

/**
* Checks if a `Convo` has a `status` that is "active", meaning the chat is
* loaded and ready to be used, or its in a suspended or background state, and
* ready for resumption.
*/
export function isConvoActive(
convo: ConvoState,
): convo is ConvoStateReady | ConvoStateBackgrounded | ConvoStateSuspended {
return (
convo.status === ConvoStatus.Ready ||
convo.status === ConvoStatus.Backgrounded ||
convo.status === ConvoStatus.Suspended
)
}

0 comments on commit 1c51a48

Please sign in to comment.