mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React, {useCallback} from 'react'
2import {ActivityIndicator, StyleSheet, View} from 'react-native'
3import {AppBskyGraphDefs as GraphDefs} from '@atproto/api'
4import {Text} from '../util/text/Text'
5import {UserAvatar} from '../util/UserAvatar'
6import {MyLists} from '../lists/MyLists'
7import {Button} from '../util/forms/Button'
8import * as Toast from '../util/Toast'
9import {sanitizeDisplayName} from 'lib/strings/display-names'
10import {sanitizeHandle} from 'lib/strings/handles'
11import {s} from 'lib/styles'
12import {usePalette} from 'lib/hooks/usePalette'
13import {isWeb, isAndroid} from 'platform/detection'
14import {Trans, msg} from '@lingui/macro'
15import {useLingui} from '@lingui/react'
16import {useModalControls} from '#/state/modals'
17import {
18 useDangerousListMembershipsQuery,
19 getMembership,
20 ListMembersip,
21 useListMembershipAddMutation,
22 useListMembershipRemoveMutation,
23} from '#/state/queries/list-memberships'
24import {cleanError} from '#/lib/strings/errors'
25import {useSession} from '#/state/session'
26
27export const snapPoints = ['fullscreen']
28
29export function Component({
30 subject,
31 displayName,
32 onAdd,
33 onRemove,
34}: {
35 subject: string
36 displayName: string
37 onAdd?: (listUri: string) => void
38 onRemove?: (listUri: string) => void
39}) {
40 const {closeModal} = useModalControls()
41 const pal = usePalette('default')
42 const {_} = useLingui()
43 const {data: memberships} = useDangerousListMembershipsQuery()
44
45 const onPressDone = useCallback(() => {
46 closeModal()
47 }, [closeModal])
48
49 return (
50 <View testID="userAddRemoveListsModal" style={s.hContentRegion}>
51 <Text style={[styles.title, pal.text]}>
52 <Trans>Update {displayName} in Lists</Trans>
53 </Text>
54 <MyLists
55 filter="all"
56 inline
57 renderItem={(list, index) => (
58 <ListItem
59 index={index}
60 list={list}
61 memberships={memberships}
62 subject={subject}
63 onAdd={onAdd}
64 onRemove={onRemove}
65 />
66 )}
67 style={[styles.list, pal.border]}
68 />
69 <View style={[styles.btns, pal.border]}>
70 <Button
71 testID="doneBtn"
72 type="default"
73 onPress={onPressDone}
74 style={styles.footerBtn}
75 accessibilityLabel={_(msg`Done`)}
76 accessibilityHint=""
77 onAccessibilityEscape={onPressDone}
78 label="Done"
79 />
80 </View>
81 </View>
82 )
83}
84
85function ListItem({
86 index,
87 list,
88 memberships,
89 subject,
90 onAdd,
91 onRemove,
92}: {
93 index: number
94 list: GraphDefs.ListView
95 memberships: ListMembersip[] | undefined
96 subject: string
97 onAdd?: (listUri: string) => void
98 onRemove?: (listUri: string) => void
99}) {
100 const pal = usePalette('default')
101 const {_} = useLingui()
102 const {currentAccount} = useSession()
103 const [isProcessing, setIsProcessing] = React.useState(false)
104 const membership = React.useMemo(
105 () => getMembership(memberships, list.uri, subject),
106 [memberships, list.uri, subject],
107 )
108 const listMembershipAddMutation = useListMembershipAddMutation()
109 const listMembershipRemoveMutation = useListMembershipRemoveMutation()
110
111 const onToggleMembership = useCallback(async () => {
112 if (typeof membership === 'undefined') {
113 return
114 }
115 setIsProcessing(true)
116 try {
117 if (membership === false) {
118 await listMembershipAddMutation.mutateAsync({
119 listUri: list.uri,
120 actorDid: subject,
121 })
122 Toast.show(_(msg`Added to list`))
123 onAdd?.(list.uri)
124 } else {
125 await listMembershipRemoveMutation.mutateAsync({
126 listUri: list.uri,
127 actorDid: subject,
128 membershipUri: membership,
129 })
130 Toast.show(_(msg`Removed from list`))
131 onRemove?.(list.uri)
132 }
133 } catch (e) {
134 Toast.show(cleanError(e))
135 } finally {
136 setIsProcessing(false)
137 }
138 }, [
139 _,
140 list,
141 subject,
142 membership,
143 setIsProcessing,
144 onAdd,
145 onRemove,
146 listMembershipAddMutation,
147 listMembershipRemoveMutation,
148 ])
149
150 return (
151 <View
152 testID={`toggleBtn-${list.name}`}
153 style={[
154 styles.listItem,
155 pal.border,
156 {
157 borderTopWidth: index === 0 ? 0 : 1,
158 },
159 ]}>
160 <View style={styles.listItemAvi}>
161 <UserAvatar size={40} avatar={list.avatar} />
162 </View>
163 <View style={styles.listItemContent}>
164 <Text
165 type="lg"
166 style={[s.bold, pal.text]}
167 numberOfLines={1}
168 lineHeight={1.2}>
169 {sanitizeDisplayName(list.name)}
170 </Text>
171 <Text type="md" style={[pal.textLight]} numberOfLines={1}>
172 {list.purpose === 'app.bsky.graph.defs#curatelist' && 'User list '}
173 {list.purpose === 'app.bsky.graph.defs#modlist' && 'Moderation list '}
174 by{' '}
175 {list.creator.did === currentAccount?.did
176 ? 'you'
177 : sanitizeHandle(list.creator.handle, '@')}
178 </Text>
179 </View>
180 <View>
181 {isProcessing || typeof membership === 'undefined' ? (
182 <ActivityIndicator />
183 ) : (
184 <Button
185 testID={`user-${subject}-addBtn`}
186 type="default"
187 label={membership === false ? _(msg`Add`) : _(msg`Remove`)}
188 onPress={onToggleMembership}
189 />
190 )}
191 </View>
192 </View>
193 )
194}
195
196const styles = StyleSheet.create({
197 container: {
198 paddingHorizontal: isWeb ? 0 : 16,
199 },
200 title: {
201 textAlign: 'center',
202 fontWeight: 'bold',
203 fontSize: 24,
204 marginBottom: 10,
205 },
206 list: {
207 flex: 1,
208 borderTopWidth: 1,
209 },
210 btns: {
211 position: 'relative',
212 flexDirection: 'row',
213 alignItems: 'center',
214 justifyContent: 'center',
215 gap: 10,
216 paddingTop: 10,
217 paddingBottom: isAndroid ? 10 : 0,
218 borderTopWidth: 1,
219 },
220 footerBtn: {
221 paddingHorizontal: 24,
222 paddingVertical: 12,
223 },
224
225 listItem: {
226 flexDirection: 'row',
227 alignItems: 'center',
228 paddingHorizontal: 14,
229 paddingVertical: 10,
230 },
231 listItemAvi: {
232 width: 54,
233 paddingLeft: 4,
234 paddingTop: 8,
235 paddingBottom: 10,
236 },
237 listItemContent: {
238 flex: 1,
239 paddingRight: 10,
240 paddingTop: 10,
241 paddingBottom: 10,
242 },
243 checkbox: {
244 flexDirection: 'row',
245 alignItems: 'center',
246 justifyContent: 'center',
247 borderWidth: 1,
248 width: 24,
249 height: 24,
250 borderRadius: 6,
251 marginRight: 8,
252 },
253 loadingContainer: {
254 position: 'absolute',
255 top: 10,
256 right: 0,
257 bottom: 0,
258 justifyContent: 'center',
259 },
260})