Bluesky app fork with some witchin' additions 馃挮
at feat/tealfm 105 lines 3.9 kB view raw
1import UserNotifications 2import UIKit 3 4let APP_GROUP = "group.app.witchsky" 5typealias ContentHandler = (UNNotificationContent) -> Void 6 7// This extension allows us to do some processing of the received notification 8// data before displaying the notification to the user. In our use case, there 9// are a few particular things that we want to do: 10// 11// - Determine whether we should play a sound for the notification 12// - Download and display any images for the notification 13// - Update the badge count accordingly 14// 15// The extension may or may not create a new process to handle a notification. 16// It is also possible that multiple notifications will be processed by the 17// same instance of `NotificationService`, though these will happen in 18// parallel. 19// 20// Because multiple instances of `NotificationService` may exist, we should 21// be careful in accessing preferences that will be mutated _by the 22// extension itself_. For example, we should not worry about `playChatSound` 23// changing, since we never mutate that value within the extension itself. 24// However, since we mutate `badgeCount` frequently, we should ensure that 25// these updates always run sync with each other and that the have access 26// to the most recent values. 27 28class NotificationService: UNNotificationServiceExtension { 29 private var contentHandler: ContentHandler? 30 private var bestAttempt: UNMutableNotificationContent? 31 32 override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { 33 self.contentHandler = contentHandler 34 35 guard let bestAttempt = NSEUtil.createCopy(request.content), 36 let reason = request.content.userInfo["reason"] as? String 37 else { 38 contentHandler(request.content) 39 return 40 } 41 42 self.bestAttempt = bestAttempt 43 if reason == "chat-message" { 44 mutateWithChatMessage(bestAttempt) 45 } else { 46 mutateWithBadge(bestAttempt) 47 } 48 49 // Any image downloading (or other network tasks) should be handled at the end 50 // of this block. Otherwise, if there is a timeout and serviceExtensionTimeWillExpire 51 // gets called, we might not have all the needed mutations completed in time. 52 53 contentHandler(bestAttempt) 54 } 55 56 override func serviceExtensionTimeWillExpire() { 57 guard let contentHandler = self.contentHandler, 58 let bestAttempt = self.bestAttempt else { 59 return 60 } 61 contentHandler(bestAttempt) 62 } 63 64 // MARK: Mutations 65 66 func mutateWithBadge(_ content: UNMutableNotificationContent) { 67 NSEUtil.shared.prefsQueue.sync { 68 var count = NSEUtil.shared.prefs?.integer(forKey: "badgeCount") ?? 0 69 count += 1 70 71 // Set the new badge number for the notification, then store that value for using later 72 content.badge = NSNumber(value: count) 73 NSEUtil.shared.prefs?.setValue(count, forKey: "badgeCount") 74 } 75 } 76 77 func mutateWithChatMessage(_ content: UNMutableNotificationContent) { 78 if NSEUtil.shared.prefs?.bool(forKey: "playSoundChat") == true { 79 mutateWithDmSound(content) 80 } 81 } 82 83 func mutateWithDefaultSound(_ content: UNMutableNotificationContent) { 84 content.sound = UNNotificationSound.default 85 } 86 87 func mutateWithDmSound(_ content: UNMutableNotificationContent) { 88 content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "dm.aiff")) 89 } 90} 91 92// NSEUtil's purpose is to create a shared instance of `UserDefaults` across 93// `NotificationService` instances. It also includes a queue so that we can process 94// updates to `UserDefaults` in parallel. 95 96private class NSEUtil { 97 static let shared = NSEUtil() 98 99 var prefs = UserDefaults(suiteName: APP_GROUP) 100 var prefsQueue = DispatchQueue(label: "NSEPrefsQueue") 101 102 static func createCopy(_ content: UNNotificationContent) -> UNMutableNotificationContent? { 103 return content.mutableCopy() as? UNMutableNotificationContent 104 } 105}