import React, {useCallback, useMemo} from 'react' import {GestureResponderEvent, StyleProp, TextStyle, View} from 'react-native' import { AppBskyEmbedRecord, ChatBskyConvoDefs, RichText as RichTextAPI, } from '@atproto/api' import {I18n} from '@lingui/core' 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 {isOnlyEmoji} from '#/alf/typography' 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 {MessageItemEmbed} from './MessageItemEmbed' import {localDateString} from './util' let MessageItem = ({ item, }: { item: ConvoItem & {type: 'message' | 'pending-message'} }): React.ReactNode => { const t = useTheme() const {currentAccount} = useSession() 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 = 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 } // 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() // 5 minutes return diff > 5 * 60 * 1000 } return true }, [message, nextMessage, isPending]) const pendingColor = t.palette.primary_200 const rt = useMemo(() => { return new RichTextAPI({text: message.text, facets: message.facets}) }, [message.text, message.facets]) return ( <> {isNewDay && } {AppBskyEmbedRecord.isView(message.embed) && ( )} {rt.text.length > 0 && ( )} {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( (i18n: I18n, timestamp: string) => { const date = new Date(timestamp) const now = new Date() const time = i18n.date(date, { hour: 'numeric', minute: 'numeric', }) const diff = now.getTime() - date.getTime() // if under 30 seconds if (diff < 1000 * 30) { return _(msg`Now`) } return time }, [_], ) return ( {({timeElapsed}) => ( {timeElapsed} )} {item.type === 'pending-message' && item.failed && ( <> {' '} ·{' '} {_(msg`Failed to send`)} {item.retry && ( <> {' '} ·{' '} {_(msg`Retry`)} )} )} ) } MessageItemMetadata = React.memo(MessageItemMetadata) export {MessageItemMetadata}