add support for mark-read-generic and mark-read-messages

update kotlin stub

update js types

update the correct badge count

clamp single decrements too

clamp decrement

future proof with `decrementBy`

add support for `mark-read-generic` and `mark-read-messages`

do nothing when receiving `mark-read-*` is received

simplify swift

update android with new types

add types to `NotificationReason`

add logic for decrementing the badge

add decrement mutation
This commit is contained in:
Hailey
2024-06-08 15:55:30 -07:00
parent a2d1cf68b9
commit 7dad1c280b
7 changed files with 133 additions and 25 deletions
+79 -19
View File
@@ -3,53 +3,113 @@ import UIKit
let APP_GROUP = "group.app.bsky"
enum NotificationType: String {
case Like = "like"
case Repost = "repost"
case Follow = "follow"
case Reply = "reply"
case Quote = "quote"
case ChatMessage = "chat-message"
case MarkReadGeneric = "mark-read-generic"
case MarkReadMessages = "mark-read-messages"
}
enum BadgeType: String {
case Generic = "badgeCountGeneric"
case Messages = "badgeCountMessages"
}
enum BadgeOperation {
case Increment
case Decrement
}
class NotificationService: UNNotificationServiceExtension {
var prefs = UserDefaults(suiteName: APP_GROUP)
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
guard let bestAttempt = createCopy(request.content),
let reason = request.content.userInfo["reason"] as? String
let reasonString = request.content.userInfo["reason"] as? String,
let reason = NotificationType(rawValue: reasonString)
else {
contentHandler(request.content)
return
}
if reason == "chat-message" {
switch reason {
case NotificationType.Like, NotificationType.Repost, NotificationType.Follow, NotificationType.Reply, NotificationType.Quote:
mutateWithBadge(bestAttempt, badgeType: BadgeType.Generic, operation: BadgeOperation.Increment)
case NotificationType.ChatMessage:
mutateWithChatMessage(bestAttempt)
} else {
mutateWithBadge(bestAttempt)
case NotificationType.MarkReadGeneric:
mutateWithBadge(bestAttempt, badgeType: BadgeType.Generic, operation: BadgeOperation.Decrement)
case NotificationType.MarkReadMessages:
mutateWithBadge(bestAttempt, badgeType: BadgeType.Messages, operation: BadgeOperation.Decrement)
}
contentHandler(bestAttempt)
}
override func serviceExtensionTimeWillExpire() {
// If for some reason the alloted time expires, we don't actually want to display a notification
}
func createCopy(_ content: UNNotificationContent) -> UNMutableNotificationContent? {
return content.mutableCopy() as? UNMutableNotificationContent
}
func mutateWithBadge(_ content: UNMutableNotificationContent) {
var count = prefs?.integer(forKey: "badgeCount") ?? 0
count += 1
// Set the new badge number for the notification, then store that value for using later
content.badge = NSNumber(value: count)
prefs?.setValue(count, forKey: "badgeCount")
func getDecrementedBadgeCount(current: Int, decrementBy by: Int) -> Int {
let new = current - by
if new < 0 {
return 0
}
return new
}
func mutateWithBadge(_ content: UNMutableNotificationContent, badgeType type: BadgeType, operation: BadgeOperation) {
var genericCount = prefs?.integer(forKey: BadgeType.Generic.rawValue) ?? 0
var messagesCount = prefs?.integer(forKey: BadgeType.Messages.rawValue) ?? 0
if type == BadgeType.Generic {
if operation == BadgeOperation.Decrement {
if let decrementBy = content.userInfo["decrementBy"] as? Int {
genericCount = getDecrementedBadgeCount(current: genericCount, decrementBy: decrementBy)
} else {
genericCount = 0
}
} else {
genericCount += 1
}
prefs?.setValue(genericCount, forKey: BadgeType.Generic.rawValue)
} else if type == BadgeType.Messages {
if operation == BadgeOperation.Decrement {
if let decrementBy = content.userInfo["decrementBy"] as? Int {
messagesCount = getDecrementedBadgeCount(current: messagesCount, decrementBy: decrementBy)
} else {
messagesCount = getDecrementedBadgeCount(current: messagesCount, decrementBy: 1)
}
} else {
genericCount += 1
}
prefs?.setValue(messagesCount, forKey: BadgeType.Generic.rawValue)
}
content.badge = NSNumber(value: genericCount + messagesCount)
}
func mutateWithChatMessage(_ content: UNMutableNotificationContent) {
if self.prefs?.bool(forKey: "playSoundChat") == true {
mutateWithDmSound(content)
}
}
func mutateWithDefaultSound(_ content: UNMutableNotificationContent) {
content.sound = UNNotificationSound.default
}
func mutateWithDmSound(_ content: UNMutableNotificationContent) {
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "dm.aiff"))
}
@@ -3,6 +3,17 @@ package expo.modules.backgroundnotificationhandler
import android.content.Context
import com.google.firebase.messaging.RemoteMessage
enum class NotificationType(val type: String) {
Like("like"),
Repost("repost"),
Follow("follow"),
Reply("reply"),
Quote("quote"),
ChatMessage("chat-message"),
MarkReadGeneric("mark-read-generic"),
MarkReadMessages("mark-read-messages"),
}
class BackgroundNotificationHandler(
private val context: Context,
private val notifInterface: BackgroundNotificationHandlerInterface
@@ -13,8 +24,12 @@ class BackgroundNotificationHandler(
return
}
if (remoteMessage.data["reason"] == "chat-message") {
val type = NotificationType.valueOf(remoteMessage.data["reason"] ?: return)
if (type == NotificationType.ChatMessage) {
mutateWithChatMessage(remoteMessage)
} else if (type == NotificationType.MarkReadGeneric || type == NotificationType.MarkReadMessages) {
return
}
notifInterface.showMessage(remoteMessage)
@@ -67,7 +67,7 @@ class ExpoBackgroundNotificationHandlerModule : Module() {
NotificationPrefs(appContext.reactContext).removeManyFromStringArray(forKey, strings)
}
AsyncFunction("setBadgeCountAsync") { _: Int ->
AsyncFunction("setBadgeCountAsync") { _: String, _: Int ->
// This does nothing on Android
}
}
@@ -114,8 +114,22 @@ public class ExpoBackgroundNotificationHandlerModule: Module {
}
}
AsyncFunction("setBadgeCountAsync") { (count: Int) in
userDefaults?.setValue(count, forKey: "badgeCount")
AsyncFunction("setBadgeCountAsync") { (type: BadgeCountType, count: Int) in
userDefaults?.setValue(count, forKey: type.toKeyName())
}
}
}
enum BadgeCountType : String, Enumerable {
case generic
case messages
func toKeyName() -> String {
switch self {
case .generic:
return "badgeCountGeneric"
case .messages:
return "badgeCountMessages"
}
}
}
@@ -31,7 +31,10 @@ export type ExpoBackgroundNotificationHandlerModule = {
forKey: keyof BackgroundNotificationHandlerPreferences,
value: string[],
) => Promise<void>
setBadgeCountAsync: (count: number) => Promise<void>
setBadgeCountAsync: (
type: 'generic' | 'messages',
count: number,
) => Promise<void>
}
// TODO there are more preferences in the native code, however they have not been added here yet.
@@ -24,5 +24,5 @@ export const BackgroundNotificationHandler = {
removeFromStringArrayAsync: async (_: string, __: string) => {},
addManyToStringArrayAsync: async (_: string, __: string[]) => {},
removeManyFromStringArrayAsync: async (_: string, __: string[]) => {},
setBadgeCountAsync: async (_: number) => {},
setBadgeCountAsync: async (_: 'generic' | 'messages', __: number) => {},
} as ExpoBackgroundNotificationHandlerModule
+16
View File
@@ -26,6 +26,8 @@ type NotificationReason =
| 'reply'
| 'quote'
| 'chat-message'
| 'mark-read-generic'
| 'mark-read-messages'
type NotificationPayload =
| {
@@ -194,6 +196,20 @@ export function useNotificationsHandler() {
shouldPlaySound: false,
shouldSetBadge: false,
}
} else if (
payload.reason === 'mark-read-generic' ||
payload.reason === 'mark-read-messages'
) {
logger.debug(
`Notifications: ${payload.reason}`,
{},
logger.DebugContext.notifications,
)
return {
shouldShowAlert: false,
shouldPlaySound: false,
shouldSetBadge: false,
}
}
// Any notification other than a chat message should invalidate the unread page