use ListFooter for notifications feed

This commit is contained in:
Samuel Newman
2025-09-10 19:59:26 +03:00
parent 5be753ecdf
commit ef253a5516
+33 -64
View File
@@ -1,35 +1,31 @@
import React from 'react' import {useCallback, useMemo, useState} from 'react'
import { import {type ListRenderItemInfo, View} from 'react-native'
ActivityIndicator,
type ListRenderItemInfo,
StyleSheet,
View,
} from 'react-native'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {s} from '#/lib/styles'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useNotificationFeedQuery} from '#/state/queries/notifications/feed' import {
type FeedNotification,
useNotificationFeedQuery,
} from '#/state/queries/notifications/feed'
import {EmptyState} from '#/view/com/util/EmptyState' import {EmptyState} from '#/view/com/util/EmptyState'
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
import {List, type ListProps, type ListRef} from '#/view/com/util/List' import {List, type ListProps, type ListRef} from '#/view/com/util/List'
import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn' import {atoms as a} from '#/alf'
import {ListFooter} from '#/components/Lists'
import {NotificationFeedItem} from './NotificationFeedItem' import {NotificationFeedItem} from './NotificationFeedItem'
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
const LOADING_ITEM = {_reactKey: '__loading__'} const LOADING_ITEM = {_reactKey: '__loading__'}
export function NotificationFeed({ export function NotificationFeed({
filter, filter,
enabled, enabled,
scrollElRef, scrollElRef,
onPressTryAgain,
onScrolledDownChange, onScrolledDownChange,
ListHeaderComponent, ListHeaderComponent,
refreshNotifications, refreshNotifications,
@@ -37,13 +33,12 @@ export function NotificationFeed({
filter: 'all' | 'mentions' filter: 'all' | 'mentions'
enabled: boolean enabled: boolean
scrollElRef?: ListRef scrollElRef?: ListRef
onPressTryAgain?: () => void
onScrolledDownChange: (isScrolledDown: boolean) => void onScrolledDownChange: (isScrolledDown: boolean) => void
ListHeaderComponent?: ListProps['ListHeaderComponent'] ListHeaderComponent?: ListProps['ListHeaderComponent']
refreshNotifications: () => Promise<void> refreshNotifications: () => Promise<void>
}) { }) {
const initialNumToRender = useInitialNumToRender() const initialNumToRender = useInitialNumToRender()
const [isPTRing, setIsPTRing] = React.useState(false) const [isPTRing, setIsPTRing] = useState(false)
const {_} = useLingui() const {_} = useLingui()
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const { const {
@@ -55,6 +50,7 @@ export function NotificationFeed({
hasNextPage, hasNextPage,
isFetchingNextPage, isFetchingNextPage,
fetchNextPage, fetchNextPage,
refetch,
} = useNotificationFeedQuery({ } = useNotificationFeedQuery({
enabled: enabled && !!moderationOpts, enabled: enabled && !!moderationOpts,
filter, filter,
@@ -67,8 +63,8 @@ export function NotificationFeed({
const isEmpty = const isEmpty =
!isFetching && !data?.pages.find(page => page.items.length > 0) !isFetching && !data?.pages.find(page => page.items.length > 0)
const items = React.useMemo(() => { const items = useMemo(() => {
let arr: any[] = [] let arr: (FeedNotification | {_reactKey: string})[] = []
if (isFetched) { if (isFetched) {
if (isEmpty) { if (isEmpty) {
arr = arr.concat([EMPTY_FEED_ITEM]) arr = arr.concat([EMPTY_FEED_ITEM])
@@ -77,16 +73,13 @@ export function NotificationFeed({
arr = arr.concat(page.items) arr = arr.concat(page.items)
} }
} }
if (isError && !isEmpty) {
arr = arr.concat([LOAD_MORE_ERROR_ITEM])
}
} else { } else {
arr.push(LOADING_ITEM) arr.push(LOADING_ITEM)
} }
return arr return arr
}, [isFetched, isError, isEmpty, data]) }, [isFetched, isEmpty, data])
const onRefresh = React.useCallback(async () => { const onRefresh = useCallback(async () => {
try { try {
setIsPTRing(true) setIsPTRing(true)
await refreshNotifications() await refreshNotifications()
@@ -99,7 +92,7 @@ export function NotificationFeed({
} }
}, [refreshNotifications, setIsPTRing]) }, [refreshNotifications, setIsPTRing])
const onEndReached = React.useCallback(async () => { const onEndReached = useCallback(async () => {
if (isFetching || !hasNextPage || isError) return if (isFetching || !hasNextPage || isError) return
try { try {
@@ -109,27 +102,14 @@ export function NotificationFeed({
} }
}, [isFetching, hasNextPage, isError, fetchNextPage]) }, [isFetching, hasNextPage, isError, fetchNextPage])
const onPressRetryLoadMore = React.useCallback(() => { const renderItem = useCallback(
fetchNextPage() ({item, index}: ListRenderItemInfo<FeedNotification>) => {
}, [fetchNextPage])
const renderItem = React.useCallback(
({item, index}: ListRenderItemInfo<any>) => {
if (item === EMPTY_FEED_ITEM) { if (item === EMPTY_FEED_ITEM) {
return ( return (
<EmptyState <EmptyState
icon="bell" icon="bell"
message={_(msg`No notifications yet!`)} message={_(msg`No notifications yet!`)}
style={styles.emptyState} style={[a.py_5xl]}
/>
)
} else if (item === LOAD_MORE_ERROR_ITEM) {
return (
<LoadMoreRetryBtn
label={_(
msg`There was an issue fetching notifications. Tap here to try again.`,
)}
onPress={onPressRetryLoadMore}
/> />
) )
} else if (item === LOADING_ITEM) { } else if (item === LOADING_ITEM) {
@@ -144,28 +124,13 @@ export function NotificationFeed({
/> />
) )
}, },
[moderationOpts, _, onPressRetryLoadMore, filter], [moderationOpts, _, filter],
)
const FeedFooter = React.useCallback(
() =>
isFetchingNextPage ? (
<View style={styles.feedFooter}>
<ActivityIndicator />
</View>
) : (
<View />
),
[isFetchingNextPage],
) )
return ( return (
<View style={s.hContentRegion}> <View style={a.util_screen_outer}>
{error && ( {error && isEmpty && (
<ErrorMessage <ErrorMessage message={cleanError(error)} onPressTryAgain={refetch} />
message={cleanError(error)}
onPressTryAgain={onPressTryAgain}
/>
)} )}
<List <List
testID="notifsFeed" testID="notifsFeed"
@@ -174,13 +139,22 @@ export function NotificationFeed({
keyExtractor={item => item._reactKey} keyExtractor={item => item._reactKey}
renderItem={renderItem} renderItem={renderItem}
ListHeaderComponent={ListHeaderComponent} ListHeaderComponent={ListHeaderComponent}
ListFooterComponent={FeedFooter} ListFooterComponent={
isFetched && !isEmpty ? (
<ListFooter
hasNextPage={hasNextPage}
isFetchingNextPage={isFetchingNextPage}
error={error && !isEmpty ? cleanError(error) : undefined}
onRetry={fetchNextPage}
height={260}
/>
) : null
}
refreshing={isPTRing} refreshing={isPTRing}
onRefresh={onRefresh} onRefresh={onRefresh}
onEndReached={onEndReached} onEndReached={onEndReached}
onEndReachedThreshold={2} onEndReachedThreshold={2}
onScrolledDownChange={onScrolledDownChange} onScrolledDownChange={onScrolledDownChange}
contentContainerStyle={s.contentContainer}
desktopFixedHeight desktopFixedHeight
initialNumToRender={initialNumToRender} initialNumToRender={initialNumToRender}
windowSize={11} windowSize={11}
@@ -190,8 +164,3 @@ export function NotificationFeed({
</View> </View>
) )
} }
const styles = StyleSheet.create({
feedFooter: {paddingTop: 20},
emptyState: {paddingVertical: 40},
})