about summary refs log tree commit diff
path: root/src/state/queries/notifications/unread.tsx
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-11-12 18:13:11 -0800
committerGitHub <noreply@github.com>2023-11-12 18:13:11 -0800
commitb445c15cc99a56c2baf727d05cf53b44aef4542b (patch)
treea8b18769ba4987557d4e9b4cd72871f12fb2e1a3 /src/state/queries/notifications/unread.tsx
parentc584a3378d66459c04eee7d98560920e09c5f09f (diff)
downloadvoidsky-b445c15cc99a56c2baf727d05cf53b44aef4542b.tar.zst
Refactor notifications to use react-query (#1878)
* Move broadcast channel to lib

* Refactor view/com/post/Post and remove temporary 2 components

* Add useModerationOpts hook

* Refactor notifications to use react-query

* Fix: only trigger updates in useModerationOpts when the values have changed

* Implement unread notification tracking

* Add moderation filtering to notifications

* Handle native/push notifications

* Remove dead code

---------

Co-authored-by: Eric Bailey <git@esb.lol>
Diffstat (limited to 'src/state/queries/notifications/unread.tsx')
-rw-r--r--src/state/queries/notifications/unread.tsx113
1 files changed, 113 insertions, 0 deletions
diff --git a/src/state/queries/notifications/unread.tsx b/src/state/queries/notifications/unread.tsx
new file mode 100644
index 000000000..91aa6f3c2
--- /dev/null
+++ b/src/state/queries/notifications/unread.tsx
@@ -0,0 +1,113 @@
+import React from 'react'
+import * as Notifications from 'expo-notifications'
+import BroadcastChannel from '#/lib/broadcast'
+import {useSession} from '#/state/session'
+import {useModerationOpts} from '../preferences'
+import {shouldFilterNotif} from './util'
+import {isNative} from '#/platform/detection'
+
+const UPDATE_INTERVAL = 30 * 1e3 // 30sec
+
+const broadcast = new BroadcastChannel('NOTIFS_BROADCAST_CHANNEL')
+
+type StateContext = string
+
+interface ApiContext {
+  markAllRead: () => Promise<void>
+  checkUnread: () => Promise<void>
+}
+
+const stateContext = React.createContext<StateContext>('')
+
+const apiContext = React.createContext<ApiContext>({
+  async markAllRead() {},
+  async checkUnread() {},
+})
+
+export function Provider({children}: React.PropsWithChildren<{}>) {
+  const {hasSession, agent} = useSession()
+  const moderationOpts = useModerationOpts()
+
+  const [numUnread, setNumUnread] = React.useState('')
+
+  const checkUnreadRef = React.useRef<(() => Promise<void>) | null>(null)
+  const lastSyncRef = React.useRef<Date>(new Date())
+
+  // periodic sync
+  React.useEffect(() => {
+    if (!hasSession || !checkUnreadRef.current) {
+      return
+    }
+    checkUnreadRef.current() // fire on init
+    const interval = setInterval(checkUnreadRef.current, UPDATE_INTERVAL)
+    return () => clearInterval(interval)
+  }, [hasSession])
+
+  // listen for broadcasts
+  React.useEffect(() => {
+    const listener = ({data}: MessageEvent) => {
+      lastSyncRef.current = new Date()
+      setNumUnread(data.event)
+    }
+    broadcast.addEventListener('message', listener)
+    return () => {
+      broadcast.removeEventListener('message', listener)
+    }
+  }, [setNumUnread])
+
+  // create API
+  const api = React.useMemo<ApiContext>(() => {
+    return {
+      async markAllRead() {
+        // update server
+        await agent.updateSeenNotifications(lastSyncRef.current.toISOString())
+
+        // update & broadcast
+        setNumUnread('')
+        broadcast.postMessage({event: ''})
+      },
+
+      async checkUnread() {
+        // count
+        const res = await agent.listNotifications({limit: 40})
+        const filtered = res.data.notifications.filter(
+          notif => !notif.isRead && !shouldFilterNotif(notif, moderationOpts),
+        )
+        const num =
+          filtered.length >= 30
+            ? '30+'
+            : filtered.length === 0
+            ? ''
+            : String(filtered.length)
+        if (isNative) {
+          Notifications.setBadgeCountAsync(Math.min(filtered.length, 30))
+        }
+
+        // track last sync
+        const now = new Date()
+        const lastIndexed = filtered[0] && new Date(filtered[0].indexedAt)
+        lastSyncRef.current =
+          !lastIndexed || now > lastIndexed ? now : lastIndexed
+
+        // update & broadcast
+        setNumUnread(num)
+        broadcast.postMessage({event: num})
+      },
+    }
+  }, [setNumUnread, agent, moderationOpts])
+  checkUnreadRef.current = api.checkUnread
+
+  return (
+    <stateContext.Provider value={numUnread}>
+      <apiContext.Provider value={api}>{children}</apiContext.Provider>
+    </stateContext.Provider>
+  )
+}
+
+export function useUnreadNotifications() {
+  return React.useContext(stateContext)
+}
+
+export function useUnreadNotificationsApi() {
+  return React.useContext(apiContext)
+}