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