Feed and notifs improvements (#498)

* Reduce frequency of the notifications sync

* Reduce frequency of home feed polling

* Ensure loading spinner is visible in notifications

* Render notifications loading spinner in the flatlist

* Fixes and performance improvements to notifications

* Render 30+ on notifications if at max

* Fix issue with repeating posts in home feed

* Dont check for unread notifs if we're already at max
This commit is contained in:
Paul Frazee 2023-04-19 20:11:10 -05:00 committed by GitHub
parent b24ba3adc9
commit 04e0ebe8fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 130 additions and 150 deletions

View file

@ -14,6 +14,7 @@ import {usePalette} from 'lib/hooks/usePalette'
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
const LOADING_SPINNER = {_reactKey: '__loading_spinner__'}
export const Feed = observer(function Feed({
view,
@ -27,28 +28,42 @@ export const Feed = observer(function Feed({
onScroll?: OnScrollCb
}) {
const pal = usePalette('default')
const [isPTRing, setIsPTRing] = React.useState(false)
const data = React.useMemo(() => {
let feedItems
let feedItems: any[] = []
if (view.isRefreshing && !isPTRing) {
feedItems = [LOADING_SPINNER]
}
if (view.hasLoaded) {
if (view.isEmpty) {
feedItems = [EMPTY_FEED_ITEM]
feedItems = feedItems.concat([EMPTY_FEED_ITEM])
} else {
feedItems = view.notifications
feedItems = feedItems.concat(view.notifications)
}
}
if (view.loadMoreError) {
feedItems = (feedItems || []).concat([LOAD_MORE_ERROR_ITEM])
}
return feedItems
}, [view.hasLoaded, view.isEmpty, view.notifications, view.loadMoreError])
}, [
view.hasLoaded,
view.isEmpty,
view.notifications,
view.loadMoreError,
view.isRefreshing,
isPTRing,
])
const onRefresh = React.useCallback(async () => {
try {
setIsPTRing(true)
await view.refresh()
} catch (err) {
view.rootStore.log.error('Failed to refresh notifications feed', err)
} finally {
setIsPTRing(false)
}
}, [view])
}, [view, setIsPTRing])
const onEndReached = React.useCallback(async () => {
try {
@ -83,6 +98,12 @@ export const Feed = observer(function Feed({
onPress={onPressRetryLoadMore}
/>
)
} else if (item === LOADING_SPINNER) {
return (
<View style={styles.loading}>
<ActivityIndicator size="small" />
</View>
)
}
return <FeedItem item={item} />
},
@ -104,7 +125,9 @@ export const Feed = observer(function Feed({
return (
<View style={s.hContentRegion}>
<CenteredView>
{view.isLoading && !data && <NotificationFeedLoadingPlaceholder />}
{view.isLoading && !data.length && (
<NotificationFeedLoadingPlaceholder />
)}
{view.hasError && (
<ErrorMessage
message={view.error}
@ -112,7 +135,7 @@ export const Feed = observer(function Feed({
/>
)}
</CenteredView>
{data && (
{data.length && (
<FlatList
ref={scrollElRef}
data={data}
@ -121,7 +144,7 @@ export const Feed = observer(function Feed({
ListFooterComponent={FeedFooter}
refreshControl={
<RefreshControl
refreshing={view.isRefreshing}
refreshing={isPTRing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
@ -138,6 +161,9 @@ export const Feed = observer(function Feed({
})
const styles = StyleSheet.create({
loading: {
paddingVertical: 20,
},
feedFooter: {paddingTop: 20},
emptyState: {paddingVertical: 40},
})

View file

@ -20,6 +20,7 @@ import {ComposeIcon2} from 'lib/icons'
import {isDesktopWeb} from 'platform/detection'
const HEADER_OFFSET = isDesktopWeb ? 50 : 40
const POLL_FREQ = 30e3 // 30sec
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
export const HomeScreen = withAuthRequired((_opts: Props) => {
@ -150,7 +151,7 @@ const FeedPage = observer(
React.useCallback(() => {
const softResetSub = store.onScreenSoftReset(onSoftReset)
const feedCleanup = feed.registerListeners()
const pollInterval = setInterval(doPoll, 15e3)
const pollInterval = setInterval(doPoll, POLL_FREQ)
screen('Feed')
store.log.debug('HomeScreen: Updating feed')
@ -176,8 +177,8 @@ const FeedPage = observer(
}, [feed])
const onPressLoadLatest = React.useCallback(() => {
feed.resetToLatest()
scrollToTop()
feed.refresh()
}, [feed, scrollToTop])
return (

View file

@ -38,8 +38,8 @@ export const NotificationsScreen = withAuthRequired(
}, [scrollElRef])
const onPressLoadLatest = React.useCallback(() => {
store.me.notifications.processQueue()
scrollToTop()
store.me.notifications.refresh()
}, [store, scrollToTop])
// on-visible setup
@ -49,13 +49,12 @@ export const NotificationsScreen = withAuthRequired(
store.shell.setMinimalShellMode(false)
store.log.debug('NotificationsScreen: Updating feed')
const softResetSub = store.onScreenSoftReset(onPressLoadLatest)
store.me.notifications.syncQueue()
store.me.notifications.update()
screen('Notifications')
return () => {
softResetSub.remove()
store.me.notifications.markAllUnqueuedRead()
store.me.notifications.markAllRead()
}
}, [store, screen, onPressLoadLatest]),
)

View file

@ -203,9 +203,7 @@ export const DrawerContent = observer(() => {
)
}
label="Notifications"
count={
store.me.notifications.unreadCount + store.invitedUsers.numNotifs
}
count={store.me.notifications.unreadCountLabel}
bold={isAtNotifications}
onPress={onPressNotifications}
/>
@ -291,7 +289,7 @@ function MenuItem({
}: {
icon: JSX.Element
label: string
count?: number
count?: string
bold?: boolean
onPress: () => void
}) {
@ -307,14 +305,14 @@ function MenuItem({
<View
style={[
styles.menuItemCount,
count > 99
count.length > 2
? styles.menuItemCountHundreds
: count > 9
: count.length > 1
? styles.menuItemCountTens
: undefined,
]}>
<Text style={styles.menuItemCountLabel} numberOfLines={1}>
{count > 999 ? `${Math.round(count / 1000)}k` : count}
{count}
</Text>
</View>
) : undefined}

View file

@ -132,9 +132,7 @@ export const BottomBar = observer(({navigation}: BottomTabBarProps) => {
)
}
onPress={onPressNotifications}
notificationCount={
store.me.notifications.unreadCount + store.invitedUsers.numNotifs
}
notificationCount={store.me.notifications.unreadCountLabel}
/>
<Btn
testID="bottomBarProfileBtn"
@ -170,7 +168,7 @@ function Btn({
}: {
testID?: string
icon: JSX.Element
notificationCount?: number
notificationCount?: string
onPress?: (event: GestureResponderEvent) => void
onLongPress?: (event: GestureResponderEvent) => void
}) {

View file

@ -70,7 +70,7 @@ function BackBtn() {
}
interface NavItemProps {
count?: number
count?: string
href: string
icon: JSX.Element
iconFilled: JSX.Element
@ -95,7 +95,7 @@ const NavItem = observer(
<Link href={href} style={styles.navItem}>
<View style={[styles.navItemIconWrapper]}>
{isCurrent ? iconFilled : icon}
{typeof count === 'number' && count > 0 && (
{typeof count === 'string' && count && (
<Text type="button" style={styles.navItemCount}>
{count}
</Text>
@ -162,9 +162,7 @@ export const DesktopLeftNav = observer(function DesktopLeftNav() {
/>
<NavItem
href="/notifications"
count={
store.me.notifications.unreadCount + store.invitedUsers.numNotifs
}
count={store.me.notifications.unreadCountLabel}
icon={<BellIcon strokeWidth={2} size={24} style={pal.text} />}
iconFilled={
<BellIconSolid strokeWidth={1.5} size={24} style={pal.text} />