diff --git a/src/state/models/feeds/notifications.ts b/src/state/models/feeds/notifications.ts
index ea3538438b..4daa3ca8de 100644
--- a/src/state/models/feeds/notifications.ts
+++ b/src/state/models/feeds/notifications.ts
@@ -191,6 +191,7 @@ export class NotificationsFeedModel {
isRefreshing = false
hasLoaded = false
error = ''
+ loadMoreError = ''
params: ListNotifications.QueryParams
hasMore = true
loadMoreCursor?: string
@@ -305,10 +306,9 @@ export class NotificationsFeedModel {
await this._appendAll(res)
this._xIdle()
} catch (e: any) {
- this._xIdle() // don't bubble the error to the user
- this.rootStore.log.error('NotificationsView: Failed to load more', {
- params: this.params,
- e,
+ this._xIdle(undefined, e)
+ runInAction(() => {
+ this.hasMore = false
})
}
} finally {
@@ -316,6 +316,15 @@ export class NotificationsFeedModel {
}
})
+ /**
+ * Attempt to load more again after a failure
+ */
+ async retryLoadMore() {
+ this.loadMoreError = ''
+ this.hasMore = true
+ return this.loadMore()
+ }
+
/**
* Load more posts at the start of the notifications
*/
@@ -443,13 +452,20 @@ export class NotificationsFeedModel {
this.error = ''
}
- _xIdle(err?: any) {
+ _xIdle(error?: any, loadMoreError?: any) {
this.isLoading = false
this.isRefreshing = false
this.hasLoaded = true
- this.error = cleanError(err)
- if (err) {
- this.rootStore.log.error('Failed to fetch notifications', err)
+ this.error = cleanError(error)
+ this.loadMoreError = cleanError(loadMoreError)
+ if (error) {
+ this.rootStore.log.error('Failed to fetch notifications', error)
+ }
+ if (loadMoreError) {
+ this.rootStore.log.error(
+ 'Failed to load more notifications',
+ loadMoreError,
+ )
}
}
diff --git a/src/state/models/feeds/posts.ts b/src/state/models/feeds/posts.ts
index 9e593f3134..0046f97812 100644
--- a/src/state/models/feeds/posts.ts
+++ b/src/state/models/feeds/posts.ts
@@ -213,6 +213,7 @@ export class PostsFeedModel {
hasNewLatest = false
hasLoaded = false
error = ''
+ loadMoreError = ''
params: GetTimeline.QueryParams | GetAuthorFeed.QueryParams
hasMore = true
loadMoreCursor: string | undefined
@@ -382,18 +383,25 @@ export class PostsFeedModel {
await this._appendAll(res)
this._xIdle()
} catch (e: any) {
- this._xIdle() // don't bubble the error to the user
- this.rootStore.log.error('FeedView: Failed to load more', {
- params: this.params,
- e,
+ this._xIdle(undefined, e)
+ runInAction(() => {
+ this.hasMore = false
})
- this.hasMore = false
}
} finally {
this.lock.release()
}
})
+ /**
+ * Attempt to load more again after a failure
+ */
+ async retryLoadMore() {
+ this.loadMoreError = ''
+ this.hasMore = true
+ return this.loadMore()
+ }
+
/**
* Update content in-place
*/
@@ -503,13 +511,20 @@ export class PostsFeedModel {
this.error = ''
}
- _xIdle(err?: any) {
+ _xIdle(error?: any, loadMoreError?: any) {
this.isLoading = false
this.isRefreshing = false
this.hasLoaded = true
- this.error = cleanError(err)
- if (err) {
- this.rootStore.log.error('Posts feed request failed', err)
+ this.error = cleanError(error)
+ this.loadMoreError = cleanError(loadMoreError)
+ if (error) {
+ this.rootStore.log.error('Posts feed request failed', error)
+ }
+ if (loadMoreError) {
+ this.rootStore.log.error(
+ 'Posts feed load-more request failed',
+ loadMoreError,
+ )
}
}
diff --git a/src/view/com/notifications/Feed.tsx b/src/view/com/notifications/Feed.tsx
index 83fa0a9908..2196b34691 100644
--- a/src/view/com/notifications/Feed.tsx
+++ b/src/view/com/notifications/Feed.tsx
@@ -6,12 +6,14 @@ import {NotificationsFeedModel} from 'state/models/feeds/notifications'
import {FeedItem} from './FeedItem'
import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
import {ErrorMessage} from '../util/error/ErrorMessage'
+import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {EmptyState} from '../util/EmptyState'
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
+const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
export const Feed = observer(function Feed({
view,
@@ -34,8 +36,11 @@ export const Feed = observer(function Feed({
feedItems = view.notifications
}
}
+ if (view.loadMoreError) {
+ feedItems = (feedItems || []).concat([LOAD_MORE_ERROR_ITEM])
+ }
return feedItems
- }, [view.hasLoaded, view.isEmpty, view.notifications])
+ }, [view.hasLoaded, view.isEmpty, view.notifications, view.loadMoreError])
const onRefresh = React.useCallback(async () => {
try {
@@ -45,6 +50,7 @@ export const Feed = observer(function Feed({
view.rootStore.log.error('Failed to refresh notifications feed', err)
}
}, [view])
+
const onEndReached = React.useCallback(async () => {
try {
await view.loadMore()
@@ -53,22 +59,36 @@ export const Feed = observer(function Feed({
}
}, [view])
+ const onPressRetryLoadMore = React.useCallback(() => {
+ view.retryLoadMore()
+ }, [view])
+
// TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf
// VirtualizedList: You have a large list that is slow to update - make sure your
// renderItem function renders components that follow React performance best practices
// like PureComponent, shouldComponentUpdate, etc
- const renderItem = React.useCallback(({item}: {item: any}) => {
- if (item === EMPTY_FEED_ITEM) {
- return (
-
- )
- }
- return
- }, [])
+ const renderItem = React.useCallback(
+ ({item}: {item: any}) => {
+ if (item === EMPTY_FEED_ITEM) {
+ return (
+
+ )
+ } else if (item === LOAD_MORE_ERROR_ITEM) {
+ return (
+
+ )
+ }
+ return
+ },
+ [onPressRetryLoadMore],
+ )
const FeedFooter = React.useCallback(
() =>
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx
index ddebe5e093..1721247295 100644
--- a/src/view/com/posts/Feed.tsx
+++ b/src/view/com/posts/Feed.tsx
@@ -13,6 +13,7 @@ import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {PostsFeedModel} from 'state/models/feeds/posts'
import {FeedSlice} from './FeedSlice'
+import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
import {s} from 'lib/styles'
import {useAnalytics} from 'lib/analytics'
@@ -21,6 +22,7 @@ import {usePalette} from 'lib/hooks/usePalette'
const LOADING_ITEM = {_reactKey: '__loading__'}
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
const ERROR_ITEM = {_reactKey: '__error__'}
+const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
export const Feed = observer(function Feed({
feed,
@@ -58,11 +60,21 @@ export const Feed = observer(function Feed({
} else {
feedItems = feedItems.concat(feed.slices)
}
+ if (feed.loadMoreError) {
+ feedItems = feedItems.concat([LOAD_MORE_ERROR_ITEM])
+ }
} else if (feed.isLoading) {
feedItems = feedItems.concat([LOADING_ITEM])
}
return feedItems
- }, [feed.hasError, feed.hasLoaded, feed.isLoading, feed.isEmpty, feed.slices])
+ }, [
+ feed.hasError,
+ feed.hasLoaded,
+ feed.isLoading,
+ feed.isEmpty,
+ feed.slices,
+ feed.loadMoreError,
+ ])
// events
// =
@@ -87,6 +99,10 @@ export const Feed = observer(function Feed({
}
}, [feed, track])
+ const onPressRetryLoadMore = React.useCallback(() => {
+ feed.retryLoadMore()
+ }, [feed])
+
// rendering
// =
@@ -104,12 +120,25 @@ export const Feed = observer(function Feed({
onPressTryAgain={onPressTryAgain}
/>
)
+ } else if (item === LOAD_MORE_ERROR_ITEM) {
+ return (
+
+ )
} else if (item === LOADING_ITEM) {
return
}
return
},
- [feed, onPressTryAgain, showPostFollowBtn, renderEmptyState],
+ [
+ feed,
+ onPressTryAgain,
+ onPressRetryLoadMore,
+ showPostFollowBtn,
+ renderEmptyState,
+ ],
)
const FeedFooter = React.useCallback(
diff --git a/src/view/com/util/LoadMoreRetryBtn.tsx b/src/view/com/util/LoadMoreRetryBtn.tsx
new file mode 100644
index 0000000000..a2e9838b36
--- /dev/null
+++ b/src/view/com/util/LoadMoreRetryBtn.tsx
@@ -0,0 +1,44 @@
+import React from 'react'
+import {StyleSheet} from 'react-native'
+import {
+ FontAwesomeIcon,
+ FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
+import {Button} from './forms/Button'
+import {Text} from './text/Text'
+import {usePalette} from 'lib/hooks/usePalette'
+
+export function LoadMoreRetryBtn({
+ label,
+ onPress,
+}: {
+ label: string
+ onPress: () => void
+}) {
+ const pal = usePalette('default')
+ return (
+
+ )
+}
+
+const styles = StyleSheet.create({
+ loadMoreRetry: {
+ flexDirection: 'row',
+ gap: 14,
+ alignItems: 'center',
+ borderRadius: 0,
+ marginTop: 1,
+ paddingVertical: 12,
+ paddingHorizontal: 20,
+ },
+ label: {
+ flex: 1,
+ },
+})
diff --git a/src/view/index.ts b/src/view/index.ts
index 17e9dbbed8..47a5f8acfd 100644
--- a/src/view/index.ts
+++ b/src/view/index.ts
@@ -1,6 +1,6 @@
import {library} from '@fortawesome/fontawesome-svg-core'
-import {faAddressCard} from '@fortawesome/free-regular-svg-icons/faAddressCard'
+import {faAddressCard} from '@fortawesome/free-regular-svg-icons'
import {faAngleDown} from '@fortawesome/free-solid-svg-icons/faAngleDown'
import {faAngleLeft} from '@fortawesome/free-solid-svg-icons/faAngleLeft'
import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight'
@@ -14,6 +14,7 @@ import {
} from '@fortawesome/free-solid-svg-icons'
import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket'
import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare'
+import {faArrowRotateLeft} from '@fortawesome/free-solid-svg-icons/faArrowRotateLeft'
import {faArrowsRotate} from '@fortawesome/free-solid-svg-icons/faArrowsRotate'
import {faAt} from '@fortawesome/free-solid-svg-icons/faAt'
import {faBars} from '@fortawesome/free-solid-svg-icons/faBars'
@@ -86,6 +87,7 @@ export function setup() {
faArrowRightFromBracket,
faArrowUpFromBracket,
faArrowUpRightFromSquare,
+ faArrowRotateLeft,
faArrowsRotate,
faAt,
faBars,