mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {
3 ActivityIndicator,
4 ListRenderItemInfo,
5 StyleSheet,
6 View,
7} from 'react-native'
8import {msg} from '@lingui/macro'
9import {useLingui} from '@lingui/react'
10
11import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
12import {cleanError} from '#/lib/strings/errors'
13import {s} from '#/lib/styles'
14import {logger} from '#/logger'
15import {useModerationOpts} from '#/state/preferences/moderation-opts'
16import {useNotificationFeedQuery} from '#/state/queries/notifications/feed'
17import {EmptyState} from '#/view/com/util/EmptyState'
18import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
19import {List, ListRef} from '#/view/com/util/List'
20import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
21import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
22import {NotificationFeedItem} from './NotificationFeedItem'
23
24const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
25const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
26const LOADING_ITEM = {_reactKey: '__loading__'}
27
28export function NotificationFeed({
29 filter,
30 enabled,
31 scrollElRef,
32 onPressTryAgain,
33 onScrolledDownChange,
34 ListHeaderComponent,
35 refreshNotifications,
36}: {
37 filter: 'all' | 'mentions'
38 enabled: boolean
39 scrollElRef?: ListRef
40 onPressTryAgain?: () => void
41 onScrolledDownChange: (isScrolledDown: boolean) => void
42 ListHeaderComponent?: () => JSX.Element
43 refreshNotifications: () => Promise<void>
44}) {
45 const initialNumToRender = useInitialNumToRender()
46 const [isPTRing, setIsPTRing] = React.useState(false)
47 const {_} = useLingui()
48 const moderationOpts = useModerationOpts()
49 const {
50 data,
51 isFetching,
52 isFetched,
53 isError,
54 error,
55 hasNextPage,
56 isFetchingNextPage,
57 fetchNextPage,
58 } = useNotificationFeedQuery({
59 enabled: enabled && !!moderationOpts,
60 filter,
61 })
62 const isEmpty = !isFetching && !data?.pages[0]?.items.length
63
64 const items = React.useMemo(() => {
65 let arr: any[] = []
66 if (isFetched) {
67 if (isEmpty) {
68 arr = arr.concat([EMPTY_FEED_ITEM])
69 } else if (data) {
70 for (const page of data?.pages) {
71 arr = arr.concat(page.items)
72 }
73 }
74 if (isError && !isEmpty) {
75 arr = arr.concat([LOAD_MORE_ERROR_ITEM])
76 }
77 } else {
78 arr.push(LOADING_ITEM)
79 }
80 return arr
81 }, [isFetched, isError, isEmpty, data])
82
83 const onRefresh = React.useCallback(async () => {
84 try {
85 setIsPTRing(true)
86 await refreshNotifications()
87 } catch (err) {
88 logger.error('Failed to refresh notifications feed', {
89 message: err,
90 })
91 } finally {
92 setIsPTRing(false)
93 }
94 }, [refreshNotifications, setIsPTRing])
95
96 const onEndReached = React.useCallback(async () => {
97 if (isFetching || !hasNextPage || isError) return
98
99 try {
100 await fetchNextPage()
101 } catch (err) {
102 logger.error('Failed to load more notifications', {message: err})
103 }
104 }, [isFetching, hasNextPage, isError, fetchNextPage])
105
106 const onPressRetryLoadMore = React.useCallback(() => {
107 fetchNextPage()
108 }, [fetchNextPage])
109
110 const renderItem = React.useCallback(
111 ({item, index}: ListRenderItemInfo<any>) => {
112 if (item === EMPTY_FEED_ITEM) {
113 return (
114 <EmptyState
115 icon="bell"
116 message={_(msg`No notifications yet!`)}
117 style={styles.emptyState}
118 />
119 )
120 } else if (item === LOAD_MORE_ERROR_ITEM) {
121 return (
122 <LoadMoreRetryBtn
123 label={_(
124 msg`There was an issue fetching notifications. Tap here to try again.`,
125 )}
126 onPress={onPressRetryLoadMore}
127 />
128 )
129 } else if (item === LOADING_ITEM) {
130 return <NotificationFeedLoadingPlaceholder />
131 }
132 return (
133 <NotificationFeedItem
134 highlightUnread={filter === 'all'}
135 item={item}
136 moderationOpts={moderationOpts!}
137 hideTopBorder={index === 0}
138 />
139 )
140 },
141 [moderationOpts, _, onPressRetryLoadMore, filter],
142 )
143
144 const FeedFooter = React.useCallback(
145 () =>
146 isFetchingNextPage ? (
147 <View style={styles.feedFooter}>
148 <ActivityIndicator />
149 </View>
150 ) : (
151 <View />
152 ),
153 [isFetchingNextPage],
154 )
155
156 return (
157 <View style={s.hContentRegion}>
158 {error && (
159 <ErrorMessage
160 message={cleanError(error)}
161 onPressTryAgain={onPressTryAgain}
162 />
163 )}
164 <List
165 testID="notifsFeed"
166 ref={scrollElRef}
167 data={items}
168 keyExtractor={item => item._reactKey}
169 renderItem={renderItem}
170 ListHeaderComponent={ListHeaderComponent}
171 ListFooterComponent={FeedFooter}
172 refreshing={isPTRing}
173 onRefresh={onRefresh}
174 onEndReached={onEndReached}
175 onEndReachedThreshold={2}
176 onScrolledDownChange={onScrolledDownChange}
177 contentContainerStyle={s.contentContainer}
178 desktopFixedHeight
179 initialNumToRender={initialNumToRender}
180 windowSize={11}
181 sideBorders={false}
182 removeClippedSubviews={true}
183 />
184 </View>
185 )
186}
187
188const styles = StyleSheet.create({
189 feedFooter: {paddingTop: 20},
190 emptyState: {paddingVertical: 40},
191})