about summary refs log tree commit diff
path: root/src/lib
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2025-07-02 00:36:04 +0300
committerGitHub <noreply@github.com>2025-07-01 14:36:04 -0700
commitbc072570d27e1f397406daea355570f5aec95647 (patch)
tree0d698c0bababd9b5e221df763a1ab15744ebdb71 /src/lib
parent8f9a8ddce022e328b07b793c3f1500e1c423ef73 (diff)
downloadvoidsky-bc072570d27e1f397406daea355570f5aec95647.tar.zst
Activity notification settings (#8485)
Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Co-authored-by: hailey <me@haileyok.com>
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/api/feed/list.ts12
-rw-r--r--src/lib/api/feed/posts.ts52
-rw-r--r--src/lib/hooks/useNotificationHandler.ts44
-rw-r--r--src/lib/moderation/create-sanitized-display-name.ts7
-rw-r--r--src/lib/routes/types.ts2
-rw-r--r--src/lib/statsig/gates.ts1
6 files changed, 97 insertions, 21 deletions
diff --git a/src/lib/api/feed/list.ts b/src/lib/api/feed/list.ts
index 9744e3d4c..9697b0aaf 100644
--- a/src/lib/api/feed/list.ts
+++ b/src/lib/api/feed/list.ts
@@ -1,20 +1,20 @@
 import {
-  AppBskyFeedDefs,
-  AppBskyFeedGetListFeed as GetListFeed,
-  BskyAgent,
+  type Agent,
+  type AppBskyFeedDefs,
+  type AppBskyFeedGetListFeed as GetListFeed,
 } from '@atproto/api'
 
-import {FeedAPI, FeedAPIResponse} from './types'
+import {type FeedAPI, type FeedAPIResponse} from './types'
 
 export class ListFeedAPI implements FeedAPI {
-  agent: BskyAgent
+  agent: Agent
   params: GetListFeed.QueryParams
 
   constructor({
     agent,
     feedParams,
   }: {
-    agent: BskyAgent
+    agent: Agent
     feedParams: GetListFeed.QueryParams
   }) {
     this.agent = agent
diff --git a/src/lib/api/feed/posts.ts b/src/lib/api/feed/posts.ts
new file mode 100644
index 000000000..33eff5099
--- /dev/null
+++ b/src/lib/api/feed/posts.ts
@@ -0,0 +1,52 @@
+import {
+  type Agent,
+  type AppBskyFeedDefs,
+  type AppBskyFeedGetPosts,
+} from '@atproto/api'
+
+import {logger} from '#/logger'
+import {type FeedAPI, type FeedAPIResponse} from './types'
+
+export class PostListFeedAPI implements FeedAPI {
+  agent: Agent
+  params: AppBskyFeedGetPosts.QueryParams
+  peek: AppBskyFeedDefs.FeedViewPost | null = null
+
+  constructor({
+    agent,
+    feedParams,
+  }: {
+    agent: Agent
+    feedParams: AppBskyFeedGetPosts.QueryParams
+  }) {
+    this.agent = agent
+    if (feedParams.uris.length > 25) {
+      logger.warn(
+        `Too many URIs provided - expected 25, got ${feedParams.uris.length}`,
+      )
+    }
+    this.params = {
+      uris: feedParams.uris.slice(0, 25),
+    }
+  }
+
+  async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
+    if (this.peek) return this.peek
+    throw new Error('Has not fetched yet')
+  }
+
+  async fetch({}: {}): Promise<FeedAPIResponse> {
+    const res = await this.agent.app.bsky.feed.getPosts({
+      ...this.params,
+    })
+    if (res.success) {
+      this.peek = {post: res.data.posts[0]}
+      return {
+        feed: res.data.posts.map(post => ({post})),
+      }
+    }
+    return {
+      feed: [],
+    }
+  }
+}
diff --git a/src/lib/hooks/useNotificationHandler.ts b/src/lib/hooks/useNotificationHandler.ts
index 311f38a79..6c3e7deb8 100644
--- a/src/lib/hooks/useNotificationHandler.ts
+++ b/src/lib/hooks/useNotificationHandler.ts
@@ -1,6 +1,6 @@
 import {useEffect} from 'react'
 import * as Notifications from 'expo-notifications'
-import {type AppBskyNotificationListNotifications} from '@atproto/api'
+import {AtUri} from '@atproto/api'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {CommonActions, useNavigation} from '@react-navigation/native'
@@ -32,6 +32,7 @@ export type NotificationReason =
   | 'repost-via-repost'
   | 'verified'
   | 'unverified'
+  | 'subscribed-post'
 
 /**
  * Manually overridden type, but retains the possibility of
@@ -112,61 +113,68 @@ export function useNotificationsHandler() {
     })
 
     Notifications.setNotificationChannelAsync(
-      'like' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      'like' satisfies NotificationReason,
       {
         name: _(msg`Likes`),
         importance: Notifications.AndroidImportance.HIGH,
       },
     )
     Notifications.setNotificationChannelAsync(
-      'repost' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      'repost' satisfies NotificationReason,
       {
         name: _(msg`Reposts`),
         importance: Notifications.AndroidImportance.HIGH,
       },
     )
     Notifications.setNotificationChannelAsync(
-      'reply' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      'reply' satisfies NotificationReason,
       {
         name: _(msg`Replies`),
         importance: Notifications.AndroidImportance.HIGH,
       },
     )
     Notifications.setNotificationChannelAsync(
-      'mention' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      'mention' satisfies NotificationReason,
       {
         name: _(msg`Mentions`),
         importance: Notifications.AndroidImportance.HIGH,
       },
     )
     Notifications.setNotificationChannelAsync(
-      'quote' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      'quote' satisfies NotificationReason,
       {
         name: _(msg`Quotes`),
         importance: Notifications.AndroidImportance.HIGH,
       },
     )
     Notifications.setNotificationChannelAsync(
-      'follow' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      'follow' satisfies NotificationReason,
       {
         name: _(msg`New followers`),
         importance: Notifications.AndroidImportance.HIGH,
       },
     )
     Notifications.setNotificationChannelAsync(
-      'like-via-repost' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      'like-via-repost' satisfies NotificationReason,
       {
         name: _(msg`Likes of your reposts`),
         importance: Notifications.AndroidImportance.HIGH,
       },
     )
     Notifications.setNotificationChannelAsync(
-      'repost-via-repost' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      'repost-via-repost' satisfies NotificationReason,
       {
         name: _(msg`Reposts of your reposts`),
         importance: Notifications.AndroidImportance.HIGH,
       },
     )
+    Notifications.setNotificationChannelAsync(
+      'subscribed-post' satisfies NotificationReason,
+      {
+        name: _(msg`Activity from others`),
+        importance: Notifications.AndroidImportance.HIGH,
+      },
+    )
   }, [_])
 
   useEffect(() => {
@@ -220,6 +228,23 @@ export function useNotificationsHandler() {
         }
       } else {
         switch (payload.reason) {
+          case 'subscribed-post':
+            const urip = new AtUri(payload.uri)
+            if (urip.collection === 'app.bsky.feed.post') {
+              setTimeout(() => {
+                // @ts-expect-error types are weird here
+                navigation.navigate('HomeTab', {
+                  screen: 'PostThread',
+                  params: {
+                    name: urip.host,
+                    rkey: urip.rkey,
+                  },
+                })
+              }, 500)
+            } else {
+              resetToTab('NotificationsTab')
+            }
+            break
           case 'like':
           case 'repost':
           case 'follow':
@@ -231,6 +256,7 @@ export function useNotificationsHandler() {
           case 'repost-via-repost':
           case 'verified':
           case 'unverified':
+          default:
             resetToTab('NotificationsTab')
             break
           // TODO implement these after we have an idea of how to handle each individual case
diff --git a/src/lib/moderation/create-sanitized-display-name.ts b/src/lib/moderation/create-sanitized-display-name.ts
index 4f9584f91..4c62a5c03 100644
--- a/src/lib/moderation/create-sanitized-display-name.ts
+++ b/src/lib/moderation/create-sanitized-display-name.ts
@@ -1,12 +1,9 @@
-import {AppBskyActorDefs} from '@atproto/api'
-
 import {sanitizeDisplayName} from '#/lib/strings/display-names'
 import {sanitizeHandle} from '#/lib/strings/handles'
+import type * as bsky from '#/types/bsky'
 
 export function createSanitizedDisplayName(
-  profile:
-    | AppBskyActorDefs.ProfileViewBasic
-    | AppBskyActorDefs.ProfileViewDetailed,
+  profile: bsky.profile.AnyProfileView,
   noAt = false,
 ) {
   if (profile.displayName != null && profile.displayName !== '') {
diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts
index c92be34c2..b1db5caa6 100644
--- a/src/lib/routes/types.ts
+++ b/src/lib/routes/types.ts
@@ -51,6 +51,7 @@ export type CommonNavigatorParams = {
   AppearanceSettings: undefined
   AccountSettings: undefined
   PrivacyAndSecuritySettings: undefined
+  ActivityPrivacySettings: undefined
   ContentAndMediaSettings: undefined
   NotificationSettings: undefined
   ReplyNotificationSettings: undefined
@@ -72,6 +73,7 @@ export type CommonNavigatorParams = {
   MessagesConversation: {conversation: string; embed?: string; accept?: true}
   MessagesSettings: undefined
   MessagesInbox: undefined
+  NotificationsActivityList: {posts: string}
   LegacyNotificationSettings: undefined
   Feeds: undefined
   Start: {name: string; rkey: string}
diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts
index fca3f609a..3b1106480 100644
--- a/src/lib/statsig/gates.ts
+++ b/src/lib/statsig/gates.ts
@@ -7,7 +7,6 @@ export type Gate =
   | 'old_postonboarding'
   | 'onboarding_add_video_feed'
   | 'post_threads_v2_unspecced'
-  | 'reengagement_features'
   | 'remove_show_latest_button'
   | 'test_gate_1'
   | 'test_gate_2'