mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at uiwork 260 lines 6.6 kB view raw
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})