A group of notifications fixes (#227)

* Fix: don't group together notifications that can't visually be grouped (close #221)

* Mark all notifications read on PTR

* Small optimization: useCallback and useMemo in posts feed

* Add loading spinner to footer of notifications (close #222)
This commit is contained in:
Paul Frazee
2023-02-21 10:32:29 -06:00
committed by GitHub
parent 1861f95a1f
commit f55fbe73c7
3 changed files with 112 additions and 69 deletions
+2 -2
View File
@@ -14,7 +14,7 @@ import {RootStoreModel} from './root-store'
import {PostThreadViewModel} from './post-thread-view' import {PostThreadViewModel} from './post-thread-view'
import {cleanError} from '../../lib/strings' import {cleanError} from '../../lib/strings'
const UNGROUPABLE_REASONS = ['assertion'] const GROUPABLE_REASONS = ['vote', 'repost', 'follow']
const PAGE_SIZE = 30 const PAGE_SIZE = 30
const MS_1HR = 1e3 * 60 * 60 const MS_1HR = 1e3 * 60 * 60
const MS_2DAY = MS_1HR * 48 const MS_2DAY = MS_1HR * 48
@@ -539,7 +539,7 @@ function groupNotifications(
for (const item of items) { for (const item of items) {
const ts = +new Date(item.indexedAt) const ts = +new Date(item.indexedAt)
let grouped = false let grouped = false
if (!UNGROUPABLE_REASONS.includes(item.reason)) { if (GROUPABLE_REASONS.includes(item.reason)) {
for (const item2 of items2) { for (const item2 of items2) {
const ts2 = +new Date(item2.indexedAt) const ts2 = +new Date(item2.indexedAt)
if ( if (
+46 -25
View File
@@ -1,6 +1,6 @@
import React, {MutableRefObject} from 'react' import React, {MutableRefObject} from 'react'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {FlatList, StyleSheet, View} from 'react-native' import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native'
import {NotificationsViewModel} from '../../../state/models/notifications-view' import {NotificationsViewModel} from '../../../state/models/notifications-view'
import {FeedItem} from './FeedItem' import {FeedItem} from './FeedItem'
import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
@@ -22,11 +22,39 @@ export const Feed = observer(function Feed({
onPressTryAgain?: () => void onPressTryAgain?: () => void
onScroll?: OnScrollCb onScroll?: OnScrollCb
}) { }) {
const data = React.useMemo(() => {
let feedItems
if (view.hasLoaded) {
if (view.isEmpty) {
feedItems = [EMPTY_FEED_ITEM]
} else {
feedItems = view.notifications
}
}
return feedItems
}, [view.hasLoaded, view.isEmpty, view.notifications])
const onRefresh = React.useCallback(async () => {
try {
await view.refresh()
await view.markAllRead()
} catch (err) {
view.rootStore.log.error('Failed to refresh notifications feed', err)
}
}, [view])
const onEndReached = React.useCallback(async () => {
try {
await view.loadMore()
} catch (err) {
view.rootStore.log.error('Failed to load more notifications', err)
}
}, [view])
// TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf // 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 // VirtualizedList: You have a large list that is slow to update - make sure your
// renderItem function renders components that follow React performance best practices // renderItem function renders components that follow React performance best practices
// like PureComponent, shouldComponentUpdate, etc // like PureComponent, shouldComponentUpdate, etc
const renderItem = ({item}: {item: any}) => { const renderItem = React.useCallback(({item}: {item: any}) => {
if (item === EMPTY_FEED_ITEM) { if (item === EMPTY_FEED_ITEM) {
return ( return (
<EmptyState <EmptyState
@@ -37,29 +65,20 @@ export const Feed = observer(function Feed({
) )
} }
return <FeedItem item={item} /> return <FeedItem item={item} />
} }, [])
const onRefresh = async () => {
try { const FeedFooter = React.useCallback(
await view.refresh() () =>
} catch (err) { view.isLoading ? (
view.rootStore.log.error('Failed to refresh notifications feed', err) <View style={styles.feedFooter}>
} <ActivityIndicator />
} </View>
const onEndReached = async () => { ) : (
try { <View />
await view.loadMore() ),
} catch (err) { [view],
view.rootStore.log.error('Failed to load more notifications', err) )
}
}
let data
if (view.hasLoaded) {
if (view.isEmpty) {
data = [EMPTY_FEED_ITEM]
} else {
data = view.notifications
}
}
return ( return (
<View style={s.h100pct}> <View style={s.h100pct}>
{view.isLoading && !data && <NotificationFeedLoadingPlaceholder />} {view.isLoading && !data && <NotificationFeedLoadingPlaceholder />}
@@ -72,6 +91,7 @@ export const Feed = observer(function Feed({
data={data} data={data}
keyExtractor={item => item._reactKey} keyExtractor={item => item._reactKey}
renderItem={renderItem} renderItem={renderItem}
ListFooterComponent={FeedFooter}
refreshing={view.isRefreshing} refreshing={view.isRefreshing}
onRefresh={onRefresh} onRefresh={onRefresh}
onEndReached={onEndReached} onEndReached={onEndReached}
@@ -84,5 +104,6 @@ export const Feed = observer(function Feed({
}) })
const styles = StyleSheet.create({ const styles = StyleSheet.create({
feedFooter: {paddingTop: 20},
emptyState: {paddingVertical: 40}, emptyState: {paddingVertical: 40},
}) })
+64 -42
View File
@@ -40,27 +40,25 @@ export const Feed = observer(function Feed({
const {track} = useAnalytics() const {track} = useAnalytics()
const [isRefreshing, setIsRefreshing] = React.useState(false) const [isRefreshing, setIsRefreshing] = React.useState(false)
// TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf const data = React.useMemo(() => {
// VirtualizedList: You have a large list that is slow to update - make sure your let feedItems: any[] = []
// renderItem function renders components that follow React performance best practices if (feed.hasError) {
// like PureComponent, shouldComponentUpdate, etc feedItems = feedItems.concat([ERROR_FEED_ITEM])
const renderItem = ({item}: {item: any}) => {
if (item === EMPTY_FEED_ITEM) {
return (
<EmptyState
icon="bars"
message="This feed is empty!"
style={styles.emptyState}
/>
)
} else if (item === ERROR_FEED_ITEM) {
return (
<ErrorMessage message={feed.error} onPressTryAgain={onPressTryAgain} />
)
} }
return <FeedItem item={item} /> if (feed.hasLoaded) {
} if (feed.isEmpty) {
const onRefresh = async () => { feedItems = feedItems.concat([EMPTY_FEED_ITEM])
} else {
feedItems = feedItems.concat(feed.feed)
}
}
return feedItems
}, [feed.hasError, feed.hasLoaded, feed.isEmpty, feed.feed])
// events
// =
const onRefresh = React.useCallback(async () => {
track('Feed:onRefresh') track('Feed:onRefresh')
setIsRefreshing(true) setIsRefreshing(true)
try { try {
@@ -69,34 +67,58 @@ export const Feed = observer(function Feed({
feed.rootStore.log.error('Failed to refresh posts feed', err) feed.rootStore.log.error('Failed to refresh posts feed', err)
} }
setIsRefreshing(false) setIsRefreshing(false)
} }, [feed, track, setIsRefreshing])
const onEndReached = async () => { const onEndReached = React.useCallback(async () => {
track('Feed:onEndReached') track('Feed:onEndReached')
try { try {
await feed.loadMore() await feed.loadMore()
} catch (err) { } catch (err) {
feed.rootStore.log.error('Failed to load more posts', err) feed.rootStore.log.error('Failed to load more posts', err)
} }
} }, [feed, track])
let data: any[] = []
if (feed.hasError) { // rendering
data = data.concat([ERROR_FEED_ITEM]) // =
}
if (feed.hasLoaded) { // TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf
if (feed.isEmpty) { // VirtualizedList: You have a large list that is slow to update - make sure your
data = data.concat([EMPTY_FEED_ITEM]) // renderItem function renders components that follow React performance best practices
} else { // like PureComponent, shouldComponentUpdate, etc
data = data.concat(feed.feed) const renderItem = React.useCallback(
} ({item}: {item: any}) => {
} if (item === EMPTY_FEED_ITEM) {
const FeedFooter = () => return (
feed.isLoading ? ( <EmptyState
<View style={styles.feedFooter}> icon="bars"
<ActivityIndicator /> message="This feed is empty!"
</View> style={styles.emptyState}
) : ( />
<View /> )
) } else if (item === ERROR_FEED_ITEM) {
return (
<ErrorMessage
message={feed.error}
onPressTryAgain={onPressTryAgain}
/>
)
}
return <FeedItem item={item} />
},
[feed, onPressTryAgain],
)
const FeedFooter = React.useCallback(
() =>
feed.isLoading ? (
<View style={styles.feedFooter}>
<ActivityIndicator />
</View>
) : (
<View />
),
[feed],
)
return ( return (
<View testID={testID} style={style}> <View testID={testID} style={style}>
{feed.isLoading && data.length === 0 && ( {feed.isLoading && data.length === 0 && (