Use a persistent notifications model to improve load times of the notifications view
This commit is contained in:
parent
1aa1f77049
commit
9051aecdcb
4 changed files with 49 additions and 41 deletions
|
@ -1,6 +1,7 @@
|
||||||
import {makeAutoObservable, runInAction} from 'mobx'
|
import {makeAutoObservable, runInAction} from 'mobx'
|
||||||
import {RootStoreModel} from './root-store'
|
import {RootStoreModel} from './root-store'
|
||||||
import {MembershipsViewModel} from './memberships-view'
|
import {MembershipsViewModel} from './memberships-view'
|
||||||
|
import {NotificationsViewModel} from './notifications-view'
|
||||||
|
|
||||||
export class MeModel {
|
export class MeModel {
|
||||||
did?: string
|
did?: string
|
||||||
|
@ -9,9 +10,11 @@ export class MeModel {
|
||||||
description?: string
|
description?: string
|
||||||
notificationCount: number = 0
|
notificationCount: number = 0
|
||||||
memberships?: MembershipsViewModel
|
memberships?: MembershipsViewModel
|
||||||
|
notifications: NotificationsViewModel
|
||||||
|
|
||||||
constructor(public rootStore: RootStoreModel) {
|
constructor(public rootStore: RootStoreModel) {
|
||||||
makeAutoObservable(this, {rootStore: false}, {autoBind: true})
|
makeAutoObservable(this, {rootStore: false}, {autoBind: true})
|
||||||
|
this.notifications = new NotificationsViewModel(this.rootStore, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
|
@ -43,7 +46,12 @@ export class MeModel {
|
||||||
this.memberships = new MembershipsViewModel(this.rootStore, {
|
this.memberships = new MembershipsViewModel(this.rootStore, {
|
||||||
actor: this.did,
|
actor: this.did,
|
||||||
})
|
})
|
||||||
await this.memberships?.setup()
|
await this.memberships?.setup().catch(e => {
|
||||||
|
console.error('Failed to setup memberships model', e)
|
||||||
|
})
|
||||||
|
await this.notifications.setup().catch(e => {
|
||||||
|
console.error('Failed to setup notifications model', e)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
this.clear()
|
this.clear()
|
||||||
}
|
}
|
||||||
|
@ -56,7 +64,12 @@ export class MeModel {
|
||||||
async fetchStateUpdate() {
|
async fetchStateUpdate() {
|
||||||
const res = await this.rootStore.api.app.bsky.notification.getCount()
|
const res = await this.rootStore.api.app.bsky.notification.getCount()
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
const newNotifications = this.notificationCount !== res.data.count
|
||||||
this.notificationCount = res.data.count
|
this.notificationCount = res.data.count
|
||||||
|
if (newNotifications) {
|
||||||
|
// trigger pre-emptive fetch on new notifications
|
||||||
|
this.notifications.refresh()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -203,7 +203,6 @@ export class NotificationsViewModel {
|
||||||
await this._pendingWork()
|
await this._pendingWork()
|
||||||
this._loadPromise = this._initialLoad(isRefreshing)
|
this._loadPromise = this._initialLoad(isRefreshing)
|
||||||
await this._loadPromise
|
await this._loadPromise
|
||||||
this._updateReadState()
|
|
||||||
this._loadPromise = undefined
|
this._loadPromise = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +239,20 @@ export class NotificationsViewModel {
|
||||||
this._updatePromise = undefined
|
this._updatePromise = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update read/unread state
|
||||||
|
*/
|
||||||
|
async updateReadState() {
|
||||||
|
try {
|
||||||
|
await this.rootStore.api.app.bsky.notification.updateSeen({
|
||||||
|
seenAt: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
this.rootStore.me.clearNotificationCount()
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Failed to update notifications read state', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// state transitions
|
// state transitions
|
||||||
// =
|
// =
|
||||||
|
|
||||||
|
@ -329,11 +342,10 @@ export class NotificationsViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _replaceAll(res: ListNotifications.Response) {
|
private async _replaceAll(res: ListNotifications.Response) {
|
||||||
this.notifications.length = 0
|
return this._appendAll(res, true)
|
||||||
return this._appendAll(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _appendAll(res: ListNotifications.Response) {
|
private async _appendAll(res: ListNotifications.Response, replace = false) {
|
||||||
this.loadMoreCursor = res.data.cursor
|
this.loadMoreCursor = res.data.cursor
|
||||||
this.hasMore = !!this.loadMoreCursor
|
this.hasMore = !!this.loadMoreCursor
|
||||||
let counter = this.notifications.length
|
let counter = this.notifications.length
|
||||||
|
@ -357,7 +369,11 @@ export class NotificationsViewModel {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.notifications = this.notifications.concat(itemModels)
|
if (replace) {
|
||||||
|
this.notifications = itemModels
|
||||||
|
} else {
|
||||||
|
this.notifications = this.notifications.concat(itemModels)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,17 +390,6 @@ export class NotificationsViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _updateReadState() {
|
|
||||||
try {
|
|
||||||
await this.rootStore.api.app.bsky.notification.updateSeen({
|
|
||||||
seenAt: new Date().toISOString(),
|
|
||||||
})
|
|
||||||
this.rootStore.me.clearNotificationCount()
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Failed to update notifications read state', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function groupNotifications(
|
function groupNotifications(
|
||||||
|
|
|
@ -32,7 +32,7 @@ export const Feed = observer(function Feed({
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
{view.isLoading && !view.isRefreshing && !view.hasContent && (
|
{view.isLoading && !view.isRefreshing && (
|
||||||
<NotificationFeedLoadingPlaceholder />
|
<NotificationFeedLoadingPlaceholder />
|
||||||
)}
|
)}
|
||||||
{view.hasError && (
|
{view.hasError && (
|
||||||
|
@ -43,7 +43,7 @@ export const Feed = observer(function Feed({
|
||||||
onPressTryAgain={onPressTryAgain}
|
onPressTryAgain={onPressTryAgain}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{view.hasContent && (
|
{view.hasLoaded && (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={view.notifications}
|
data={view.notifications}
|
||||||
keyExtractor={item => item._reactKey}
|
keyExtractor={item => item._reactKey}
|
||||||
|
@ -53,7 +53,7 @@ export const Feed = observer(function Feed({
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{view.isEmpty && (
|
{view.hasLoaded && view.isEmpty && (
|
||||||
<EmptyState icon="bell" message="No notifications yet!" />
|
<EmptyState icon="bell" message="No notifications yet!" />
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -7,43 +7,33 @@ import {NotificationsViewModel} from '../../state/models/notifications-view'
|
||||||
import {ScreenParams} from '../routes'
|
import {ScreenParams} from '../routes'
|
||||||
|
|
||||||
export const Notifications = ({navIdx, visible}: ScreenParams) => {
|
export const Notifications = ({navIdx, visible}: ScreenParams) => {
|
||||||
const [hasSetup, setHasSetup] = useState<boolean>(false)
|
|
||||||
const [notesView, setNotesView] = useState<
|
|
||||||
NotificationsViewModel | undefined
|
|
||||||
>()
|
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let aborted = false
|
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
console.log('Updating notifications feed')
|
||||||
store.me.refreshMemberships() // needed for the invite notifications
|
store.me.refreshMemberships() // needed for the invite notifications
|
||||||
if (hasSetup) {
|
store.me.notifications
|
||||||
console.log('Updating notifications feed')
|
.update()
|
||||||
notesView?.update()
|
.catch(e => {
|
||||||
} else {
|
console.error('Error while updating notifications feed', e)
|
||||||
store.nav.setTitle(navIdx, 'Notifications')
|
|
||||||
const newNotesView = new NotificationsViewModel(store, {})
|
|
||||||
setNotesView(newNotesView)
|
|
||||||
newNotesView.setup().then(() => {
|
|
||||||
if (aborted) return
|
|
||||||
setHasSetup(true)
|
|
||||||
})
|
})
|
||||||
}
|
.then(() => {
|
||||||
return () => {
|
store.me.notifications.updateReadState()
|
||||||
aborted = true
|
})
|
||||||
}
|
store.nav.setTitle(navIdx, 'Notifications')
|
||||||
}, [visible, store])
|
}, [visible, store])
|
||||||
|
|
||||||
const onPressTryAgain = () => {
|
const onPressTryAgain = () => {
|
||||||
notesView?.refresh()
|
store.me.notifications.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
<ViewHeader title="Notifications" />
|
<ViewHeader title="Notifications" />
|
||||||
{notesView && <Feed view={notesView} onPressTryAgain={onPressTryAgain} />}
|
<Feed view={store.me.notifications} onPressTryAgain={onPressTryAgain} />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue