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