import {memo, useCallback} from 'react' import {LayoutAnimation, Platform} from 'react-native' import * as Clipboard from 'expo-clipboard' import {type ChatBskyConvoDefs, RichText} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate' import {richTextToString} from '#/lib/strings/rich-text-helpers' import {useConvoActive} from '#/state/messages/convo' import {useLanguagePrefs} from '#/state/preferences' import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache' import {useSession} from '#/state/session' import {atoms as a} from '#/alf' 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 TranslateIcon} from '#/components/icons/Bubble' import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' import {ReportDialog} from '#/components/moderation/ReportDialog' import * as Prompt from '#/components/Prompt' import {usePromptControl} from '#/components/Prompt' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' import {EmojiReactionPicker} from './EmojiReactionPicker' import {hasReachedReactionLimit} from './util' export let MessageContextMenu = ({ message, children, onTap, }: { message: ChatBskyConvoDefs.MessageView children: TriggerProps['children'] onTap?: () => void }): React.ReactNode => { const {t: l} = useLingui() const ax = useAnalytics() const {currentAccount} = useSession() const queryClient = useQueryClient() const convo = useConvoActive() const deleteControl = usePromptControl() const reportControl = usePromptControl() const blockOrDeleteControl = usePromptControl() const langPrefs = useLanguagePrefs() const translate = useGoogleTranslate() const isFromSelf = message.sender?.did === currentAccount?.did const isGroupChatEnabled = ax.features.enabled(ax.features.GroupChatsEnable) const onCopyMessage = useCallback(() => { const str = richTextToString( new RichText({ text: message.text, facets: message.facets, }), true, ) void Clipboard.setStringAsync(str) Toast.show(l`Copied to clipboard`, { type: 'success', }) }, [l, message.text, message.facets]) const onPressTranslateMessage = useCallback(() => { void translate(message.text, langPrefs.primaryLanguage) ax.metric('translate', { os: Platform.OS, possibleSourceLanguages: [], // N/A for chats expectedTargetLanguage: langPrefs.primaryLanguage, textLength: message.text.length, googleTranslate: true, }) }, [ax, langPrefs.primaryLanguage, message.text, translate]) const onDelete = useCallback(() => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) convo .deleteMessage(message.id) .then(() => Toast.show(l({message: 'Message deleted', context: 'toast'}))) .catch(() => Toast.show(l`Failed to delete message`)) }, [l, 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(l`Failed to remove emoji reaction`)) } else { if (hasReachedReactionLimit(message, currentAccount?.did)) return convo.addReaction(message.id, emoji).catch(() => Toast.show(l`Failed to add emoji reaction`, { type: 'error', }), ) } }, [l, convo, message, currentAccount?.did], ) const sender = convo.convo.members.find( member => member.did === message.sender.did, ) return ( <> {IS_NATIVE && ( )} {children} {message.text.length > 0 && ( <> {l`Translate`} {l`Copy message text`} )} deleteControl.open()}> {l`Delete for me`} {!isFromSelf && ( reportControl.open()}> {l`Report`} )} { if (sender) { unstableCacheProfileView(queryClient, sender) } blockOrDeleteControl.open() }} /> ) } MessageContextMenu = memo(MessageContextMenu)