[🐴] Reduce amount that message sent date is shown (#4228)

This commit is contained in:
Samuel Newman
2024-10-03 18:19:38 +03:00
committed by GitHub
parent 523a9a48c2
commit 7e79c7f768
6 changed files with 209 additions and 94 deletions
+80
View File
@@ -0,0 +1,80 @@
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {subDays} from 'date-fns'
import {atoms as a, useTheme} from '#/alf'
import {Text} from '../Typography'
import {localDateString} from './util'
const timeFormatter = new Intl.DateTimeFormat(undefined, {
hour: 'numeric',
minute: 'numeric',
})
const weekdayFormatter = new Intl.DateTimeFormat(undefined, {
weekday: 'long',
})
const longDateFormatter = new Intl.DateTimeFormat(undefined, {
weekday: 'short',
month: 'long',
day: 'numeric',
})
const longDateFormatterWithYear = new Intl.DateTimeFormat(undefined, {
weekday: 'short',
month: 'long',
day: 'numeric',
year: 'numeric',
})
let DateDivider = ({date: dateStr}: {date: string}): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()
let date: string
const time = timeFormatter.format(new Date(dateStr))
const timestamp = new Date(dateStr)
const today = new Date()
const yesterday = subDays(today, 1)
const oneWeekAgo = subDays(today, 7)
if (localDateString(today) === localDateString(timestamp)) {
date = _(msg`Today`)
} else if (localDateString(yesterday) === localDateString(timestamp)) {
date = _(msg`Yesterday`)
} else {
if (timestamp < oneWeekAgo) {
if (timestamp.getFullYear() === today.getFullYear()) {
date = longDateFormatter.format(timestamp)
} else {
date = longDateFormatterWithYear.format(timestamp)
}
} else {
date = weekdayFormatter.format(timestamp)
}
}
return (
<View style={[a.w_full, a.my_lg]}>
<Text
style={[
a.text_xs,
a.text_center,
t.atoms.bg,
t.atoms.text_contrast_medium,
a.px_md,
]}>
<Trans>
<Text style={[a.text_xs, t.atoms.text_contrast_medium, a.font_bold]}>
{date}
</Text>{' '}
at {time}
</Trans>
</Text>
</View>
)
}
DateDivider = React.memo(DateDivider)
export {DateDivider}
+88 -90
View File
@@ -17,13 +17,15 @@ import {useLingui} from '@lingui/react'
import {ConvoItem} from '#/state/messages/convo/types' import {ConvoItem} from '#/state/messages/convo/types'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {TimeElapsed} from 'view/com/util/TimeElapsed' import {TimeElapsed} from '#/view/com/util/TimeElapsed'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {ActionsWrapper} from '#/components/dms/ActionsWrapper' import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
import {InlineLinkText} from '#/components/Link' import {InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {isOnlyEmoji, RichText} from '../RichText' import {isOnlyEmoji, RichText} from '../RichText'
import {DateDivider} from './DateDivider'
import {MessageItemEmbed} from './MessageItemEmbed' import {MessageItemEmbed} from './MessageItemEmbed'
import {localDateString} from './util'
let MessageItem = ({ let MessageItem = ({
item, item,
@@ -33,14 +35,37 @@ let MessageItem = ({
const t = useTheme() const t = useTheme()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {message, nextMessage} = item const {message, nextMessage, prevMessage} = item
const isPending = item.type === 'pending-message' const isPending = item.type === 'pending-message'
const isFromSelf = message.sender?.did === currentAccount?.did const isFromSelf = message.sender?.did === currentAccount?.did
const nextIsMessage = ChatBskyConvoDefs.isMessageView(nextMessage)
const isNextFromSelf = const isNextFromSelf =
ChatBskyConvoDefs.isMessageView(nextMessage) && nextIsMessage && nextMessage.sender?.did === currentAccount?.did
nextMessage.sender?.did === currentAccount?.did
const isNextFromSameSender = isNextFromSelf === isFromSelf
const isNewDay = useMemo(() => {
if (!prevMessage) return true
const thisDate = new Date(message.sentAt)
const prevDate = new Date(prevMessage.sentAt)
return localDateString(thisDate) !== localDateString(prevDate)
}, [message, prevMessage])
const isLastMessageOfDay = useMemo(() => {
if (!nextMessage || !nextIsMessage) return true
const thisDate = new Date(message.sentAt)
const prevDate = new Date(nextMessage.sentAt)
return localDateString(thisDate) !== localDateString(prevDate)
}, [message.sentAt, nextIsMessage, nextMessage])
const needsTail = isLastMessageOfDay || !isNextFromSameSender
const isLastInGroup = useMemo(() => { const isLastInGroup = useMemo(() => {
// if this message is pending, it means the next message is pending too // if this message is pending, it means the next message is pending too
@@ -48,24 +73,19 @@ let MessageItem = ({
return false return false
} }
// if the next message is from a different sender, then it's the last in the group // or, if there's a 5 minute gap between this message and the next
if (isFromSelf ? !isNextFromSelf : isNextFromSelf) {
return true
}
// or, if there's a 3 minute gap between this message and the next
if (ChatBskyConvoDefs.isMessageView(nextMessage)) { if (ChatBskyConvoDefs.isMessageView(nextMessage)) {
const thisDate = new Date(message.sentAt) const thisDate = new Date(message.sentAt)
const nextDate = new Date(nextMessage.sentAt) const nextDate = new Date(nextMessage.sentAt)
const diff = nextDate.getTime() - thisDate.getTime() const diff = nextDate.getTime() - thisDate.getTime()
// 3 minutes // 5 minutes
return diff > 3 * 60 * 1000 return diff > 5 * 60 * 1000
} }
return true return true
}, [message, nextMessage, isFromSelf, isNextFromSelf, isPending]) }, [message, nextMessage, isPending])
const lastInGroupRef = useRef(isLastInGroup) const lastInGroupRef = useRef(isLastInGroup)
if (lastInGroupRef.current !== isLastInGroup) { if (lastInGroupRef.current !== isLastInGroup) {
@@ -80,52 +100,59 @@ let MessageItem = ({
}, [message.text, message.facets]) }, [message.text, message.facets])
return ( return (
<View style={[isFromSelf ? a.mr_md : a.ml_md]}> <>
<ActionsWrapper isFromSelf={isFromSelf} message={message}> {isNewDay && <DateDivider date={message.sentAt} />}
{AppBskyEmbedRecord.isView(message.embed) && ( <View
<MessageItemEmbed embed={message.embed} /> style={[
)} isFromSelf ? a.mr_md : a.ml_md,
{rt.text.length > 0 && ( nextIsMessage && !isNextFromSameSender && a.mb_md,
<View ]}>
style={ <ActionsWrapper isFromSelf={isFromSelf} message={message}>
!isOnlyEmoji(message.text) && [ {AppBskyEmbedRecord.isView(message.embed) && (
a.py_sm, <MessageItemEmbed embed={message.embed} />
a.my_2xs, )}
a.rounded_md, {rt.text.length > 0 && (
{ <View
paddingLeft: 14, style={
paddingRight: 14, !isOnlyEmoji(message.text) && [
backgroundColor: isFromSelf a.py_sm,
? isPending a.my_2xs,
? pendingColor a.rounded_md,
: t.palette.primary_500 {
: t.palette.contrast_50, paddingLeft: 14,
borderRadius: 17, paddingRight: 14,
}, backgroundColor: isFromSelf
isFromSelf ? a.self_end : a.self_start, ? isPending
isFromSelf ? pendingColor
? {borderBottomRightRadius: isLastInGroup ? 2 : 17} : t.palette.primary_500
: {borderBottomLeftRadius: isLastInGroup ? 2 : 17}, : t.palette.contrast_50,
] borderRadius: 17,
}> },
<RichText isFromSelf ? a.self_end : a.self_start,
value={rt} isFromSelf
style={[a.text_md, isFromSelf && {color: t.palette.white}]} ? {borderBottomRightRadius: needsTail ? 2 : 17}
interactiveStyle={a.underline} : {borderBottomLeftRadius: needsTail ? 2 : 17},
enableTags ]
emojiMultiplier={3} }>
/> <RichText
</View> value={rt}
)} style={[a.text_md, isFromSelf && {color: t.palette.white}]}
</ActionsWrapper> interactiveStyle={a.underline}
enableTags
emojiMultiplier={3}
/>
</View>
)}
</ActionsWrapper>
{isLastInGroup && ( {isLastInGroup && (
<MessageItemMetadata <MessageItemMetadata
item={item} item={item}
style={isFromSelf ? a.text_right : a.text_left} style={isFromSelf ? a.text_right : a.text_left}
/> />
)} )}
</View> </View>
</>
) )
} }
MessageItem = React.memo(MessageItem) MessageItem = React.memo(MessageItem)
@@ -165,31 +192,12 @@ let MessageItemMetadata = ({
const diff = now.getTime() - date.getTime() const diff = now.getTime() - date.getTime()
// if under 1 minute // if under 30 seconds
if (diff < 1000 * 60) { if (diff < 1000 * 30) {
return _(msg`Now`) return _(msg`Now`)
} }
// if in the last day return time
if (localDateString(now) === localDateString(date)) {
return time
}
// if yesterday
const yesterday = new Date(now)
yesterday.setDate(yesterday.getDate() - 1)
if (localDateString(yesterday) === localDateString(date)) {
return _(msg`Yesterday, ${time}`)
}
return i18n.date(date, {
hour: 'numeric',
minute: 'numeric',
day: 'numeric',
month: 'numeric',
year: 'numeric',
})
}, },
[_], [_],
) )
@@ -242,15 +250,5 @@ let MessageItemMetadata = ({
</Text> </Text>
) )
} }
MessageItemMetadata = React.memo(MessageItemMetadata) MessageItemMetadata = React.memo(MessageItemMetadata)
export {MessageItemMetadata} export {MessageItemMetadata}
function localDateString(date: Date) {
// can't use toISOString because it should be in local time
const mm = date.getMonth()
const dd = date.getDate()
const yyyy = date.getFullYear()
// not padding with 0s because it's not necessary, it's just used for comparison
return `${yyyy}-${mm}-${dd}`
}
+1
View File
@@ -277,6 +277,7 @@ function PreviewMessage({message}: {message: ChatBskyConvoDefs.MessageView}) {
message, message,
key: '', key: '',
nextMessage: null, nextMessage: null,
prevMessage: null,
}} }}
style={[a.text_left, a.mb_0]} style={[a.text_left, a.mb_0]}
/> />
+9
View File
@@ -16,3 +16,12 @@ export function canBeMessaged(profile: AppBskyActorDefs.ProfileView) {
return false return false
} }
} }
export function localDateString(date: Date) {
// can't use toISOString because it should be in local time
const mm = date.getMonth()
const dd = date.getDate()
const yyyy = date.getFullYear()
// not padding with 0s because it's not necessary, it's just used for comparison
return `${yyyy}-${mm}-${dd}`
}
+19 -4
View File
@@ -972,6 +972,7 @@ export class Convo {
key: m.id, key: m.id,
message: m, message: m,
nextMessage: null, nextMessage: null,
prevMessage: null,
}) })
} else if (ChatBskyConvoDefs.isDeletedMessageView(m)) { } else if (ChatBskyConvoDefs.isDeletedMessageView(m)) {
items.unshift({ items.unshift({
@@ -979,6 +980,7 @@ export class Convo {
key: m.id, key: m.id,
message: m, message: m,
nextMessage: null, nextMessage: null,
prevMessage: null,
}) })
} }
}) })
@@ -1001,6 +1003,7 @@ export class Convo {
key: m.id, key: m.id,
message: m, message: m,
nextMessage: null, nextMessage: null,
prevMessage: null,
}) })
} else if (ChatBskyConvoDefs.isDeletedMessageView(m)) { } else if (ChatBskyConvoDefs.isDeletedMessageView(m)) {
items.push({ items.push({
@@ -1008,6 +1011,7 @@ export class Convo {
key: m.id, key: m.id,
message: m, message: m,
nextMessage: null, nextMessage: null,
prevMessage: null,
}) })
} }
}) })
@@ -1030,6 +1034,7 @@ export class Convo {
sender: this.sender!, sender: this.sender!,
}, },
nextMessage: null, nextMessage: null,
prevMessage: null,
failed: this.pendingMessageFailure !== null, failed: this.pendingMessageFailure !== null,
retry: retry:
this.pendingMessageFailure === 'recoverable' this.pendingMessageFailure === 'recoverable'
@@ -1060,29 +1065,39 @@ export class Convo {
}) })
.map((item, i, arr) => { .map((item, i, arr) => {
let nextMessage = null let nextMessage = null
let prevMessage = null
const isMessage = isConvoItemMessage(item) const isMessage = isConvoItemMessage(item)
if (isMessage) { if (isMessage) {
if ( if (
isMessage && ChatBskyConvoDefs.isMessageView(item.message) ||
(ChatBskyConvoDefs.isMessageView(item.message) || ChatBskyConvoDefs.isDeletedMessageView(item.message)
ChatBskyConvoDefs.isDeletedMessageView(item.message))
) { ) {
const next = arr[i + 1] const next = arr[i + 1]
if ( if (
isConvoItemMessage(next) && isConvoItemMessage(next) &&
next &&
(ChatBskyConvoDefs.isMessageView(next.message) || (ChatBskyConvoDefs.isMessageView(next.message) ||
ChatBskyConvoDefs.isDeletedMessageView(next.message)) ChatBskyConvoDefs.isDeletedMessageView(next.message))
) { ) {
nextMessage = next.message nextMessage = next.message
} }
const prev = arr[i - 1]
if (
isConvoItemMessage(prev) &&
(ChatBskyConvoDefs.isMessageView(prev.message) ||
ChatBskyConvoDefs.isDeletedMessageView(prev.message))
) {
prevMessage = prev.message
}
} }
return { return {
...item, ...item,
nextMessage, nextMessage,
prevMessage,
} }
} }
+12
View File
@@ -87,6 +87,10 @@ export type ConvoItem =
| ChatBskyConvoDefs.MessageView | ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView | ChatBskyConvoDefs.DeletedMessageView
| null | null
prevMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
} }
| { | {
type: 'pending-message' type: 'pending-message'
@@ -96,6 +100,10 @@ export type ConvoItem =
| ChatBskyConvoDefs.MessageView | ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView | ChatBskyConvoDefs.DeletedMessageView
| null | null
prevMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
failed: boolean failed: boolean
/** /**
* Retry sending the message. If present, the message is in a failed state. * Retry sending the message. If present, the message is in a failed state.
@@ -110,6 +118,10 @@ export type ConvoItem =
| ChatBskyConvoDefs.MessageView | ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView | ChatBskyConvoDefs.DeletedMessageView
| null | null
prevMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
} }
| { | {
type: 'error' type: 'error'