[🐴] Make status checks easier, fix load state (#4010)
* Make status checks easier, fix load state * Make naming more clear * Split up types for easier re-use * Replace hacky usagezio/stable
parent
bffb9b5906
commit
1c51a48764
|
@ -7,8 +7,7 @@ import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from 'platform/detection'
|
||||||
import {useConvo} from 'state/messages/convo'
|
import {useConvoActive} from 'state/messages/convo'
|
||||||
import {ConvoStatus} from 'state/messages/convo/types'
|
|
||||||
import {useSession} from 'state/session'
|
import {useSession} from 'state/session'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
@ -34,7 +33,7 @@ export let MessageMenu = ({
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const convo = useConvo()
|
const convo = useConvoActive()
|
||||||
const deleteControl = usePromptControl()
|
const deleteControl = usePromptControl()
|
||||||
const retryDeleteControl = usePromptControl()
|
const retryDeleteControl = usePromptControl()
|
||||||
const reportControl = usePromptControl()
|
const reportControl = usePromptControl()
|
||||||
|
@ -55,8 +54,6 @@ export let MessageMenu = ({
|
||||||
}, [_, message.text, message.facets])
|
}, [_, message.text, message.facets])
|
||||||
|
|
||||||
const onDelete = React.useCallback(() => {
|
const onDelete = React.useCallback(() => {
|
||||||
if (convo.status !== ConvoStatus.Ready) return
|
|
||||||
|
|
||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||||
convo
|
convo
|
||||||
.deleteMessage(message.id)
|
.deleteMessage(message.id)
|
||||||
|
|
|
@ -7,8 +7,8 @@ import {AppBskyRichtextFacet, RichText} from '@atproto/api'
|
||||||
|
|
||||||
import {shortenLinks} from '#/lib/strings/rich-text-manip'
|
import {shortenLinks} from '#/lib/strings/rich-text-manip'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
import {useConvo} from '#/state/messages/convo'
|
import {useConvoActive} from '#/state/messages/convo'
|
||||||
import {ConvoItem, ConvoStatus} from '#/state/messages/convo/types'
|
import {ConvoItem} from '#/state/messages/convo/types'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
import {ScrollProvider} from 'lib/ScrollContext'
|
import {ScrollProvider} from 'lib/ScrollContext'
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from 'platform/detection'
|
||||||
|
@ -60,7 +60,7 @@ function onScrollToIndexFailed() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MessagesList() {
|
export function MessagesList() {
|
||||||
const convo = useConvo()
|
const convo = useConvoActive()
|
||||||
const {getAgent} = useAgent()
|
const {getAgent} = useAgent()
|
||||||
const flatListRef = useRef<FlatList>(null)
|
const flatListRef = useRef<FlatList>(null)
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ export function MessagesList() {
|
||||||
// The check for `hasInitiallyScrolled` prevents an initial fetch on mount. FlatList triggers `onStartReached`
|
// 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.
|
// immediately on mount, since we are in fact at an offset of zero, so we have to ignore those initial calls.
|
||||||
const onStartReached = useCallback(() => {
|
const onStartReached = useCallback(() => {
|
||||||
if (convo.status === ConvoStatus.Ready && hasInitiallyScrolled.value) {
|
if (hasInitiallyScrolled.value) {
|
||||||
convo.fetchMessageHistory()
|
convo.fetchMessageHistory()
|
||||||
}
|
}
|
||||||
}, [convo, hasInitiallyScrolled])
|
}, [convo, hasInitiallyScrolled])
|
||||||
|
@ -150,12 +150,10 @@ export function MessagesList() {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
if (convo.status === ConvoStatus.Ready) {
|
convo.sendMessage({
|
||||||
convo.sendMessage({
|
text: rt.text,
|
||||||
text: rt.text,
|
facets: rt.facets,
|
||||||
facets: rt.facets,
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[convo, getAgent],
|
[convo, getAgent],
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {useCurrentConvoId} from '#/state/messages/current-convo-id'
|
import {useCurrentConvoId} from '#/state/messages/current-convo-id'
|
||||||
import {BACK_HITSLOP} from 'lib/constants'
|
import {BACK_HITSLOP} from 'lib/constants'
|
||||||
import {isIOS, isWeb} from 'platform/detection'
|
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 {ConvoStatus} from 'state/messages/convo/types'
|
||||||
import {PreviewableUserAvatar} from 'view/com/util/UserAvatar'
|
import {PreviewableUserAvatar} from 'view/com/util/UserAvatar'
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
import {CenteredView} from 'view/com/util/Views'
|
||||||
|
@ -72,14 +72,14 @@ function Inner() {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!hasInitiallyRendered &&
|
!hasInitiallyRendered &&
|
||||||
convoState.status === ConvoStatus.Ready &&
|
isConvoActive(convoState) &&
|
||||||
!convoState.isFetchingHistory
|
!convoState.isFetchingHistory
|
||||||
) {
|
) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setHasInitiallyRendered(true)
|
setHasInitiallyRendered(true)
|
||||||
}, 15)
|
}, 15)
|
||||||
}
|
}
|
||||||
}, [convoState.isFetchingHistory, convoState.status, hasInitiallyRendered])
|
}, [convoState, hasInitiallyRendered])
|
||||||
|
|
||||||
if (convoState.status === ConvoStatus.Error) {
|
if (convoState.status === ConvoStatus.Error) {
|
||||||
return (
|
return (
|
||||||
|
@ -108,10 +108,10 @@ function Inner() {
|
||||||
<CenteredView style={a.flex_1} sideBorders>
|
<CenteredView style={a.flex_1} sideBorders>
|
||||||
<Header profile={convoState.recipients?.[0]} />
|
<Header profile={convoState.recipients?.[0]} />
|
||||||
<View style={[a.flex_1]}>
|
<View style={[a.flex_1]}>
|
||||||
{convoState.status !== ConvoStatus.Ready ? (
|
{isConvoActive(convoState) ? (
|
||||||
<ListMaybePlaceholder isLoading />
|
|
||||||
) : (
|
|
||||||
<MessagesList />
|
<MessagesList />
|
||||||
|
) : (
|
||||||
|
<ListMaybePlaceholder isLoading />
|
||||||
)}
|
)}
|
||||||
{!hasInitiallyRendered && (
|
{!hasInitiallyRendered && (
|
||||||
<View
|
<View
|
||||||
|
@ -230,7 +230,7 @@ let Header = ({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{convoState.status === ConvoStatus.Ready && profile ? (
|
{isConvoActive(convoState) && profile ? (
|
||||||
<ConvoMenu
|
<ConvoMenu
|
||||||
convo={convoState.convo}
|
convo={convoState.convo}
|
||||||
profile={profile}
|
profile={profile}
|
||||||
|
|
|
@ -3,11 +3,20 @@ import {AppState} from 'react-native'
|
||||||
import {useFocusEffect, useIsFocused} from '@react-navigation/native'
|
import {useFocusEffect, useIsFocused} from '@react-navigation/native'
|
||||||
|
|
||||||
import {Convo} from '#/state/messages/convo/agent'
|
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 {useMessagesEventBus} from '#/state/messages/events'
|
||||||
import {useMarkAsReadMutation} from '#/state/queries/messages/conversation'
|
import {useMarkAsReadMutation} from '#/state/queries/messages/conversation'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
|
|
||||||
|
export * from '#/state/messages/convo/util'
|
||||||
|
|
||||||
const ChatContext = React.createContext<ConvoState | null>(null)
|
const ChatContext = React.createContext<ConvoState | null>(null)
|
||||||
|
|
||||||
export function useConvo() {
|
export function useConvo() {
|
||||||
|
@ -18,6 +27,27 @@ export function useConvo() {
|
||||||
return ctx
|
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({
|
export function ConvoProvider({
|
||||||
children,
|
children,
|
||||||
convoId,
|
convoId,
|
||||||
|
|
|
@ -107,82 +107,88 @@ export type ConvoItem =
|
||||||
retry: () => void
|
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 =
|
export type ConvoState =
|
||||||
| {
|
| ConvoStateUninitialized
|
||||||
status: ConvoStatus.Uninitialized
|
| ConvoStateInitializing
|
||||||
items: []
|
| ConvoStateReady
|
||||||
convo: undefined
|
| ConvoStateBackgrounded
|
||||||
error: undefined
|
| ConvoStateSuspended
|
||||||
sender: undefined
|
| ConvoStateError
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue