From 7dbe6aa5defeecbd819e5377ef21765fa0bac288 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 29 May 2024 13:28:48 +0300 Subject: [PATCH] add date divider between days --- src/components/dms/DateDivider.tsx | 50 ++++++++++ src/components/dms/MessageItem.tsx | 142 +++++++++++++---------------- src/components/dms/util.ts | 9 ++ src/state/messages/convo/agent.ts | 23 ++++- src/state/messages/convo/types.ts | 12 +++ 5 files changed, 155 insertions(+), 81 deletions(-) create mode 100644 src/components/dms/DateDivider.tsx diff --git a/src/components/dms/DateDivider.tsx b/src/components/dms/DateDivider.tsx new file mode 100644 index 0000000000..b1f38b1271 --- /dev/null +++ b/src/components/dms/DateDivider.tsx @@ -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 ( + + + {display} + + ) +} +DateDivider = React.memo(DateDivider) +export {DateDivider} diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx index ab87cac906..32c086e711 100644 --- a/src/components/dms/MessageItem.tsx +++ b/src/components/dms/MessageItem.tsx @@ -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, @@ -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 @@ -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) { @@ -73,52 +86,56 @@ let MessageItem = ({ }, [message.text, message.facets]) return ( - - - - + {isNewDay && } + + + + + + + + {isLastInGroup && ( + - - - - {isLastInGroup && ( - - )} - + )} + + ) } MessageItem = React.memo(MessageItem) @@ -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 }, [_], ) @@ -235,15 +233,5 @@ let MessageItemMetadata = ({ ) } - 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}` -} diff --git a/src/components/dms/util.ts b/src/components/dms/util.ts index 5952b9acf4..003532d0c6 100644 --- a/src/components/dms/util.ts +++ b/src/components/dms/util.ts @@ -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}` +} diff --git a/src/state/messages/convo/agent.ts b/src/state/messages/convo/agent.ts index a0355ab07d..e5eec9dde3 100644 --- a/src/state/messages/convo/agent.ts +++ b/src/state/messages/convo/agent.ts @@ -972,6 +972,7 @@ export class Convo { key: m.id, message: m, nextMessage: null, + prevMessage: null, }) } else if (ChatBskyConvoDefs.isDeletedMessageView(m)) { items.unshift({ @@ -979,6 +980,7 @@ export class Convo { key: m.id, message: m, nextMessage: null, + prevMessage: null, }) } }) @@ -1001,6 +1003,7 @@ export class Convo { key: m.id, message: m, nextMessage: null, + prevMessage: null, }) } else if (ChatBskyConvoDefs.isDeletedMessageView(m)) { items.push({ @@ -1008,6 +1011,7 @@ export class Convo { key: m.id, message: m, nextMessage: null, + prevMessage: null, }) } }) @@ -1029,6 +1033,7 @@ export class Convo { sender: this.sender!, }, nextMessage: null, + prevMessage: null, failed: this.pendingMessageFailure !== null, retry: this.pendingMessageFailure === 'recoverable' @@ -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, } } diff --git a/src/state/messages/convo/types.ts b/src/state/messages/convo/types.ts index 53e205e211..21772262ea 100644 --- a/src/state/messages/convo/types.ts +++ b/src/state/messages/convo/types.ts @@ -87,6 +87,10 @@ export type ConvoItem = | ChatBskyConvoDefs.MessageView | ChatBskyConvoDefs.DeletedMessageView | null + prevMessage: + | ChatBskyConvoDefs.MessageView + | ChatBskyConvoDefs.DeletedMessageView + | null } | { type: 'pending-message' @@ -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. @@ -110,6 +118,10 @@ export type ConvoItem = | ChatBskyConvoDefs.MessageView | ChatBskyConvoDefs.DeletedMessageView | null + prevMessage: + | ChatBskyConvoDefs.MessageView + | ChatBskyConvoDefs.DeletedMessageView + | null } | { type: 'error'