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 thread-prepare-4 203 lines 6.0 kB view raw
1import React from 'react' 2import { 3 findNodeHandle, 4 ListRenderItemInfo, 5 StyleProp, 6 View, 7 ViewStyle, 8} from 'react-native' 9import {msg} from '@lingui/macro' 10import {useLingui} from '@lingui/react' 11import {useQueryClient} from '@tanstack/react-query' 12 13import {cleanError} from '#/lib/strings/errors' 14import {logger} from '#/logger' 15import {isNative, isWeb} from '#/platform/detection' 16import {RQKEY, useProfileListsQuery} from '#/state/queries/profile-lists' 17import {EmptyState} from '#/view/com/util/EmptyState' 18import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 19import {atoms as a, ios, useTheme} from '#/alf' 20import * as ListCard from '#/components/ListCard' 21import {ErrorMessage} from '../util/error/ErrorMessage' 22import {List, ListRef} from '../util/List' 23import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' 24 25const LOADING = {_reactKey: '__loading__'} 26const EMPTY = {_reactKey: '__empty__'} 27const ERROR_ITEM = {_reactKey: '__error__'} 28const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'} 29 30interface SectionRef { 31 scrollToTop: () => void 32} 33 34interface ProfileListsProps { 35 did: string 36 scrollElRef: ListRef 37 headerOffset: number 38 enabled?: boolean 39 style?: StyleProp<ViewStyle> 40 testID?: string 41 setScrollViewTag: (tag: number | null) => void 42} 43 44export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>( 45 function ProfileListsImpl( 46 {did, scrollElRef, headerOffset, enabled, style, testID, setScrollViewTag}, 47 ref, 48 ) { 49 const t = useTheme() 50 const {_} = useLingui() 51 const [isPTRing, setIsPTRing] = React.useState(false) 52 const opts = React.useMemo(() => ({enabled}), [enabled]) 53 const { 54 data, 55 isFetching, 56 isFetched, 57 hasNextPage, 58 fetchNextPage, 59 isError, 60 error, 61 refetch, 62 } = useProfileListsQuery(did, opts) 63 const isEmpty = !isFetching && !data?.pages[0]?.lists.length 64 65 const items = React.useMemo(() => { 66 let items: any[] = [] 67 if (isError && isEmpty) { 68 items = items.concat([ERROR_ITEM]) 69 } 70 if (!isFetched && isFetching) { 71 items = items.concat([LOADING]) 72 } else if (isEmpty) { 73 items = items.concat([EMPTY]) 74 } else if (data?.pages) { 75 for (const page of data?.pages) { 76 items = items.concat(page.lists) 77 } 78 } 79 if (isError && !isEmpty) { 80 items = items.concat([LOAD_MORE_ERROR_ITEM]) 81 } 82 return items 83 }, [isError, isEmpty, isFetched, isFetching, data]) 84 85 // events 86 // = 87 88 const queryClient = useQueryClient() 89 90 const onScrollToTop = React.useCallback(() => { 91 scrollElRef.current?.scrollToOffset({ 92 animated: isNative, 93 offset: -headerOffset, 94 }) 95 queryClient.invalidateQueries({queryKey: RQKEY(did)}) 96 }, [scrollElRef, queryClient, headerOffset, did]) 97 98 React.useImperativeHandle(ref, () => ({ 99 scrollToTop: onScrollToTop, 100 })) 101 102 const onRefresh = React.useCallback(async () => { 103 setIsPTRing(true) 104 try { 105 await refetch() 106 } catch (err) { 107 logger.error('Failed to refresh lists', {message: err}) 108 } 109 setIsPTRing(false) 110 }, [refetch, setIsPTRing]) 111 112 const onEndReached = React.useCallback(async () => { 113 if (isFetching || !hasNextPage || isError) return 114 try { 115 await fetchNextPage() 116 } catch (err) { 117 logger.error('Failed to load more lists', {message: err}) 118 } 119 }, [isFetching, hasNextPage, isError, fetchNextPage]) 120 121 const onPressRetryLoadMore = React.useCallback(() => { 122 fetchNextPage() 123 }, [fetchNextPage]) 124 125 // rendering 126 // = 127 128 const renderItemInner = React.useCallback( 129 ({item, index}: ListRenderItemInfo<any>) => { 130 if (item === EMPTY) { 131 return ( 132 <EmptyState 133 icon="list-ul" 134 message={_(msg`You have no lists.`)} 135 testID="listsEmpty" 136 /> 137 ) 138 } else if (item === ERROR_ITEM) { 139 return ( 140 <ErrorMessage 141 message={cleanError(error)} 142 onPressTryAgain={refetch} 143 /> 144 ) 145 } else if (item === LOAD_MORE_ERROR_ITEM) { 146 return ( 147 <LoadMoreRetryBtn 148 label={_( 149 msg`There was an issue fetching your lists. Tap here to try again.`, 150 )} 151 onPress={onPressRetryLoadMore} 152 /> 153 ) 154 } else if (item === LOADING) { 155 return <FeedLoadingPlaceholder /> 156 } 157 return ( 158 <View 159 style={[ 160 (index !== 0 || isWeb) && a.border_t, 161 t.atoms.border_contrast_low, 162 a.px_lg, 163 a.py_lg, 164 ]}> 165 <ListCard.Default view={item} /> 166 </View> 167 ) 168 }, 169 [error, refetch, onPressRetryLoadMore, _, t.atoms.border_contrast_low], 170 ) 171 172 React.useEffect(() => { 173 if (enabled && scrollElRef.current) { 174 const nativeTag = findNodeHandle(scrollElRef.current) 175 setScrollViewTag(nativeTag) 176 } 177 }, [enabled, scrollElRef, setScrollViewTag]) 178 179 return ( 180 <View testID={testID} style={style}> 181 <List 182 testID={testID ? `${testID}-flatlist` : undefined} 183 ref={scrollElRef} 184 data={items} 185 keyExtractor={(item: any) => item._reactKey || item.uri} 186 renderItem={renderItemInner} 187 refreshing={isPTRing} 188 onRefresh={onRefresh} 189 headerOffset={headerOffset} 190 progressViewOffset={ios(0)} 191 contentContainerStyle={ 192 isNative && {paddingBottom: headerOffset + 100} 193 } 194 indicatorStyle={t.name === 'light' ? 'black' : 'white'} 195 removeClippedSubviews={true} 196 // @ts-ignore our .web version only -prf 197 desktopFixedHeight 198 onEndReached={onEndReached} 199 /> 200 </View> 201 ) 202 }, 203)