Skip to content

Commit

Permalink
add date divider between days
Browse files Browse the repository at this point in the history
  • Loading branch information
mozzius committed May 29, 2024
1 parent 86fecd5 commit 7dbe6aa
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 81 deletions.
50 changes: 50 additions & 0 deletions src/components/dms/DateDivider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react'
import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'

import {atoms as a, useTheme} from '#/alf'
import {Text} from '../Typography'
import {localDateString} from './util'

let DateDivider = ({date: dateStr}: {date: string}): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()

let display: string

const date = new Date(dateStr)

const today = new Date()
const yesterday = new Date()
yesterday.setDate(today.getDate() - 1)

if (localDateString(today) === localDateString(date)) {
display = _(msg`Today`)
} else if (localDateString(yesterday) === localDateString(date)) {
display = _(msg`Yesterday`)
} else {
display = new Intl.DateTimeFormat(undefined, {
day: 'numeric',
month: 'numeric',
year: 'numeric',
}).format(date)
}

return (
<View style={[a.w_full, a.relative, a.my_lg, a.flex_row, a.justify_center]}>
<View
style={[
a.border_b,
t.atoms.border_contrast_low,
a.mx_lg,
a.absolute,
{top: '50%', left: 0, right: 0},
]}
/>
<Text style={[a.text_center, t.atoms.bg, a.px_md]}>{display}</Text>
</View>
)
}
DateDivider = React.memo(DateDivider)
export {DateDivider}
142 changes: 65 additions & 77 deletions src/components/dms/MessageItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
import {InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography'
import {RichText} from '../RichText'
import {DateDivider} from './DateDivider'
import {localDateString} from './util'

let MessageItem = ({
item,
Expand All @@ -27,7 +29,7 @@ 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
Expand All @@ -39,6 +41,17 @@ let MessageItem = ({

const isNextFromSameSender = isNextFromSelf === isFromSelf

const isNewDay = useMemo(() => {
// TODO: figure out how we can show this for when we're at the start
// of the conversation
if (!prevMessage) return false

const thisDate = new Date(message.sentAt)
const prevDate = new Date(prevMessage.sentAt)

return localDateString(thisDate) !== localDateString(prevDate)
}, [message, prevMessage])

const isLastInGroup = useMemo(() => {
// if this message is pending, it means the next message is pending too
if (isPending && nextMessage) {
Expand Down Expand Up @@ -73,52 +86,56 @@ let MessageItem = ({
}, [message.text, message.facets])

return (
<View
style={[
isFromSelf ? a.mr_md : a.ml_md,
nextIsMessage && !isNextFromSameSender && a.mb_md,
]}>
<ActionsWrapper isFromSelf={isFromSelf} message={message}>
<View
style={[
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
? {borderBottomRightRadius: isLastInGroup ? 2 : 17}
: {borderBottomLeftRadius: isLastInGroup ? 2 : 17},
]}>
<RichText
value={rt}
<>
{isNewDay && <DateDivider date={message.sentAt} />}
<View
style={[
isFromSelf ? a.mr_md : a.ml_md,
nextIsMessage && !isNextFromSameSender && a.mb_md,
]}>
<ActionsWrapper isFromSelf={isFromSelf} message={message}>
<View
style={[
a.text_md,
a.leading_snug,
isFromSelf && {color: t.palette.white},
isPending && t.name !== 'light' && {color: t.palette.primary_300},
]}
interactiveStyle={a.underline}
enableTags
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
? {borderBottomRightRadius: isLastInGroup ? 2 : 17}
: {borderBottomLeftRadius: isLastInGroup ? 2 : 17},
]}>
<RichText
value={rt}
style={[
a.text_md,
a.leading_snug,
isFromSelf && {color: t.palette.white},
isPending &&
t.name !== 'light' && {color: t.palette.primary_300},
]}
interactiveStyle={a.underline}
enableTags
/>
</View>
</ActionsWrapper>

{isLastInGroup && (
<MessageItemMetadata
item={item}
style={isFromSelf ? a.text_right : a.text_left}
/>
</View>
</ActionsWrapper>

{isLastInGroup && (
<MessageItemMetadata
item={item}
style={isFromSelf ? a.text_right : a.text_left}
/>
)}
</View>
)}
</View>
</>
)
}
MessageItem = React.memo(MessageItem)
Expand Down Expand Up @@ -158,31 +175,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 new Intl.DateTimeFormat(undefined, {
hour: 'numeric',
minute: 'numeric',
day: 'numeric',
month: 'numeric',
year: 'numeric',
}).format(date)
return time
},
[_],
)
Expand Down Expand Up @@ -235,15 +233,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}`
}
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}`
}
23 changes: 19 additions & 4 deletions src/state/messages/convo/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -972,13 +972,15 @@ export class Convo {
key: m.id,
message: m,
nextMessage: null,
prevMessage: null,
})
} else if (ChatBskyConvoDefs.isDeletedMessageView(m)) {
items.unshift({
type: 'deleted-message',
key: m.id,
message: m,
nextMessage: null,
prevMessage: null,
})
}
})
Expand All @@ -1001,13 +1003,15 @@ export class Convo {
key: m.id,
message: m,
nextMessage: null,
prevMessage: null,
})
} else if (ChatBskyConvoDefs.isDeletedMessageView(m)) {
items.push({
type: 'deleted-message',
key: m.id,
message: m,
nextMessage: null,
prevMessage: null,
})
}
})
Expand All @@ -1029,6 +1033,7 @@ export class Convo {
sender: this.sender!,
},
nextMessage: null,
prevMessage: null,
failed: this.pendingMessageFailure !== null,
retry:
this.pendingMessageFailure === 'recoverable'
Expand Down Expand Up @@ -1059,29 +1064,39 @@ export class Convo {
})
.map((item, i, arr) => {
let nextMessage = null
let prevMessage = null
const isMessage = isConvoItemMessage(item)

if (isMessage) {
if (
isMessage &&
(ChatBskyConvoDefs.isMessageView(item.message) ||
ChatBskyConvoDefs.isDeletedMessageView(item.message))
ChatBskyConvoDefs.isMessageView(item.message) ||
ChatBskyConvoDefs.isDeletedMessageView(item.message)
) {
const next = arr[i + 1]

if (
isConvoItemMessage(next) &&
next &&
(ChatBskyConvoDefs.isMessageView(next.message) ||
ChatBskyConvoDefs.isDeletedMessageView(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 {
...item,
nextMessage,
prevMessage,
}
}

Expand Down
12 changes: 12 additions & 0 deletions src/state/messages/convo/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ export type ConvoItem =
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
prevMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
}
| {
type: 'pending-message'
Expand All @@ -96,6 +100,10 @@ export type ConvoItem =
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
prevMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
failed: boolean
/**
* Retry sending the message. If present, the message is in a failed state.
Expand All @@ -110,6 +118,10 @@ export type ConvoItem =
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
prevMessage:
| ChatBskyConvoDefs.MessageView
| ChatBskyConvoDefs.DeletedMessageView
| null
}
| {
type: 'error'
Expand Down

0 comments on commit 7dbe6aa

Please sign in to comment.