mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at mod-auth 237 lines 6.7 kB view raw
1import React from 'react' 2import { 3 ActivityIndicator, 4 Dimensions, 5 StyleProp, 6 View, 7 ViewStyle, 8} from 'react-native' 9import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api' 10import {List, ListRef} from '../util/List' 11import {ProfileCardFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' 12import {ErrorMessage} from '../util/error/ErrorMessage' 13import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' 14import {ProfileCard} from '../profile/ProfileCard' 15import {Button} from '../util/forms/Button' 16import {useAnalytics} from 'lib/analytics/analytics' 17import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 18import {useListMembersQuery} from '#/state/queries/list-members' 19import {logger} from '#/logger' 20import {useModalControls} from '#/state/modals' 21import {useSession} from '#/state/session' 22import {cleanError} from '#/lib/strings/errors' 23import {useLingui} from '@lingui/react' 24import {msg} from '@lingui/macro' 25 26const LOADING_ITEM = {_reactKey: '__loading__'} 27const EMPTY_ITEM = {_reactKey: '__empty__'} 28const ERROR_ITEM = {_reactKey: '__error__'} 29const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'} 30 31export function ListMembers({ 32 list, 33 style, 34 scrollElRef, 35 onScrolledDownChange, 36 onPressTryAgain, 37 renderHeader, 38 renderEmptyState, 39 testID, 40 headerOffset = 0, 41 desktopFixedHeightOffset, 42}: { 43 list: string 44 style?: StyleProp<ViewStyle> 45 scrollElRef?: ListRef 46 onScrolledDownChange: (isScrolledDown: boolean) => void 47 onPressTryAgain?: () => void 48 renderHeader: () => JSX.Element 49 renderEmptyState: () => JSX.Element 50 testID?: string 51 headerOffset?: number 52 desktopFixedHeightOffset?: number 53}) { 54 const {track} = useAnalytics() 55 const {_} = useLingui() 56 const [isRefreshing, setIsRefreshing] = React.useState(false) 57 const {isMobile} = useWebMediaQueries() 58 const {openModal} = useModalControls() 59 const {currentAccount} = useSession() 60 61 const { 62 data, 63 isFetching, 64 isFetched, 65 isError, 66 error, 67 refetch, 68 fetchNextPage, 69 hasNextPage, 70 } = useListMembersQuery(list) 71 const isEmpty = !isFetching && !data?.pages[0].items.length 72 const isOwner = 73 currentAccount && data?.pages[0].list.creator.did === currentAccount.did 74 75 const items = React.useMemo(() => { 76 let items: any[] = [] 77 if (isFetched) { 78 if (isEmpty && isError) { 79 items = items.concat([ERROR_ITEM]) 80 } 81 if (isEmpty) { 82 items = items.concat([EMPTY_ITEM]) 83 } else if (data) { 84 for (const page of data.pages) { 85 items = items.concat(page.items) 86 } 87 } 88 if (!isEmpty && isError) { 89 items = items.concat([LOAD_MORE_ERROR_ITEM]) 90 } 91 } else if (isFetching) { 92 items = items.concat([LOADING_ITEM]) 93 } 94 return items 95 }, [isFetched, isEmpty, isError, data, isFetching]) 96 97 // events 98 // = 99 100 const onRefresh = React.useCallback(async () => { 101 track('Lists:onRefresh') 102 setIsRefreshing(true) 103 try { 104 await refetch() 105 } catch (err) { 106 logger.error('Failed to refresh lists', {message: err}) 107 } 108 setIsRefreshing(false) 109 }, [refetch, track, setIsRefreshing]) 110 111 const onEndReached = React.useCallback(async () => { 112 if (isFetching || !hasNextPage || isError) return 113 track('Lists:onEndReached') 114 try { 115 await fetchNextPage() 116 } catch (err) { 117 logger.error('Failed to load more lists', {message: err}) 118 } 119 }, [isFetching, hasNextPage, isError, fetchNextPage, track]) 120 121 const onPressRetryLoadMore = React.useCallback(() => { 122 fetchNextPage() 123 }, [fetchNextPage]) 124 125 const onPressEditMembership = React.useCallback( 126 (profile: AppBskyActorDefs.ProfileViewBasic) => { 127 openModal({ 128 name: 'user-add-remove-lists', 129 subject: profile.did, 130 displayName: profile.displayName || profile.handle, 131 handle: profile.handle, 132 }) 133 }, 134 [openModal], 135 ) 136 137 // rendering 138 // = 139 140 const renderMemberButton = React.useCallback( 141 (profile: AppBskyActorDefs.ProfileViewBasic) => { 142 if (!isOwner) { 143 return null 144 } 145 return ( 146 <Button 147 testID={`user-${profile.handle}-editBtn`} 148 type="default" 149 label={_(msg({message: 'Edit', context: 'action'}))} 150 onPress={() => onPressEditMembership(profile)} 151 /> 152 ) 153 }, 154 [isOwner, onPressEditMembership, _], 155 ) 156 157 const renderItem = React.useCallback( 158 ({item}: {item: any}) => { 159 if (item === EMPTY_ITEM) { 160 return renderEmptyState() 161 } else if (item === ERROR_ITEM) { 162 return ( 163 <ErrorMessage 164 message={cleanError(error)} 165 onPressTryAgain={onPressTryAgain} 166 /> 167 ) 168 } else if (item === LOAD_MORE_ERROR_ITEM) { 169 return ( 170 <LoadMoreRetryBtn 171 label={_( 172 msg`There was an issue fetching the list. Tap here to try again.`, 173 )} 174 onPress={onPressRetryLoadMore} 175 /> 176 ) 177 } else if (item === LOADING_ITEM) { 178 return <ProfileCardFeedLoadingPlaceholder /> 179 } 180 return ( 181 <ProfileCard 182 testID={`user-${ 183 (item as AppBskyGraphDefs.ListItemView).subject.handle 184 }`} 185 profile={(item as AppBskyGraphDefs.ListItemView).subject} 186 renderButton={renderMemberButton} 187 style={{paddingHorizontal: isMobile ? 8 : 14, paddingVertical: 4}} 188 noModFilter 189 /> 190 ) 191 }, 192 [ 193 renderMemberButton, 194 renderEmptyState, 195 error, 196 onPressTryAgain, 197 onPressRetryLoadMore, 198 isMobile, 199 _, 200 ], 201 ) 202 203 const Footer = React.useCallback( 204 () => ( 205 <View style={{paddingTop: 20, paddingBottom: 400}}> 206 {isFetching && <ActivityIndicator />} 207 </View> 208 ), 209 [isFetching], 210 ) 211 212 return ( 213 <View testID={testID} style={style}> 214 <List 215 testID={testID ? `${testID}-flatlist` : undefined} 216 ref={scrollElRef} 217 data={items} 218 keyExtractor={(item: any) => item.subject?.did || item._reactKey} 219 renderItem={renderItem} 220 ListHeaderComponent={renderHeader} 221 ListFooterComponent={Footer} 222 refreshing={isRefreshing} 223 onRefresh={onRefresh} 224 headerOffset={headerOffset} 225 contentContainerStyle={{ 226 minHeight: Dimensions.get('window').height * 1.5, 227 }} 228 onScrolledDownChange={onScrolledDownChange} 229 onEndReached={onEndReached} 230 onEndReachedThreshold={0.6} 231 removeClippedSubviews={true} 232 // @ts-ignore our .web version only -prf 233 desktopFixedHeight={desktopFixedHeightOffset || true} 234 /> 235 </View> 236 ) 237}