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 {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 {AfterReportDialog} from '#/components/dms/AfterReportDialog' 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 {ReportDialog} from '#/components/moderation/ReportDialog' import * as Prompt from '#/components/Prompt' import {usePromptControl} from '#/components/Prompt' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' import {EmojiReactionPicker} from './EmojiReactionPicker' import {hasReachedReactionLimit} from './util' export let MessageContextMenu = ({ message, children, }: { message: ChatBskyConvoDefs.MessageView children: TriggerProps['children'] }): React.ReactNode => { const {_} = useLingui() const ax = useAnalytics() const {currentAccount} = useSession() const convo = useConvoActive() const deleteControl = usePromptControl() const reportControl = usePromptControl() const blockOrDeleteControl = 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) ax.metric('translate', { sourceLanguages: [], targetLanguage: langPrefs.primaryLanguage, textLength: message.text.length, }) }, [ax, 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 ( <> {IS_NATIVE && ( )} {children} {message.text.length > 0 && ( <> {_(msg`Translate`)} {_(msg`Copy message text`)} )} deleteControl.open()}> {_(msg`Delete for me`)} {!isFromSelf && ( reportControl.open()}> {_(msg`Report`)} )} { blockOrDeleteControl.open() }} /> ) } MessageContextMenu = memo(MessageContextMenu)