From 1c51a48764e4145679198f68368713410e28c8da Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 14 May 2024 11:59:53 -0500 Subject: [PATCH] =?UTF-8?q?[=F0=9F=90=B4]=20Make=20status=20checks=20easie?= =?UTF-8?q?r,=20fix=20load=20state=20(#4010)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make status checks easier, fix load state * Make naming more clear * Split up types for easier re-use * Replace hacky usage --- src/components/dms/MessageMenu.tsx | 7 +- .../Messages/Conversation/MessagesList.tsx | 18 +- src/screens/Messages/Conversation/index.tsx | 14 +- src/state/messages/convo/index.tsx | 32 +++- src/state/messages/convo/types.ts | 162 +++++++++--------- src/state/messages/convo/util.ts | 22 +++ 6 files changed, 154 insertions(+), 101 deletions(-) create mode 100644 src/state/messages/convo/util.ts diff --git a/src/components/dms/MessageMenu.tsx b/src/components/dms/MessageMenu.tsx index 3349b2ff89..55c3ac21b6 100644 --- a/src/components/dms/MessageMenu.tsx +++ b/src/components/dms/MessageMenu.tsx @@ -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' @@ -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() @@ -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) diff --git a/src/screens/Messages/Conversation/MessagesList.tsx b/src/screens/Messages/Conversation/MessagesList.tsx index 5ba82eeff9..dac534cd41 100644 --- a/src/screens/Messages/Conversation/MessagesList.tsx +++ b/src/screens/Messages/Conversation/MessagesList.tsx @@ -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' @@ -60,7 +60,7 @@ function onScrollToIndexFailed() { } export function MessagesList() { - const convo = useConvo() + const convo = useConvoActive() const {getAgent} = useAgent() const flatListRef = useRef(null) @@ -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]) @@ -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], ) diff --git a/src/screens/Messages/Conversation/index.tsx b/src/screens/Messages/Conversation/index.tsx index a783a0bd6d..01c205ac82 100644 --- a/src/screens/Messages/Conversation/index.tsx +++ b/src/screens/Messages/Conversation/index.tsx @@ -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' @@ -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 ( @@ -108,10 +108,10 @@ function Inner() {
- {convoState.status !== ConvoStatus.Ready ? ( - - ) : ( + {isConvoActive(convoState) ? ( + ) : ( + )} {!hasInitiallyRendered && ( )} - {convoState.status === ConvoStatus.Ready && profile ? ( + {isConvoActive(convoState) && profile ? ( (null) export function useConvo() { @@ -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, diff --git a/src/state/messages/convo/types.ts b/src/state/messages/convo/types.ts index 4615acc2d9..6ce4d40bd6 100644 --- a/src/state/messages/convo/types.ts +++ b/src/state/messages/convo/types.ts @@ -107,82 +107,88 @@ export type ConvoItem = retry: () => void } +type DeleteMessage = (messageId: string) => Promise +type SendMessage = ( + message: ChatBskyConvoSendMessage.InputSchema['message'], +) => Promise +type FetchMessageHistory = () => Promise + +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 - 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 - sendMessage: ( - message: ChatBskyConvoSendMessage.InputSchema['message'], - ) => Promise - fetchMessageHistory: () => Promise - } - | { - status: ConvoStatus.Backgrounded - items: ConvoItem[] - convo: ChatBskyConvoDefs.ConvoView - error: undefined - sender: AppBskyActorDefs.ProfileViewBasic - recipients: AppBskyActorDefs.ProfileViewBasic[] - isFetchingHistory: boolean - deleteMessage: (messageId: string) => Promise - sendMessage: ( - message: ChatBskyConvoSendMessage.InputSchema['message'], - ) => Promise - fetchMessageHistory: () => Promise - } - | { - 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 diff --git a/src/state/messages/convo/util.ts b/src/state/messages/convo/util.ts new file mode 100644 index 0000000000..ffaa4104a7 --- /dev/null +++ b/src/state/messages/convo/util.ts @@ -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 + ) +}