about summary refs log tree commit diff
path: root/src/state/models
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/models')
-rw-r--r--src/state/models/feeds/notifications.ts148
-rw-r--r--src/state/models/feeds/posts.ts47
-rw-r--r--src/state/models/me.ts7
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() {