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