Skip to content

Commit

Permalink
[🐴] Reduce amount that message sent date is shown (#4228)
Browse files Browse the repository at this point in the history
  • Loading branch information
mozzius authored and tkusano committed Oct 4, 2024
1 parent 3ab5190 commit af8e342
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 94 deletions.
80 changes: 80 additions & 0 deletions src/components/dms/DateDivider.tsx
Original file line number Diff line number Diff line change
@@ -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}
178 changes: 88 additions & 90 deletions src/components/dms/MessageItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ 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 {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 {isOnlyEmoji, RichText} from '../RichText'
import {DateDivider} from './DateDivider'
import {MessageItemEmbed} from './MessageItemEmbed'
import {localDateString} from './util'

let MessageItem = ({
item,
Expand All @@ -33,39 +35,57 @@ let MessageItem = ({
const t = useTheme()
const {currentAccount} = useSession()

const {message, nextMessage} = item
const {message, nextMessage, prevMessage} = item
const isPending = item.type === 'pending-message'

const isFromSelf = message.sender?.did === currentAccount?.did

const nextIsMessage = ChatBskyConvoDefs.isMessageView(nextMessage)

const isNextFromSelf =
ChatBskyConvoDefs.isMessageView(nextMessage) &&
nextMessage.sender?.did === currentAccount?.did
nextIsMessage && 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(() => {
// if this message is pending, it means the next message is pending too
if (isPending && nextMessage) {
return false
}

// if the next message is from a different sender, then it's the last in the group
if (isFromSelf ? !isNextFromSelf : isNextFromSelf) {
return true
}

// or, if there's a 3 minute gap between this message and the next
// or, if there's a 5 minute gap between this message and the next
if (ChatBskyConvoDefs.isMessageView(nextMessage)) {
const thisDate = new Date(message.sentAt)
const nextDate = new Date(nextMessage.sentAt)

const diff = nextDate.getTime() - thisDate.getTime()

// 3 minutes
return diff > 3 * 60 * 1000
// 5 minutes
return diff > 5 * 60 * 1000
}

return true
}, [message, nextMessage, isFromSelf, isNextFromSelf, isPending])
}, [message, nextMessage, isPending])

const lastInGroupRef = useRef(isLastInGroup)
if (lastInGroupRef.current !== isLastInGroup) {
Expand All @@ -80,52 +100,59 @@ let MessageItem = ({
}, [message.text, message.facets])

return (
<View style={[isFromSelf ? a.mr_md : a.ml_md]}>
<ActionsWrapper isFromSelf={isFromSelf} message={message}>
{AppBskyEmbedRecord.isView(message.embed) && (
<MessageItemEmbed embed={message.embed} />
)}
{rt.text.length > 0 && (
<View
style={
!isOnlyEmoji(message.text) && [
a.py_sm,
a.my_2xs,
a.rounded_md,
{
paddingLeft: 14,
paddingRight: 14,
backgroundColor: isFromSelf
? isPending
? pendingColor
: t.palette.primary_500
: t.palette.contrast_50,
borderRadius: 17,
},
isFromSelf ? a.self_end : a.self_start,
isFromSelf
? {borderBottomRightRadius: isLastInGroup ? 2 : 17}
: {borderBottomLeftRadius: isLastInGroup ? 2 : 17},
]
}>
<RichText
value={rt}
style={[a.text_md, isFromSelf && {color: t.palette.white}]}
interactiveStyle={a.underline}
enableTags
emojiMultiplier={3}
/>
</View>
)}
</ActionsWrapper>
<>
{isNewDay && <DateDivider date={message.sentAt} />}
<View
style={[
isFromSelf ? a.mr_md : a.ml_md,
nextIsMessage && !isNextFromSameSender && a.mb_md,
]}>
<ActionsWrapper isFromSelf={isFromSelf} message={message}>
{AppBskyEmbedRecord.isView(message.embed) && (
<MessageItemEmbed embed={message.embed} />
)}
{rt.text.length > 0 && (
<View
style={
!isOnlyEmoji(message.text) && [
a.py_sm,
a.my_2xs,
a.rounded_md,
{
paddingLeft: 14,
paddingRight: 14,
backgroundColor: isFromSelf
? isPending
? pendingColor
: t.palette.primary_500
: t.palette.contrast_50,
borderRadius: 17,
},
isFromSelf ? a.self_end : a.self_start,
isFromSelf
? {borderBottomRightRadius: needsTail ? 2 : 17}
: {borderBottomLeftRadius: needsTail ? 2 : 17},
]
}>
<RichText
value={rt}
style={[a.text_md, isFromSelf && {color: t.palette.white}]}
interactiveStyle={a.underline}
enableTags
emojiMultiplier={3}
/>
</View>
)}
</ActionsWrapper>

{isLastInGroup && (
<MessageItemMetadata
item={item}
style={isFromSelf ? a.text_right : a.text_left}
/>
)}
</View>
{isLastInGroup && (
<MessageItemMetadata
item={item}
style={isFromSelf ? a.text_right : a.text_left}
/>
)}
</View>
</>
)
}
MessageItem = React.memo(MessageItem)
Expand Down Expand Up @@ -165,31 +192,12 @@ let MessageItemMetadata = ({

const diff = now.getTime() - date.getTime()

// if under 1 minute
if (diff < 1000 * 60) {
// if under 30 seconds
if (diff < 1000 * 30) {
return _(msg`Now`)
}

// if in the last day
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',
})
return time
},
[_],
)
Expand Down Expand Up @@ -242,15 +250,5 @@ let MessageItemMetadata = ({
</Text>
)
}

MessageItemMetadata = React.memo(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 change: 1 addition & 0 deletions src/components/dms/ReportDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ function PreviewMessage({message}: {message: ChatBskyConvoDefs.MessageView}) {
message,
key: '',
nextMessage: null,
prevMessage: null,
}}
style={[a.text_left, a.mb_0]}
/>
Expand Down
9 changes: 9 additions & 0 deletions src/components/dms/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,12 @@ export function canBeMessaged(profile: AppBskyActorDefs.ProfileView) {
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}`
}
Loading

0 comments on commit af8e342

Please sign in to comment.