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