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:
@@ -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 (
|
||||||
|
|||||||
@@ -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
@@ -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 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user