diff options
author | Hailey <me@haileyok.com> | 2024-05-02 13:54:17 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-02 13:54:17 -0700 |
commit | 8ba1b10ce0d278a88e37d6b6c277a41673392877 (patch) | |
tree | 1e44ec05c427aaa124077b6fb9ce01c831daa8b3 /src/components/dms/MessageItem.tsx | |
parent | 6da18e3dcffaf72a03bde8a205a596b4b3366b86 (diff) | |
download | voidsky-8ba1b10ce0d278a88e37d6b6c277a41673392877.tar.zst |
[Clipclops] Message actions for native and web (#3807)
* haptic on long press * add animation to press and hold * eslint disable for now * adjust styles * dont trigger if animation is cancelled * organize * add a delete menu * reset scale automatically * message actions dialog cleanup center the trigger handle focus/unfocus better make triggers accessible weg dropdown menu add a wep specific wrapper decrease press delay add report button improve shrink logic use `self_end` instead of `margin: auto` rm extra `?` move `MessageItem` to `components` add delete button * rm some padding * update after merge * fix merge * web only types * fix crash * add an explanation * fix web types --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Diffstat (limited to 'src/components/dms/MessageItem.tsx')
-rw-r--r-- | src/components/dms/MessageItem.tsx | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx new file mode 100644 index 000000000..3a1d8eab7 --- /dev/null +++ b/src/components/dms/MessageItem.tsx @@ -0,0 +1,166 @@ +import React, {useCallback, useMemo} from 'react' +import {StyleProp, TextStyle, View} from 'react-native' +import {ChatBskyConvoDefs} from '@atproto-labs/api' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +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 {Text} from '#/components/Typography' + +export function MessageItem({ + item, + next, +}: { + item: ChatBskyConvoDefs.MessageView + next: + | ChatBskyConvoDefs.MessageView + | ChatBskyConvoDefs.DeletedMessageView + | null +}) { + const t = useTheme() + const {currentAccount} = useSession() + + const isFromSelf = item.sender?.did === currentAccount?.did + + const isNextFromSelf = + ChatBskyConvoDefs.isMessageView(next) && + next.sender?.did === currentAccount?.did + + const isLastInGroup = useMemo(() => { + // 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 10 minute gap between this message and the next + if (ChatBskyConvoDefs.isMessageView(next)) { + const thisDate = new Date(item.sentAt) + const nextDate = new Date(next.sentAt) + + const diff = nextDate.getTime() - thisDate.getTime() + + // 10 minutes + return diff > 10 * 60 * 1000 + } + + return true + }, [item, next, isFromSelf, isNextFromSelf]) + + return ( + <View> + <ActionsWrapper isFromSelf={isFromSelf} message={item}> + <View + style={[ + a.py_sm, + a.px_lg, + a.my_2xs, + a.rounded_md, + { + backgroundColor: isFromSelf + ? t.palette.primary_500 + : t.palette.contrast_50, + borderRadius: 17, + }, + isFromSelf + ? {borderBottomRightRadius: isLastInGroup ? 2 : 17} + : {borderBottomLeftRadius: isLastInGroup ? 2 : 17}, + ]}> + <Text + style={[ + a.text_md, + a.leading_snug, + isFromSelf && {color: t.palette.white}, + ]}> + {item.text} + </Text> + </View> + </ActionsWrapper> + <MessageItemMetadata + message={item} + isLastInGroup={isLastInGroup} + style={isFromSelf ? a.text_right : a.text_left} + /> + </View> + ) +} + +export function MessageItemMetadata({ + message, + isLastInGroup, + style, +}: { + message: ChatBskyConvoDefs.MessageView + isLastInGroup: boolean + style: StyleProp<TextStyle> +}) { + const t = useTheme() + const {_} = useLingui() + + const relativeTimestamp = useCallback( + (timestamp: string) => { + const date = new Date(timestamp) + const now = new Date() + + const time = new Intl.DateTimeFormat(undefined, { + hour: 'numeric', + minute: 'numeric', + hour12: true, + }).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 (now.toISOString().slice(0, 10) === date.toISOString().slice(0, 10)) { + return time + } + + // if yesterday + const yesterday = new Date(now) + yesterday.setDate(yesterday.getDate() - 1) + if ( + yesterday.toISOString().slice(0, 10) === date.toISOString().slice(0, 10) + ) { + return _(msg`Yesterday, ${time}`) + } + + return new Intl.DateTimeFormat(undefined, { + hour: 'numeric', + minute: 'numeric', + hour12: true, + day: 'numeric', + month: 'numeric', + year: 'numeric', + }).format(date) + }, + [_], + ) + + if (!isLastInGroup) { + return null + } + + return ( + <TimeElapsed timestamp={message.sentAt} timeToString={relativeTimestamp}> + {({timeElapsed}) => ( + <Text + style={[ + t.atoms.text_contrast_medium, + a.text_xs, + a.mt_2xs, + a.mb_lg, + style, + ]}> + {timeElapsed} + </Text> + )} + </TimeElapsed> + ) +} |