import React, {useCallback, useMemo, useRef} from 'react' import { GestureResponderEvent, LayoutAnimation, StyleProp, TextStyle, View, } from 'react-native' import {ChatBskyConvoDefs, RichText as RichTextAPI} from '@atproto/api' import {msg} from '@lingui/macro' 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 {atoms as a, useTheme} from '#/alf' import {ActionsWrapper} from '#/components/dms/ActionsWrapper' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' import {RichText} from '../RichText' let MessageItem = ({ item, }: { item: ConvoItem & {type: 'message' | 'pending-message'} }): React.ReactNode => { const t = useTheme() const {currentAccount} = useSession() const {message, nextMessage} = item const isPending = item.type === 'pending-message' const isFromSelf = message.sender?.did === currentAccount?.did const isNextFromSelf = ChatBskyConvoDefs.isMessageView(nextMessage) && nextMessage.sender?.did === currentAccount?.did 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 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 } return true }, [message, nextMessage, isFromSelf, isNextFromSelf, isPending]) const lastInGroupRef = useRef(isLastInGroup) if (lastInGroupRef.current !== isLastInGroup) { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) lastInGroupRef.current = isLastInGroup } const pendingColor = t.name === 'light' ? t.palette.primary_200 : t.palette.primary_800 const rt = useMemo(() => { return new RichTextAPI({text: message.text, facets: message.facets}) }, [message.text, message.facets]) return ( {isLastInGroup && ( )} ) } MessageItem = React.memo(MessageItem) export {MessageItem} let MessageItemMetadata = ({ item, style, }: { item: ConvoItem & {type: 'message' | 'pending-message'} style: StyleProp }): React.ReactNode => { const t = useTheme() const {_} = useLingui() const {message} = item const handleRetry = useCallback( (e: GestureResponderEvent) => { if (item.type === 'pending-message' && item.retry) { e.preventDefault() item.retry() return false } }, [item], ) const relativeTimestamp = useCallback( (timestamp: string) => { const date = new Date(timestamp) const now = new Date() const time = new Intl.DateTimeFormat(undefined, { hour: 'numeric', minute: 'numeric', }).format(date) const diff = now.getTime() - date.getTime() // if under 1 minute if (diff < 1000 * 60) { 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 ( {({timeElapsed}) => ( {timeElapsed} )} {item.type === 'pending-message' && item.retry && ( <> {' '} ·{' '} {_(msg`Failed to send`)} {' '} ·{' '} {_(msg`Retry`)} )} ) } 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}` }