mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {LayoutAnimation, Pressable, View} from 'react-native'
3import * as Clipboard from 'expo-clipboard'
4import {ChatBskyConvoDefs, RichText} from '@atproto/api'
5import {msg} from '@lingui/macro'
6import {useLingui} from '@lingui/react'
7
8import {useOpenLink} from '#/lib/hooks/useOpenLink'
9import {richTextToString} from '#/lib/strings/rich-text-helpers'
10import {getTranslatorLink} from '#/locale/helpers'
11import {isWeb} from '#/platform/detection'
12import {useConvoActive} from '#/state/messages/convo'
13import {useLanguagePrefs} from '#/state/preferences'
14import {useSession} from '#/state/session'
15import * as Toast from '#/view/com/util/Toast'
16import {atoms as a, useTheme} from '#/alf'
17import {ReportDialog} from '#/components/dms/ReportDialog'
18import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble'
19import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid'
20import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
21import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning'
22import * as Menu from '#/components/Menu'
23import * as Prompt from '#/components/Prompt'
24import {usePromptControl} from '#/components/Prompt'
25import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '../icons/Clipboard'
26
27export let MessageMenu = ({
28 message,
29 control,
30 triggerOpacity,
31}: {
32 triggerOpacity?: number
33 message: ChatBskyConvoDefs.MessageView
34 control: Menu.MenuControlProps
35}): React.ReactNode => {
36 const {_} = useLingui()
37 const t = useTheme()
38 const {currentAccount} = useSession()
39 const convo = useConvoActive()
40 const deleteControl = usePromptControl()
41 const reportControl = usePromptControl()
42 const langPrefs = useLanguagePrefs()
43 const openLink = useOpenLink()
44
45 const isFromSelf = message.sender?.did === currentAccount?.did
46
47 const onCopyMessage = React.useCallback(() => {
48 const str = richTextToString(
49 new RichText({
50 text: message.text,
51 facets: message.facets,
52 }),
53 true,
54 )
55
56 Clipboard.setStringAsync(str)
57 Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
58 }, [_, message.text, message.facets])
59
60 const onPressTranslateMessage = React.useCallback(() => {
61 const translatorUrl = getTranslatorLink(
62 message.text,
63 langPrefs.primaryLanguage,
64 )
65 openLink(translatorUrl, true)
66 }, [langPrefs.primaryLanguage, message.text, openLink])
67
68 const onDelete = React.useCallback(() => {
69 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
70 convo
71 .deleteMessage(message.id)
72 .then(() =>
73 Toast.show(_(msg({message: 'Message deleted', context: 'toast'}))),
74 )
75 .catch(() => Toast.show(_(msg`Failed to delete message`)))
76 }, [_, convo, message.id])
77
78 return (
79 <>
80 <Menu.Root control={control}>
81 {isWeb && (
82 <View style={{opacity: triggerOpacity}}>
83 <Menu.Trigger label={_(msg`Chat settings`)}>
84 {({props, state}) => (
85 <Pressable
86 {...props}
87 style={[
88 a.p_sm,
89 a.rounded_full,
90 (state.hovered || state.pressed) && t.atoms.bg_contrast_25,
91 ]}>
92 <DotsHorizontal size="md" style={t.atoms.text} />
93 </Pressable>
94 )}
95 </Menu.Trigger>
96 </View>
97 )}
98
99 <Menu.Outer>
100 {message.text.length > 0 && (
101 <>
102 <Menu.Group>
103 <Menu.Item
104 testID="messageDropdownTranslateBtn"
105 label={_(msg`Translate`)}
106 onPress={onPressTranslateMessage}>
107 <Menu.ItemText>{_(msg`Translate`)}</Menu.ItemText>
108 <Menu.ItemIcon icon={Translate} position="right" />
109 </Menu.Item>
110 <Menu.Item
111 testID="messageDropdownCopyBtn"
112 label={_(msg`Copy message text`)}
113 onPress={onCopyMessage}>
114 <Menu.ItemText>{_(msg`Copy message text`)}</Menu.ItemText>
115 <Menu.ItemIcon icon={ClipboardIcon} position="right" />
116 </Menu.Item>
117 </Menu.Group>
118 <Menu.Divider />
119 </>
120 )}
121 <Menu.Group>
122 <Menu.Item
123 testID="messageDropdownDeleteBtn"
124 label={_(msg`Delete message for me`)}
125 onPress={() => deleteControl.open()}>
126 <Menu.ItemText>{_(msg`Delete for me`)}</Menu.ItemText>
127 <Menu.ItemIcon icon={Trash} position="right" />
128 </Menu.Item>
129 {!isFromSelf && (
130 <Menu.Item
131 testID="messageDropdownReportBtn"
132 label={_(msg`Report message`)}
133 onPress={() => reportControl.open()}>
134 <Menu.ItemText>{_(msg`Report`)}</Menu.ItemText>
135 <Menu.ItemIcon icon={Warning} position="right" />
136 </Menu.Item>
137 )}
138 </Menu.Group>
139 </Menu.Outer>
140 </Menu.Root>
141
142 <ReportDialog
143 currentScreen="conversation"
144 params={{type: 'convoMessage', convoId: convo.convo.id, message}}
145 control={reportControl}
146 />
147
148 <Prompt.Basic
149 control={deleteControl}
150 title={_(msg`Delete message`)}
151 description={_(
152 msg`Are you sure you want to delete this message? The message will be deleted for you, but not for the other participant.`,
153 )}
154 confirmButtonCta={_(msg`Delete`)}
155 confirmButtonColor="negative"
156 onConfirm={onDelete}
157 />
158 </>
159 )
160}
161MessageMenu = React.memo(MessageMenu)