mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React, {useCallback} from 'react'
2import {Keyboard, Pressable, View} from 'react-native'
3import {ChatBskyConvoDefs, ModerationCause} from '@atproto/api'
4import {msg, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
6import {useNavigation} from '@react-navigation/native'
7
8import {NavigationProp} from '#/lib/routes/types'
9import {Shadow} from '#/state/cache/types'
10import {
11 useConvoQuery,
12 useMarkAsReadMutation,
13} from '#/state/queries/messages/conversation'
14import {useMuteConvo} from '#/state/queries/messages/mute-conversation'
15import {useProfileBlockMutationQueue} from '#/state/queries/profile'
16import * as Toast from '#/view/com/util/Toast'
17import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
18import {BlockedByListDialog} from '#/components/dms/BlockedByListDialog'
19import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt'
20import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt'
21import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeft} from '#/components/icons/ArrowBoxLeft'
22import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid'
23import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag'
24import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
25import {
26 Person_Stroke2_Corner0_Rounded as Person,
27 PersonCheck_Stroke2_Corner0_Rounded as PersonCheck,
28 PersonX_Stroke2_Corner0_Rounded as PersonX,
29} from '#/components/icons/Person'
30import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker'
31import * as Menu from '#/components/Menu'
32import * as Prompt from '#/components/Prompt'
33import * as bsky from '#/types/bsky'
34import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '../icons/Bubble'
35import {ReportDialog} from './ReportDialog'
36
37let ConvoMenu = ({
38 convo,
39 profile,
40 control,
41 currentScreen,
42 showMarkAsRead,
43 hideTrigger,
44 blockInfo,
45 latestReportableMessage,
46 style,
47}: {
48 convo: ChatBskyConvoDefs.ConvoView
49 profile: Shadow<bsky.profile.AnyProfileView>
50 control?: Menu.MenuControlProps
51 currentScreen: 'list' | 'conversation'
52 showMarkAsRead?: boolean
53 hideTrigger?: boolean
54 blockInfo: {
55 listBlocks: ModerationCause[]
56 userBlock?: ModerationCause
57 }
58 latestReportableMessage?: ChatBskyConvoDefs.MessageView
59 style?: ViewStyleProp['style']
60}): React.ReactNode => {
61 const {_} = useLingui()
62 const t = useTheme()
63
64 const leaveConvoControl = Prompt.usePromptControl()
65 const reportControl = Prompt.usePromptControl()
66 const blockedByListControl = Prompt.usePromptControl()
67
68 const {listBlocks} = blockInfo
69
70 return (
71 <>
72 <Menu.Root control={control}>
73 {!hideTrigger && (
74 <View style={[style]}>
75 <Menu.Trigger label={_(msg`Chat settings`)}>
76 {({props, state}) => (
77 <Pressable
78 {...props}
79 onPress={() => {
80 Keyboard.dismiss()
81 props.onPress()
82 }}
83 style={[
84 a.p_sm,
85 a.rounded_full,
86 (state.hovered || state.pressed) && t.atoms.bg_contrast_25,
87 // make sure pfp is in the middle
88 {marginLeft: -10},
89 ]}>
90 <DotsHorizontal size="md" style={t.atoms.text} />
91 </Pressable>
92 )}
93 </Menu.Trigger>
94 </View>
95 )}
96
97 <Menu.Outer>
98 <MenuContent
99 profile={profile}
100 showMarkAsRead={showMarkAsRead}
101 blockInfo={blockInfo}
102 convo={convo}
103 leaveConvoControl={leaveConvoControl}
104 reportControl={reportControl}
105 blockedByListControl={blockedByListControl}
106 />
107 </Menu.Outer>
108 </Menu.Root>
109
110 <LeaveConvoPrompt
111 control={leaveConvoControl}
112 convoId={convo.id}
113 currentScreen={currentScreen}
114 />
115 {latestReportableMessage ? (
116 <ReportDialog
117 currentScreen={currentScreen}
118 params={{
119 type: 'convoMessage',
120 convoId: convo.id,
121 message: latestReportableMessage,
122 }}
123 control={reportControl}
124 />
125 ) : (
126 <ReportConversationPrompt control={reportControl} />
127 )}
128
129 <BlockedByListDialog
130 control={blockedByListControl}
131 listBlocks={listBlocks}
132 />
133 </>
134 )
135}
136ConvoMenu = React.memo(ConvoMenu)
137
138function MenuContent({
139 convo: initialConvo,
140 profile,
141 showMarkAsRead,
142 blockInfo,
143 leaveConvoControl,
144 reportControl,
145 blockedByListControl,
146}: {
147 convo: ChatBskyConvoDefs.ConvoView
148 profile: Shadow<bsky.profile.AnyProfileView>
149 showMarkAsRead?: boolean
150 blockInfo: {
151 listBlocks: ModerationCause[]
152 userBlock?: ModerationCause
153 }
154 leaveConvoControl: Prompt.PromptControlProps
155 reportControl: Prompt.PromptControlProps
156 blockedByListControl: Prompt.PromptControlProps
157}) {
158 const navigation = useNavigation<NavigationProp>()
159 const {_} = useLingui()
160 const {mutate: markAsRead} = useMarkAsReadMutation()
161
162 const {listBlocks, userBlock} = blockInfo
163 const isBlocking = userBlock || !!listBlocks.length
164 const isDeletedAccount = profile.handle === 'missing.invalid'
165
166 const convoId = initialConvo.id
167 const {data: convo} = useConvoQuery(initialConvo)
168
169 const onNavigateToProfile = useCallback(() => {
170 navigation.navigate('Profile', {name: profile.did})
171 }, [navigation, profile.did])
172
173 const {mutate: muteConvo} = useMuteConvo(convoId, {
174 onSuccess: data => {
175 if (data.convo.muted) {
176 Toast.show(_(msg({message: 'Chat muted', context: 'toast'})))
177 } else {
178 Toast.show(_(msg({message: 'Chat unmuted', context: 'toast'})))
179 }
180 },
181 onError: () => {
182 Toast.show(_(msg`Could not mute chat`), 'xmark')
183 },
184 })
185
186 const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
187
188 const toggleBlock = React.useCallback(() => {
189 if (listBlocks.length) {
190 blockedByListControl.open()
191 return
192 }
193
194 if (userBlock) {
195 queueUnblock()
196 } else {
197 queueBlock()
198 }
199 }, [userBlock, listBlocks, blockedByListControl, queueBlock, queueUnblock])
200
201 return isDeletedAccount ? (
202 <Menu.Item
203 label={_(msg`Leave conversation`)}
204 onPress={() => leaveConvoControl.open()}>
205 <Menu.ItemText>
206 <Trans>Leave conversation</Trans>
207 </Menu.ItemText>
208 <Menu.ItemIcon icon={ArrowBoxLeft} />
209 </Menu.Item>
210 ) : (
211 <>
212 <Menu.Group>
213 {showMarkAsRead && (
214 <Menu.Item
215 label={_(msg`Mark as read`)}
216 onPress={() => markAsRead({convoId})}>
217 <Menu.ItemText>
218 <Trans>Mark as read</Trans>
219 </Menu.ItemText>
220 <Menu.ItemIcon icon={Bubble} />
221 </Menu.Item>
222 )}
223 <Menu.Item
224 label={_(msg`Go to user's profile`)}
225 onPress={onNavigateToProfile}>
226 <Menu.ItemText>
227 <Trans>Go to profile</Trans>
228 </Menu.ItemText>
229 <Menu.ItemIcon icon={Person} />
230 </Menu.Item>
231 <Menu.Item
232 label={_(msg`Mute conversation`)}
233 onPress={() => muteConvo({mute: !convo?.muted})}>
234 <Menu.ItemText>
235 {convo?.muted ? (
236 <Trans>Unmute conversation</Trans>
237 ) : (
238 <Trans>Mute conversation</Trans>
239 )}
240 </Menu.ItemText>
241 <Menu.ItemIcon icon={convo?.muted ? Unmute : Mute} />
242 </Menu.Item>
243 </Menu.Group>
244 <Menu.Divider />
245 <Menu.Group>
246 <Menu.Item
247 label={isBlocking ? _(msg`Unblock account`) : _(msg`Block account`)}
248 onPress={toggleBlock}>
249 <Menu.ItemText>
250 {isBlocking ? _(msg`Unblock account`) : _(msg`Block account`)}
251 </Menu.ItemText>
252 <Menu.ItemIcon icon={isBlocking ? PersonCheck : PersonX} />
253 </Menu.Item>
254 <Menu.Item
255 label={_(msg`Report conversation`)}
256 onPress={() => reportControl.open()}>
257 <Menu.ItemText>
258 <Trans>Report conversation</Trans>
259 </Menu.ItemText>
260 <Menu.ItemIcon icon={Flag} />
261 </Menu.Item>
262 </Menu.Group>
263 <Menu.Divider />
264 <Menu.Group>
265 <Menu.Item
266 label={_(msg`Leave conversation`)}
267 onPress={() => leaveConvoControl.open()}>
268 <Menu.ItemText>
269 <Trans>Leave conversation</Trans>
270 </Menu.ItemText>
271 <Menu.ItemIcon icon={ArrowBoxLeft} />
272 </Menu.Item>
273 </Menu.Group>
274 </>
275 )
276}
277
278export {ConvoMenu}