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