about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--package.json2
-rw-r--r--src/state/queries/notifications/feed.ts44
-rw-r--r--src/state/queries/notifications/types.ts1
-rw-r--r--src/state/queries/notifications/util.ts6
-rw-r--r--src/view/screens/Notifications.tsx4
-rw-r--r--yarn.lock14
6 files changed, 42 insertions, 29 deletions
diff --git a/package.json b/package.json
index 2f380e807..6cca2ae73 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,7 @@
     "intl:compile": "lingui compile"
   },
   "dependencies": {
-    "@atproto/api": "^0.7.2",
+    "@atproto/api": "^0.7.3",
     "@bam.tech/react-native-image-resizer": "^3.0.4",
     "@braintree/sanitize-url": "^6.0.2",
     "@emoji-mart/react": "^1.1.1",
diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts
index 1610fc0bf..82eda06ea 100644
--- a/src/state/queries/notifications/feed.ts
+++ b/src/state/queries/notifications/feed.ts
@@ -16,7 +16,7 @@
  * 3. Don't call this query's `refetch()` if you're trying to sync latest; call `checkUnread()` instead.
  */
 
-import {useEffect, useRef} from 'react'
+import {useEffect} from 'react'
 import {AppBskyFeedDefs} from '@atproto/api'
 import {
   useInfiniteQuery,
@@ -49,8 +49,6 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
   const threadMutes = useMutedThreads()
   const unreads = useUnreadNotificationsApi()
   const enabled = opts?.enabled !== false
-  // state tracked across page fetches
-  const pageState = useRef({pageNum: 0, hasMarkedRead: false})
 
   const query = useInfiniteQuery<
     FeedPage,
@@ -66,8 +64,6 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
       if (!pageParam) {
         // for the first page, we check the cached page held by the unread-checker first
         page = unreads.getCachedUnreadPage()
-        // reset the page state
-        pageState.current = {pageNum: 0, hasMarkedRead: false}
       }
       if (!page) {
         page = await fetchPage({
@@ -80,27 +76,9 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
         })
       }
 
-      // NOTE
-      // this section checks to see if we need to mark notifs read
-      // we want to wait until we've seen a read notification because
-      // of a timing challenge; marking read on the first page would
-      // cause subsequent pages of unread notifs to incorrectly come
-      // back as "read". we use page 6 as an abort condition, which means
-      // after ~180 notifs we give up on tracking unread state correctly
-      // -prf
-      if (!pageState.current.hasMarkedRead) {
-        let hasMarkedRead = false
-        if (
-          pageState.current.pageNum > 5 ||
-          page.items.some(item => item.notification.isRead)
-        ) {
-          unreads.markAllRead()
-          hasMarkedRead = true
-        }
-        pageState.current = {
-          pageNum: pageState.current.pageNum + 1,
-          hasMarkedRead,
-        }
+      // if the first page has an unread, mark all read
+      if (!pageParam && page.items[0] && !page.items[0].notification.isRead) {
+        unreads.markAllRead()
       }
 
       return page
@@ -108,6 +86,20 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
     initialPageParam: undefined,
     getNextPageParam: lastPage => lastPage.cursor,
     enabled,
+    select(data: InfiniteData<FeedPage>) {
+      // override 'isRead' using the first page's returned seenAt
+      // we do this because the `markAllRead()` call above will
+      // mark subsequent pages as read prematurely
+      const seenAt = data.pages[0]?.seenAt || new Date()
+      for (const page of data.pages) {
+        for (const item of page.items) {
+          item.notification.isRead =
+            seenAt > new Date(item.notification.indexedAt)
+        }
+      }
+
+      return data
+    },
   })
 
   useEffect(() => {
diff --git a/src/state/queries/notifications/types.ts b/src/state/queries/notifications/types.ts
index b52341115..86a9bb139 100644
--- a/src/state/queries/notifications/types.ts
+++ b/src/state/queries/notifications/types.ts
@@ -24,6 +24,7 @@ export interface FeedNotification {
 
 export interface FeedPage {
   cursor: string | undefined
+  seenAt: Date
   items: FeedNotification[]
 }
 
diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts
index 9fb25867a..cc5943163 100644
--- a/src/state/queries/notifications/util.ts
+++ b/src/state/queries/notifications/util.ts
@@ -68,8 +68,14 @@ export async function fetchPage({
     notif => !isThreadMuted(notif, threadMutes),
   )
 
+  let seenAt = res.data.seenAt ? new Date(res.data.seenAt) : new Date()
+  if (Number.isNaN(seenAt.getTime())) {
+    seenAt = new Date()
+  }
+
   return {
     cursor: res.data.cursor,
+    seenAt,
     items: notifsGrouped,
   }
 }
diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx
index fceaa60c2..7ebacc9db 100644
--- a/src/view/screens/Notifications.tsx
+++ b/src/view/screens/Notifications.tsx
@@ -67,8 +67,8 @@ export function NotificationsScreen({}: Props) {
   const onFocusCheckLatest = React.useCallback(() => {
     // on focus, check for latest, but only invalidate if the user
     // isnt scrolled down to avoid moving content underneath them
-    unreadApi.checkUnread({invalidate: !isScrolledDown})
-  }, [unreadApi, isScrolledDown])
+    unreadApi.checkUnread({invalidate: !isScrolledDown && isDesktop})
+  }, [unreadApi, isScrolledDown, isDesktop])
   checkLatestRef.current = onFocusCheckLatest
 
   // on-visible setup
diff --git a/yarn.lock b/yarn.lock
index c08c37a96..e58cba164 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -48,6 +48,20 @@
     typed-emitter "^2.1.0"
     zod "^3.21.4"
 
+"@atproto/api@^0.7.3":
+  version "0.7.3"
+  resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.7.3.tgz#3224000353619970d5e397a157c6e189e195ef47"
+  integrity sha512-fKU+W+S4kKxClE6IcPBHPZAjcyBYxG28S0FW/bv3T/ZYDkNxGzDV4xuoHOyEDGtB30slltl5U83njuuRZs5xtw==
+  dependencies:
+    "@atproto/common-web" "^0.2.3"
+    "@atproto/lexicon" "^0.3.1"
+    "@atproto/syntax" "^0.1.5"
+    "@atproto/xrpc" "^0.4.1"
+    multiformats "^9.9.0"
+    tlds "^1.234.0"
+    typed-emitter "^2.1.0"
+    zod "^3.21.4"
+
 "@atproto/aws@^0.1.6":
   version "0.1.6"
   resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.1.6.tgz#c6ecbfd92b325f3c5433688534d47f43358b415b"