diff options
author | Eric Bailey <git@esb.lol> | 2024-05-15 11:45:18 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-15 11:45:18 -0500 |
commit | 04aea931925099f006e80d1472b4d5cc6d498fcb (patch) | |
tree | 45669b7edfdfcc2fcc2172da42d75359328a7070 /src | |
parent | ed8922281af46071375f47112fbb37ba5b7d578b (diff) | |
download | voidsky-04aea931925099f006e80d1472b4d5cc6d498fcb.tar.zst |
[🐴] Better retry styling (#4032)
* 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
Diffstat (limited to 'src')
-rw-r--r-- | src/components/dms/MessageItem.tsx | 136 | ||||
-rw-r--r-- | src/components/dms/MessageReportDialog.tsx | 8 | ||||
-rw-r--r-- | src/screens/Messages/Conversation/MessageListError.tsx | 1 | ||||
-rw-r--r-- | src/screens/Messages/Conversation/MessagesList.tsx | 8 | ||||
-rw-r--r-- | src/state/messages/convo/agent.ts | 36 | ||||
-rw-r--r-- | 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 f8ab85188..cafd7ca5a 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 ( <View> - <ActionsWrapper isFromSelf={isFromSelf} message={item}> + <ActionsWrapper isFromSelf={isFromSelf} message={message}> <View style={[ a.py_sm, @@ -82,7 +86,7 @@ let MessageItem = ({ paddingLeft: 14, paddingRight: 14, backgroundColor: isFromSelf - ? pending + ? isPending ? pendingColor : t.palette.primary_500 : t.palette.contrast_50, @@ -98,18 +102,20 @@ let MessageItem = ({ a.text_md, a.leading_snug, isFromSelf && {color: t.palette.white}, - pending && t.name !== 'light' && {color: t.palette.primary_300}, + isPending && t.name !== 'light' && {color: t.palette.primary_300}, ]} interactiveStyle={a.underline} enableTags /> </View> </ActionsWrapper> - <MessageItemMetadata - message={item} - isLastInGroup={isLastInGroup} - style={isFromSelf ? a.text_right : a.text_left} - /> + + {isLastInGroup && ( + <MessageItemMetadata + item={item} + style={isFromSelf ? a.text_right : a.text_left} + /> + )} </View> ) } @@ -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<TextStyle> }): 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 timestamp={message.sentAt} timeToString={relativeTimestamp}> - {({timeElapsed}) => ( - <Text - style={[ - t.atoms.text_contrast_medium, - a.text_xs, - a.mt_2xs, - a.mb_lg, - style, - ]}> - {timeElapsed} - </Text> + <Text + style={[ + a.text_xs, + a.mt_2xs, + a.mb_lg, + t.atoms.text_contrast_medium, + style, + ]}> + <TimeElapsed timestamp={message.sentAt} timeToString={relativeTimestamp}> + {({timeElapsed}) => ( + <Text style={[a.text_xs, t.atoms.text_contrast_medium]}> + {timeElapsed} + </Text> + )} + </TimeElapsed> + + {item.type === 'pending-message' && item.retry && ( + <> + {' '} + ·{' '} + <Text + style={[ + a.text_xs, + { + color: t.palette.negative_400, + }, + ]}> + {_(msg`Failed to send`)} + </Text>{' '} + ·{' '} + <InlineLinkText + label={_(msg`Click to retry failed message`)} + to="#" + onPress={handleRetry} + style={[a.text_xs]}> + {_(msg`Retry`)} + </InlineLinkText> + </> )} - </TimeElapsed> + </Text> ) } diff --git a/src/components/dms/MessageReportDialog.tsx b/src/components/dms/MessageReportDialog.tsx index 6071312e9..cc25732af 100644 --- a/src/components/dms/MessageReportDialog.tsx +++ b/src/components/dms/MessageReportDialog.tsx @@ -245,8 +245,12 @@ function PreviewMessage({message}: {message: ChatBskyConvoDefs.MessageView}) { /> </View> <MessageItemMetadata - message={message} - isLastInGroup + item={{ + type: 'message', + message, + key: '', + nextMessage: null, + }} style={[a.text_left, a.mb_0]} /> </View> diff --git a/src/screens/Messages/Conversation/MessageListError.tsx b/src/screens/Messages/Conversation/MessageListError.tsx index 38a63b0f1..c6e246a3f 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 dac534cd4..0c1bce17d 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 ( - <MessageItem - item={item.message} - next={item.nextMessage} - pending={item.type === 'pending-message'} - /> - ) + return <MessageItem item={item} /> } else if (item.type === 'deleted-message') { return <Text>Deleted message</Text> } else if (item.type === 'error-recoverable') { diff --git a/src/state/messages/convo/agent.ts b/src/state/messages/convo/agent.ts index 6d59e1369..420eff349 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 6ce4d40bd..3fb0eb6ad 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,13 +79,26 @@ export type ConvoDispatch = export type ConvoItem = | { - type: 'message' | 'pending-message' + type: 'message' + key: string + message: ChatBskyConvoDefs.MessageView + nextMessage: + | ChatBskyConvoDefs.MessageView + | 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' |