about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2025-06-20 11:01:55 +0300
committerGitHub <noreply@github.com>2025-06-20 11:01:55 +0300
commit4c75b568dfad4f44d4df76f44236bb1c9acd89a4 (patch)
tree911b496b7dbcb2712de8175e284bd8e586d26beb
parent9702b210cc8d506aaf9f4fe3fd4b3f1951554360 (diff)
downloadvoidsky-4c75b568dfad4f44d4df76f44236bb1c9acd89a4.tar.zst
Android notification channels (#8539)
-rw-r--r--modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt15
-rw-r--r--src/lib/hooks/useNotificationHandler.ts94
2 files changed, 102 insertions, 7 deletions
diff --git a/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt
index 7c1494c70..9fdfcfd89 100644
--- a/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt
+++ b/modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/BackgroundNotificationHandler.kt
@@ -15,6 +15,8 @@ class BackgroundNotificationHandler(
 
     if (remoteMessage.data["reason"] == "chat-message") {
       mutateWithChatMessage(remoteMessage)
+    } else {
+      mutateWithOtherReason(remoteMessage)
     }
 
     notifInterface.showMessage(remoteMessage)
@@ -39,4 +41,17 @@ class BackgroundNotificationHandler(
     // TODO - Remove this once we have more backend capability
     remoteMessage.data["badge"] = null
   }
+
+  private fun mutateWithOtherReason(remoteMessage: RemoteMessage) {
+    // If oreo or higher
+    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+      // If one of "like", "repost", "follow", "mention", "reply", "quote", "like-via-repost", "repost-via-repost"
+      // assign to it's eponymous channel. otherwise do nothing, let expo handle it
+      when (remoteMessage.data["reason"]) {
+        "like", "repost", "follow", "mention", "reply", "quote", "like-via-repost", "repost-via-repost" -> {
+          remoteMessage.data["channelId"] = remoteMessage.data["reason"]
+        }
+      }
+    }
+  }
 }
diff --git a/src/lib/hooks/useNotificationHandler.ts b/src/lib/hooks/useNotificationHandler.ts
index b1bfe6018..3a3d0156e 100644
--- a/src/lib/hooks/useNotificationHandler.ts
+++ b/src/lib/hooks/useNotificationHandler.ts
@@ -1,5 +1,8 @@
-import React from 'react'
+import {useEffect} from 'react'
 import * as Notifications from 'expo-notifications'
+import {type AppBskyNotificationListNotifications} from '@atproto/api'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
 import {CommonActions, useNavigation} from '@react-navigation/native'
 import {useQueryClient} from '@tanstack/react-query'
 
@@ -25,6 +28,10 @@ export type NotificationReason =
   | 'quote'
   | 'chat-message'
   | 'starterpack-joined'
+  | 'like-via-repost'
+  | 'repost-via-repost'
+  | 'verified'
+  | 'unverified'
 
 /**
  * Manually overridden type, but retains the possibility of
@@ -66,34 +73,103 @@ export function useNotificationsHandler() {
   const {currentConvoId} = useCurrentConvoId()
   const {setShowLoggedOut} = useLoggedOutViewControls()
   const closeAllActiveElements = useCloseAllActiveElements()
+  const {_} = useLingui()
 
   // On Android, we cannot control which sound is used for a notification on Android
   // 28 or higher. Instead, we have to configure a notification channel ahead of time
   // which has the sounds we want in the configuration for that channel. These two
   // channels allow for the mute/unmute functionality we want for the background
   // handler.
-  React.useEffect(() => {
+  useEffect(() => {
     if (!isAndroid) return
+    // assign both chat notifications to a group
+    // NOTE: I don't think that it will retroactively move them into the group
+    // if the channels already exist. no big deal imo -sfn
+    const CHAT_GROUP = 'chat'
+    Notifications.setNotificationChannelGroupAsync(CHAT_GROUP, {
+      name: _(msg`Chat`),
+      description: _(
+        msg`You can choose whether chat notifications have sound in the chat settings within the app`,
+      ),
+    })
     Notifications.setNotificationChannelAsync('chat-messages', {
-      name: 'Chat',
+      name: _(msg`Chat messages - sound`),
+      groupId: CHAT_GROUP,
       importance: Notifications.AndroidImportance.MAX,
       sound: 'dm.mp3',
       showBadge: true,
       vibrationPattern: [250],
       lockscreenVisibility: Notifications.AndroidNotificationVisibility.PRIVATE,
     })
-
     Notifications.setNotificationChannelAsync('chat-messages-muted', {
-      name: 'Chat - Muted',
+      name: _(msg`Chat messages - silent`),
+      groupId: CHAT_GROUP,
       importance: Notifications.AndroidImportance.MAX,
       sound: null,
       showBadge: true,
       vibrationPattern: [250],
       lockscreenVisibility: Notifications.AndroidNotificationVisibility.PRIVATE,
     })
-  }, [])
 
-  React.useEffect(() => {
+    Notifications.setNotificationChannelAsync(
+      'like' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      {
+        name: _(msg`Likes`),
+        importance: Notifications.AndroidImportance.HIGH,
+      },
+    )
+    Notifications.setNotificationChannelAsync(
+      'repost' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      {
+        name: _(msg`Reposts`),
+        importance: Notifications.AndroidImportance.HIGH,
+      },
+    )
+    Notifications.setNotificationChannelAsync(
+      'reply' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      {
+        name: _(msg`Replies`),
+        importance: Notifications.AndroidImportance.HIGH,
+      },
+    )
+    Notifications.setNotificationChannelAsync(
+      'mention' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      {
+        name: _(msg`Mentions`),
+        importance: Notifications.AndroidImportance.HIGH,
+      },
+    )
+    Notifications.setNotificationChannelAsync(
+      'quote' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      {
+        name: _(msg`Quotes`),
+        importance: Notifications.AndroidImportance.HIGH,
+      },
+    )
+    Notifications.setNotificationChannelAsync(
+      'follow' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      {
+        name: _(msg`New followers`),
+        importance: Notifications.AndroidImportance.HIGH,
+      },
+    )
+    Notifications.setNotificationChannelAsync(
+      'like-via-repost' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      {
+        name: _(msg`Likes of your reposts`),
+        importance: Notifications.AndroidImportance.HIGH,
+      },
+    )
+    Notifications.setNotificationChannelAsync(
+      'repost-via-repost' satisfies AppBskyNotificationListNotifications.Notification['reason'],
+      {
+        name: _(msg`Reposts of your reposts`),
+        importance: Notifications.AndroidImportance.HIGH,
+      },
+    )
+  }, [_])
+
+  useEffect(() => {
     const handleNotification = (payload?: NotificationPayload) => {
       if (!payload) return
 
@@ -151,6 +227,10 @@ export function useNotificationsHandler() {
           case 'quote':
           case 'reply':
           case 'starterpack-joined':
+          case 'like-via-repost':
+          case 'repost-via-repost':
+          case 'verified':
+          case 'unverified':
             resetToTab('NotificationsTab')
             break
           // TODO implement these after we have an idea of how to handle each individual case