|
@@ -1,4 +1,5 @@
|
|
|
import AudioToolbox
|
|
import AudioToolbox
|
|
|
|
|
+import CoreData
|
|
|
import Foundation
|
|
import Foundation
|
|
|
import LoopKit
|
|
import LoopKit
|
|
|
import SwiftUI
|
|
import SwiftUI
|
|
@@ -52,19 +53,24 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
|
|
|
private let center = UNUserNotificationCenter.current()
|
|
private let center = UNUserNotificationCenter.current()
|
|
|
private var lifetime = Lifetime()
|
|
private var lifetime = Lifetime()
|
|
|
|
|
|
|
|
- private let context = CoreDataStack.shared.persistentContainer.viewContext
|
|
|
|
|
|
|
+ private let viewContext = CoreDataStack.shared.persistentContainer.viewContext
|
|
|
|
|
+ private let backgroundContext = CoreDataStack.shared.newTaskContext()
|
|
|
|
|
+
|
|
|
|
|
+ private var coreDataObserver: CoreDataObserver?
|
|
|
|
|
|
|
|
init(resolver: Resolver) {
|
|
init(resolver: Resolver) {
|
|
|
super.init()
|
|
super.init()
|
|
|
center.delegate = self
|
|
center.delegate = self
|
|
|
injectServices(resolver)
|
|
injectServices(resolver)
|
|
|
- broadcaster.register(GlucoseObserver.self, observer: self)
|
|
|
|
|
broadcaster.register(DeterminationObserver.self, observer: self)
|
|
broadcaster.register(DeterminationObserver.self, observer: self)
|
|
|
broadcaster.register(BolusFailureObserver.self, observer: self)
|
|
broadcaster.register(BolusFailureObserver.self, observer: self)
|
|
|
broadcaster.register(pumpNotificationObserver.self, observer: self)
|
|
broadcaster.register(pumpNotificationObserver.self, observer: self)
|
|
|
-
|
|
|
|
|
requestNotificationPermissionsIfNeeded()
|
|
requestNotificationPermissionsIfNeeded()
|
|
|
- sendGlucoseNotification()
|
|
|
|
|
|
|
+ Task {
|
|
|
|
|
+ await sendGlucoseNotification()
|
|
|
|
|
+ }
|
|
|
|
|
+ registerHandlers()
|
|
|
|
|
+ setupGlucoseNotification()
|
|
|
subscribeOnLoop()
|
|
subscribeOnLoop()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -76,6 +82,32 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
|
|
|
.store(in: &lifetime)
|
|
.store(in: &lifetime)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private func registerHandlers() {
|
|
|
|
|
+ // Due to the Batch insert this only is used for observing Deletion of Glucose entries
|
|
|
|
|
+ coreDataObserver?.registerHandler(for: "GlucoseStored") { [weak self] in
|
|
|
|
|
+ guard let self = self else { return }
|
|
|
|
|
+ Task {
|
|
|
|
|
+ await self.sendGlucoseNotification()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func setupGlucoseNotification() {
|
|
|
|
|
+ /// custom notification that is sent when a batch insert of glucose objects is done
|
|
|
|
|
+ Foundation.NotificationCenter.default.addObserver(
|
|
|
|
|
+ self,
|
|
|
|
|
+ selector: #selector(handleBatchInsert),
|
|
|
|
|
+ name: .didPerformBatchInsert,
|
|
|
|
|
+ object: nil
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @objc private func handleBatchInsert() {
|
|
|
|
|
+ Task {
|
|
|
|
|
+ await sendGlucoseNotification()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private func addAppBadge(glucose: Int?) {
|
|
private func addAppBadge(glucose: Int?) {
|
|
|
guard let glucose = glucose, settingsManager.settings.glucoseBadge else {
|
|
guard let glucose = glucose, settingsManager.settings.glucoseBadge else {
|
|
|
DispatchQueue.main.async {
|
|
DispatchQueue.main.async {
|
|
@@ -184,32 +216,37 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private func fetchGlucose() -> [GlucoseStored]? {
|
|
|
|
|
- CoreDataStack.shared.fetchEntities(
|
|
|
|
|
|
|
+ private func fetchGlucoseIDs() async -> [NSManagedObjectID] {
|
|
|
|
|
+ let results = await CoreDataStack.shared.fetchEntitiesAsync(
|
|
|
ofType: GlucoseStored.self,
|
|
ofType: GlucoseStored.self,
|
|
|
- onContext: context,
|
|
|
|
|
|
|
+ onContext: backgroundContext,
|
|
|
predicate: NSPredicate.predicateFor20MinAgo,
|
|
predicate: NSPredicate.predicateFor20MinAgo,
|
|
|
key: "date",
|
|
key: "date",
|
|
|
- ascending: true,
|
|
|
|
|
|
|
+ ascending: false,
|
|
|
fetchLimit: 3
|
|
fetchLimit: 3
|
|
|
)
|
|
)
|
|
|
|
|
+ return await backgroundContext.perform {
|
|
|
|
|
+ return results.map(\.objectID)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private func sendGlucoseNotification() {
|
|
|
|
|
- addAppBadge(glucose: nil)
|
|
|
|
|
|
|
+ @MainActor private func sendGlucoseNotification() async {
|
|
|
|
|
+ do {
|
|
|
|
|
+ addAppBadge(glucose: nil)
|
|
|
|
|
+ let glucoseIDs = await fetchGlucoseIDs()
|
|
|
|
|
+ let glucoseObjects = try glucoseIDs.compactMap { id in
|
|
|
|
|
+ try viewContext.existingObject(with: id) as? GlucoseStored
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- context.perform {
|
|
|
|
|
- guard let glucose = self.fetchGlucose(), let lastValue = glucose.first, let lastReading = glucose.first?.glucose,
|
|
|
|
|
- let lastDirection = lastValue.direction,
|
|
|
|
|
- let secondLastReading = glucose.dropFirst().first?.glucose else { return }
|
|
|
|
|
|
|
+ guard let lastReading = glucoseObjects.first?.glucose,
|
|
|
|
|
+ let secondLastReading = glucoseObjects.dropFirst().first?.glucose,
|
|
|
|
|
+ let lastDirection = glucoseObjects.first?.direction else { return }
|
|
|
|
|
|
|
|
- self.addAppBadge(glucose: (glucose.first?.glucose).map { Int($0) })
|
|
|
|
|
|
|
+ addAppBadge(glucose: (glucoseObjects.first?.glucose).map { Int($0) })
|
|
|
|
|
|
|
|
- guard self.glucoseStorage.alarm != nil || self.settingsManager.settings.glucoseNotificationsAlways else {
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ guard glucoseStorage.alarm != nil || settingsManager.settings.glucoseNotificationsAlways else { return }
|
|
|
|
|
|
|
|
- self.ensureCanSendNotification {
|
|
|
|
|
|
|
+ ensureCanSendNotification {
|
|
|
var titles: [String] = []
|
|
var titles: [String] = []
|
|
|
var notificationAlarm = false
|
|
var notificationAlarm = false
|
|
|
|
|
|
|
@@ -224,13 +261,12 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
|
|
|
notificationAlarm = true
|
|
notificationAlarm = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- let delta = glucose.count >= 2 ? lastReading - secondLastReading : nil
|
|
|
|
|
|
|
+ let delta = glucoseObjects.count >= 2 ? lastReading - secondLastReading : nil
|
|
|
let body = self.glucoseText(
|
|
let body = self.glucoseText(
|
|
|
glucoseValue: Int(lastReading),
|
|
glucoseValue: Int(lastReading),
|
|
|
delta: Int(delta ?? 0),
|
|
delta: Int(delta ?? 0),
|
|
|
direction: lastDirection
|
|
direction: lastDirection
|
|
|
- ) + self
|
|
|
|
|
- .infoBody()
|
|
|
|
|
|
|
+ ) + self.infoBody()
|
|
|
|
|
|
|
|
if self.snoozeUntilDate > Date() {
|
|
if self.snoozeUntilDate > Date() {
|
|
|
titles.append(NSLocalizedString("(Snoozed)", comment: "(Snoozed)"))
|
|
titles.append(NSLocalizedString("(Snoozed)", comment: "(Snoozed)"))
|
|
@@ -250,6 +286,10 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
|
|
|
self.addRequest(identifier: .glucocoseNotification, content: content, deleteOld: true)
|
|
self.addRequest(identifier: .glucocoseNotification, content: content, deleteOld: true)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ debugPrint(
|
|
|
|
|
+ "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to send glucose notification with error: \(error.localizedDescription)"
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -414,12 +454,6 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-extension BaseUserNotificationsManager: GlucoseObserver {
|
|
|
|
|
- func glucoseDidUpdate(_: [BloodGlucose]) {
|
|
|
|
|
- sendGlucoseNotification()
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
extension BaseUserNotificationsManager: pumpNotificationObserver {
|
|
extension BaseUserNotificationsManager: pumpNotificationObserver {
|
|
|
func pumpNotification(alert: AlertEntry) {
|
|
func pumpNotification(alert: AlertEntry) {
|
|
|
ensureCanSendNotification {
|
|
ensureCanSendNotification {
|