[🐴] Reduce amount that message sent date is shown (#4228)
This commit is contained in:
@@ -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}
|
||||||
@@ -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}`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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}`
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
Reference in New Issue
Block a user