diff options
Diffstat (limited to 'src/state/models')
-rw-r--r-- | src/state/models/feeds/notifications.ts | 148 | ||||
-rw-r--r-- | src/state/models/feeds/posts.ts | 47 | ||||
-rw-r--r-- | src/state/models/me.ts | 7 |
3 files changed, 81 insertions, 121 deletions
diff --git a/src/state/models/feeds/notifications.ts b/src/state/models/feeds/notifications.ts index 12db9510d..ff77ab979 100644 --- a/src/state/models/feeds/notifications.ts +++ b/src/state/models/feeds/notifications.ts @@ -21,7 +21,7 @@ const MS_2DAY = MS_1HR * 48 let _idCounter = 0 -type CondFn = (notif: ListNotifications.Notification) => boolean +export const MAX_VISIBLE_NOTIFS = 30 export interface GroupedNotification extends ListNotifications.Notification { additional?: ListNotifications.Notification[] @@ -220,6 +220,7 @@ export class NotificationsFeedModel { loadMoreError = '' hasMore = true loadMoreCursor?: string + lastSync?: Date // used to linearize async modifications to state lock = new AwaitLock() @@ -259,6 +260,17 @@ export class NotificationsFeedModel { return this.queuedNotifications && this.queuedNotifications?.length > 0 } + get unreadCountLabel(): string { + const count = this.unreadCount + this.rootStore.invitedUsers.numNotifs + if (count >= MAX_VISIBLE_NOTIFS) { + return `${MAX_VISIBLE_NOTIFS}+` + } + if (count === 0) { + return '' + } + return String(count) + } + // public api // = @@ -288,10 +300,13 @@ export class NotificationsFeedModel { try { this._xLoading(isRefreshing) try { - const res = await this._fetchUntil(notif => notif.isRead, { - breakAt: 'page', + const res = await this.rootStore.agent.listNotifications({ + limit: PAGE_SIZE, }) await this._replaceAll(res) + runInAction(() => { + this.lastSync = new Date() + }) this._setQueued(undefined) this._countUnread() this._xIdle() @@ -313,55 +328,37 @@ export class NotificationsFeedModel { /** * Sync the next set of notifications to show - * returns true if the number changed */ syncQueue = bundleAsync(async () => { this.rootStore.log.debug('NotificationsModel:syncQueue') - await this.lock.acquireAsync() - try { - const res = await this._fetchUntil( - notif => - this.notifications.length - ? isEq(notif, this.notifications[0]) - : notif.isRead, - {breakAt: 'record'}, - ) - this._setQueued(res.data.notifications) - this._countUnread() - } catch (e) { - this.rootStore.log.error('NotificationsModel:syncQueue failed', {e}) - } finally { - this.lock.release() + if (this.unreadCount >= MAX_VISIBLE_NOTIFS) { + return // no need to check } - }) - - /** - * - */ - processQueue = bundleAsync(async () => { - this.rootStore.log.debug('NotificationsModel:processQueue') - if (!this.queuedNotifications) { - return - } - this.isRefreshing = true await this.lock.acquireAsync() try { - runInAction(() => { - this.mostRecentNotificationUri = this.queuedNotifications?.[0].uri - }) - const itemModels = await this._processNotifications( - this.queuedNotifications, - ) - this._setQueued(undefined) - runInAction(() => { - this.notifications = itemModels.concat(this.notifications) + const res = await this.rootStore.agent.listNotifications({ + limit: PAGE_SIZE, }) + + const queue = [] + for (const notif of res.data.notifications) { + if (this.notifications.length) { + if (isEq(notif, this.notifications[0])) { + break + } + } else { + if (!notif.isRead) { + break + } + } + queue.push(notif) + } + + this._setQueued(this._filterNotifications(queue)) + this._countUnread() } catch (e) { - this.rootStore.log.error('NotificationsModel:processQueue failed', {e}) + this.rootStore.log.error('NotificationsModel:syncQueue failed', {e}) } finally { - runInAction(() => { - this.isRefreshing = false - }) this.lock.release() } }) @@ -423,22 +420,23 @@ export class NotificationsFeedModel { /** * Update read/unread state */ - async markAllUnqueuedRead() { + async markAllRead() { try { for (const notif of this.notifications) { notif.markGroupRead() } this._countUnread() - if (this.notifications[0]) { - await this.rootStore.agent.updateSeenNotifications( - this.notifications[0].indexedAt, - ) - } + await this.rootStore.agent.updateSeenNotifications( + this.lastSync ? this.lastSync.toISOString() : undefined, + ) } catch (e: any) { this.rootStore.log.warn('Failed to update notifications read state', e) } } + /** + * Used in background fetch to trigger notifications + */ async getNewMostRecent(): Promise<NotificationsFeedItemModel | undefined> { let old = this.mostRecentNotificationUri const res = await this.rootStore.agent.listNotifications({ @@ -486,40 +484,6 @@ export class NotificationsFeedModel { // helper functions // = - async _fetchUntil( - condFn: CondFn, - {breakAt}: {breakAt: 'page' | 'record'}, - ): Promise<ListNotifications.Response> { - const accRes: ListNotifications.Response = { - success: true, - headers: {}, - data: {cursor: undefined, notifications: []}, - } - for (let i = 0; i <= 10; i++) { - const res = await this.rootStore.agent.listNotifications({ - limit: PAGE_SIZE, - cursor: accRes.data.cursor, - }) - accRes.data.cursor = res.data.cursor - - let pageIsDone = false - for (const notif of res.data.notifications) { - if (condFn(notif)) { - if (breakAt === 'record') { - return accRes - } else { - pageIsDone = true - } - } - accRes.data.notifications.push(notif) - } - if (pageIsDone || res.data.notifications.length < PAGE_SIZE) { - return accRes - } - } - return accRes - } - async _replaceAll(res: ListNotifications.Response) { if (res.data.notifications[0]) { this.mostRecentNotificationUri = res.data.notifications[0].uri @@ -540,17 +504,23 @@ export class NotificationsFeedModel { }) } - async _processNotifications( + _filterNotifications( items: ListNotifications.Notification[], - ): Promise<NotificationsFeedItemModel[]> { - const promises = [] - const itemModels: NotificationsFeedItemModel[] = [] - items = items.filter(item => { + ): ListNotifications.Notification[] { + return items.filter(item => { return ( this.rootStore.preferences.getLabelPreference(item.labels).pref !== 'hide' ) }) + } + + async _processNotifications( + items: ListNotifications.Notification[], + ): Promise<NotificationsFeedItemModel[]> { + const promises = [] + const itemModels: NotificationsFeedItemModel[] = [] + items = this._filterNotifications(items) for (const item of groupNotifications(items)) { const itemModel = new NotificationsFeedItemModel( this.rootStore, @@ -581,7 +551,7 @@ export class NotificationsFeedModel { unread += notif.numUnreadInGroup } if (this.queuedNotifications) { - unread += this.queuedNotifications.length + unread += this.queuedNotifications.filter(notif => !notif.isRead).length } this.unreadCount = unread this.rootStore.emitUnreadNotifications(unread) diff --git a/src/state/models/feeds/posts.ts b/src/state/models/feeds/posts.ts index e3328c71a..38faf658a 100644 --- a/src/state/models/feeds/posts.ts +++ b/src/state/models/feeds/posts.ts @@ -237,7 +237,6 @@ export class PostsFeedModel { // data slices: PostsFeedSliceModel[] = [] - nextSlices: PostsFeedSliceModel[] = [] constructor( public rootStore: RootStoreModel, @@ -309,7 +308,6 @@ export class PostsFeedModel { this.loadMoreCursor = undefined this.pollCursor = undefined this.slices = [] - this.nextSlices = [] this.tuner.reset() } @@ -461,30 +459,27 @@ export class PostsFeedModel { } const res = await this._getFeed({limit: PAGE_SIZE}) const tuner = new FeedTuner() - const nextSlices = tuner.tune(res.data.feed, this.feedTuners) - if (nextSlices[0]?.uri !== this.slices[0]?.uri) { - const nextSlicesModels = nextSlices.map( - slice => - new PostsFeedSliceModel( - this.rootStore, - `item-${_idCounter++}`, - slice, - ), - ) - if (autoPrepend) { + const slices = tuner.tune(res.data.feed, this.feedTuners) + if (slices[0]?.uri !== this.slices[0]?.uri) { + if (!autoPrepend) { + this.setHasNewLatest(true) + } else { + this.setHasNewLatest(false) runInAction(() => { - this.slices = nextSlicesModels.concat( + const slicesModels = slices.map( + slice => + new PostsFeedSliceModel( + this.rootStore, + `item-${_idCounter++}`, + slice, + ), + ) + this.slices = slicesModels.concat( this.slices.filter(slice1 => - nextSlicesModels.find(slice2 => slice1.uri === slice2.uri), + slicesModels.find(slice2 => slice1.uri === slice2.uri), ), ) - this.setHasNewLatest(false) - }) - } else { - runInAction(() => { - this.nextSlices = nextSlicesModels }) - this.setHasNewLatest(true) } } else { this.setHasNewLatest(false) @@ -492,16 +487,6 @@ export class PostsFeedModel { } /** - * Sets the current slices to the "next slices" loaded by checkForLatest - */ - resetToLatest() { - if (this.nextSlices.length) { - this.slices = this.nextSlices - } - this.setHasNewLatest(false) - } - - /** * Removes posts from the feed upon deletion. */ onPostDeleted(uri: string) { diff --git a/src/state/models/me.ts b/src/state/models/me.ts index 3774e1e56..e8b8e1ed0 100644 --- a/src/state/models/me.ts +++ b/src/state/models/me.ts @@ -7,6 +7,7 @@ import {MyFollowsCache} from './cache/my-follows' import {isObj, hasProp} from 'lib/type-guards' const PROFILE_UPDATE_INTERVAL = 10 * 60 * 1e3 // 10min +const NOTIFS_UPDATE_INTERVAL = 30 * 1e3 // 30sec export class MeModel { did: string = '' @@ -21,6 +22,7 @@ export class MeModel { follows: MyFollowsCache invites: ComAtprotoServerDefs.InviteCode[] = [] lastProfileStateUpdate = Date.now() + lastNotifsUpdate = Date.now() get invitesAvailable() { return this.invites.filter(isInviteAvailable).length @@ -119,7 +121,10 @@ export class MeModel { await this.fetchProfile() await this.fetchInviteCodes() } - await this.notifications.syncQueue() + if (Date.now() - this.lastNotifsUpdate > NOTIFS_UPDATE_INTERVAL) { + this.lastNotifsUpdate = Date.now() + await this.notifications.syncQueue() + } } async fetchProfile() { |