import {memo, useCallback} from 'react' import {LayoutAnimation} from 'react-native' import * as Clipboard from 'expo-clipboard' import {type ChatBskyConvoDefs, RichText} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useTranslate} from '#/lib/hooks/useTranslate' import {richTextToString} from '#/lib/strings/rich-text-helpers' import {logger} from '#/logger' import {isNative} from '#/platform/detection' import {useConvoActive} from '#/state/messages/convo' import {useLanguagePrefs} from '#/state/preferences' import {useSession} from '#/state/session' import * as Toast from '#/view/com/util/Toast' import * as ContextMenu from '#/components/ContextMenu' import {type TriggerProps} from '#/components/ContextMenu/types' import {ReportDialog} from '#/components/dms/ReportDialog' import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble' import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' import * as Prompt from '#/components/Prompt' import {usePromptControl} from '#/components/Prompt' import {EmojiReactionPicker} from './EmojiReactionPicker' import {hasReachedReactionLimit} from './util' export let MessageContextMenu = ({ message, children, }: { message: ChatBskyConvoDefs.MessageView children: TriggerProps['children'] }): React.ReactNode => { const {_} = useLingui() const {currentAccount} = useSession() const convo = useConvoActive() const deleteControl = usePromptControl() const reportControl = usePromptControl() const langPrefs = useLanguagePrefs() const translate = useTranslate() const isFromSelf = message.sender?.did === currentAccount?.did const onCopyMessage = useCallback(() => { const str = richTextToString( new RichText({ text: message.text, facets: message.facets, }), true, ) Clipboard.setStringAsync(str) Toast.show(_(msg`Copied to clipboard`), 'clipboard-check') }, [_, message.text, message.facets]) const onPressTranslateMessage = useCallback(() => { translate(message.text, langPrefs.primaryLanguage) logger.metric( 'translate', { sourceLanguages: [], targetLanguage: langPrefs.primaryLanguage, textLength: message.text.length, }, {statsig: false}, ) }, [langPrefs.primaryLanguage, message.text, translate]) const onDelete = useCallback(() => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) convo .deleteMessage(message.id) .then(() => Toast.show(_(msg({message: 'Message deleted', context: 'toast'}))), ) .catch(() => Toast.show(_(msg`Failed to delete message`))) }, [_, convo, message.id]) const onEmojiSelect = useCallback( (emoji: string) => { if ( message.reactions?.find( reaction => reaction.value === emoji && reaction.sender.did === currentAccount?.did, ) ) { convo .removeReaction(message.id, emoji) .catch(() => Toast.show(_(msg`Failed to remove emoji reaction`))) } else { if (hasReachedReactionLimit(message, currentAccount?.did)) return convo .addReaction(message.id, emoji) .catch(() => Toast.show(_(msg`Failed to add emoji reaction`), 'xmark'), ) } }, [_, convo, message, currentAccount?.did], ) const sender = convo.convo.members.find( member => member.did === message.sender.did, ) return ( <> {isNative && ( )} {children} {message.text.length > 0 && ( <> {_(msg`Translate`)} {_(msg`Copy message text`)} )} deleteControl.open()}> {_(msg`Delete for me`)} {!isFromSelf && ( reportControl.open()}> {_(msg`Report`)} )} ) } MessageContextMenu = memo(MessageContextMenu)