[🐴] 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
This commit is contained in:
parent
ed8922281a
commit
04aea93192
6 changed files with 121 additions and 87 deletions
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue