From 04aea931925099f006e80d1472b4d5cc6d498fcb Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 15 May 2024 11:45:18 -0500 Subject: [PATCH] =?UTF-8?q?[=F0=9F=90=B4]=20Better=20retry=20styling=20(#4?= =?UTF-8?q?032)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Pass whole object to MessageItem for clarity * Add retry to pending-message * Style send failure, retry * Group pending messages * Remove todos * Fix types with fake message --- src/components/dms/MessageItem.tsx | 136 +++++++++++------- src/components/dms/MessageReportDialog.tsx | 8 +- .../Conversation/MessageListError.tsx | 1 - .../Messages/Conversation/MessagesList.tsx | 8 +- src/state/messages/convo/agent.ts | 36 ++--- src/state/messages/convo/types.ts | 19 ++- 6 files changed, 121 insertions(+), 87 deletions(-) diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx index f8ab8518..cafd7ca5 100644 --- a/src/components/dms/MessageItem.tsx +++ b/src/components/dms/MessageItem.tsx @@ -1,40 +1,44 @@ import React, {useCallback, useMemo, useRef} from 'react' -import {LayoutAnimation, StyleProp, TextStyle, View} from 'react-native' +import { + GestureResponderEvent, + LayoutAnimation, + StyleProp, + TextStyle, + View, +} from 'react-native' import {ChatBskyConvoDefs, RichText as RichTextAPI} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {ConvoItem} from '#/state/messages/convo/types' import {useSession} from '#/state/session' import {TimeElapsed} from 'view/com/util/TimeElapsed' import {atoms as a, useTheme} from '#/alf' import {ActionsWrapper} from '#/components/dms/ActionsWrapper' +import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' import {RichText} from '../RichText' let MessageItem = ({ item, - next, - pending, }: { - item: ChatBskyConvoDefs.MessageView - next: - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | null - pending?: boolean + item: ConvoItem & {type: 'message' | 'pending-message'} }): React.ReactNode => { const t = useTheme() const {currentAccount} = useSession() - const isFromSelf = item.sender?.did === currentAccount?.did + const {message, nextMessage} = item + const isPending = item.type === 'pending-message' + + const isFromSelf = message.sender?.did === currentAccount?.did const isNextFromSelf = - ChatBskyConvoDefs.isMessageView(next) && - next.sender?.did === currentAccount?.did + ChatBskyConvoDefs.isMessageView(nextMessage) && + nextMessage.sender?.did === currentAccount?.did const isLastInGroup = useMemo(() => { - // TODO this means it's a placeholder. Let's figure out the right way to do this though! - if (item.id.length > 13) { + // if this message is pending, it means the next message is pending too + if (isPending && nextMessage) { return false } @@ -44,9 +48,9 @@ let MessageItem = ({ } // or, if there's a 3 minute gap between this message and the next - if (ChatBskyConvoDefs.isMessageView(next)) { - const thisDate = new Date(item.sentAt) - const nextDate = new Date(next.sentAt) + if (ChatBskyConvoDefs.isMessageView(nextMessage)) { + const thisDate = new Date(message.sentAt) + const nextDate = new Date(nextMessage.sentAt) const diff = nextDate.getTime() - thisDate.getTime() @@ -55,7 +59,7 @@ let MessageItem = ({ } return true - }, [item, next, isFromSelf, isNextFromSelf]) + }, [message, nextMessage, isFromSelf, isNextFromSelf, isPending]) const lastInGroupRef = useRef(isLastInGroup) if (lastInGroupRef.current !== isLastInGroup) { @@ -67,12 +71,12 @@ let MessageItem = ({ t.name === 'light' ? t.palette.primary_200 : t.palette.primary_800 const rt = useMemo(() => { - return new RichTextAPI({text: item.text, facets: item.facets}) - }, [item.text, item.facets]) + return new RichTextAPI({text: message.text, facets: message.facets}) + }, [message.text, message.facets]) return ( - + - + + {isLastInGroup && ( + + )} ) } @@ -117,16 +123,26 @@ MessageItem = React.memo(MessageItem) export {MessageItem} let MessageItemMetadata = ({ - message, - isLastInGroup, + item, style, }: { - message: ChatBskyConvoDefs.MessageView - isLastInGroup: boolean + item: ConvoItem & {type: 'message' | 'pending-message'} style: StyleProp }): React.ReactNode => { const t = useTheme() const {_} = useLingui() + const {message} = item + + const handleRetry = useCallback( + (e: GestureResponderEvent) => { + if (item.type === 'pending-message' && item.retry) { + e.preventDefault() + item.retry() + return false + } + }, + [item], + ) const relativeTimestamp = useCallback( (timestamp: string) => { @@ -169,25 +185,47 @@ let MessageItemMetadata = ({ [_], ) - if (!isLastInGroup) { - return null - } - return ( - - {({timeElapsed}) => ( - - {timeElapsed} - + + + {({timeElapsed}) => ( + + {timeElapsed} + + )} + + + {item.type === 'pending-message' && item.retry && ( + <> + {' '} + ·{' '} + + {_(msg`Failed to send`)} + {' '} + ·{' '} + + {_(msg`Retry`)} + + )} - + ) } diff --git a/src/components/dms/MessageReportDialog.tsx b/src/components/dms/MessageReportDialog.tsx index 6071312e..cc25732a 100644 --- a/src/components/dms/MessageReportDialog.tsx +++ b/src/components/dms/MessageReportDialog.tsx @@ -245,8 +245,12 @@ function PreviewMessage({message}: {message: ChatBskyConvoDefs.MessageView}) { /> diff --git a/src/screens/Messages/Conversation/MessageListError.tsx b/src/screens/Messages/Conversation/MessageListError.tsx index 38a63b0f..c6e246a3 100644 --- a/src/screens/Messages/Conversation/MessageListError.tsx +++ b/src/screens/Messages/Conversation/MessageListError.tsx @@ -26,7 +26,6 @@ export function MessageListError({ msg`This chat was disconnected due to a network error.`, ), [ConvoItemError.HistoryFailed]: _(msg`Failed to load past messages.`), - [ConvoItemError.PendingFailed]: _(msg`Failed to send message(s).`), }[item.code] }, [_, item.code]) diff --git a/src/screens/Messages/Conversation/MessagesList.tsx b/src/screens/Messages/Conversation/MessagesList.tsx index dac534cd..0c1bce17 100644 --- a/src/screens/Messages/Conversation/MessagesList.tsx +++ b/src/screens/Messages/Conversation/MessagesList.tsx @@ -35,13 +35,7 @@ function MaybeLoader({isLoading}: {isLoading: boolean}) { function renderItem({item}: {item: ConvoItem}) { if (item.type === 'message' || item.type === 'pending-message') { - return ( - - ) + return } else if (item.type === 'deleted-message') { return Deleted message } else if (item.type === 'error-recoverable') { diff --git a/src/state/messages/convo/agent.ts b/src/state/messages/convo/agent.ts index 6d59e136..420eff34 100644 --- a/src/state/messages/convo/agent.ts +++ b/src/state/messages/convo/agent.ts @@ -735,6 +735,8 @@ export class Convo { } } + private pendingFailed = false + async sendMessage(message: ChatBskyConvoSendMessage.InputSchema['message']) { // Ignore empty messages for now since they have no other purpose atm if (!message.text.trim()) return @@ -747,11 +749,9 @@ export class Convo { id: tempId, message, }) - // remove on each send, it might go through now without user having to click - this.footerItems.delete(ConvoItemError.PendingFailed) this.commit() - if (!this.isProcessingPendingMessages) { + if (!this.isProcessingPendingMessages && !this.pendingFailed) { this.processPendingMessages() } } @@ -805,16 +805,7 @@ export class Convo { this.commit() } catch (e: any) { logger.error(e, {context: `Convo: failed to send message`}) - this.footerItems.set(ConvoItemError.PendingFailed, { - type: 'error-recoverable', - key: ConvoItemError.PendingFailed, - code: ConvoItemError.PendingFailed, - retry: () => { - this.footerItems.delete(ConvoItemError.PendingFailed) - this.commit() - this.batchRetryPendingMessages() - }, - }) + this.pendingFailed = true this.commit() } finally { this.isProcessingPendingMessages = false @@ -868,16 +859,7 @@ export class Convo { ) } catch (e: any) { logger.error(e, {context: `Convo: failed to batch retry messages`}) - this.footerItems.set(ConvoItemError.PendingFailed, { - type: 'error-recoverable', - key: ConvoItemError.PendingFailed, - code: ConvoItemError.PendingFailed, - retry: () => { - this.footerItems.delete(ConvoItemError.PendingFailed) - this.commit() - this.batchRetryPendingMessages() - }, - }) + this.pendingFailed = true this.commit() } } @@ -958,6 +940,7 @@ export class Convo { key: m.id, message: { ...m.message, + $type: 'chat.bsky.convo.defs#messageView', id: nanoid(), rev: '__fake__', sentAt: new Date().toISOString(), @@ -968,6 +951,13 @@ export class Convo { sender: this.sender!, }, nextMessage: null, + retry: this.pendingFailed + ? () => { + this.pendingFailed = false + this.commit() + this.batchRetryPendingMessages() + } + : undefined, }) }) diff --git a/src/state/messages/convo/types.ts b/src/state/messages/convo/types.ts index 6ce4d40b..3fb0eb6a 100644 --- a/src/state/messages/convo/types.ts +++ b/src/state/messages/convo/types.ts @@ -35,10 +35,6 @@ export enum ConvoItemError { * Error fetching past messages */ HistoryFailed = 'historyFailed', - /** - * Error sending new message - */ - PendingFailed = 'pendingFailed', } export enum ConvoErrorCode { @@ -83,7 +79,7 @@ export type ConvoDispatch = export type ConvoItem = | { - type: 'message' | 'pending-message' + type: 'message' key: string message: ChatBskyConvoDefs.MessageView nextMessage: @@ -91,6 +87,19 @@ export type ConvoItem = | ChatBskyConvoDefs.DeletedMessageView | null } + | { + type: 'pending-message' + key: string + message: ChatBskyConvoDefs.MessageView + nextMessage: + | ChatBskyConvoDefs.MessageView + | ChatBskyConvoDefs.DeletedMessageView + | null + /** + * Retry sending the message. If present, the message is in a failed state. + */ + retry?: () => void + } | { type: 'deleted-message' key: string