Преглед изворни кода

WIP: bridge CGMManager.providesOwnGlucoseAlerts until LoopKit next-dev bump

trioneer пре 3 недеља
родитељ
комит
ecee548dcb

+ 4 - 0
Trio.xcodeproj/project.pbxproj

@@ -178,6 +178,7 @@
 		38E87403274F78C000975559 /* libswiftCoreNFC.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 38E87402274F78C000975559 /* libswiftCoreNFC.tbd */; settings = {ATTRIBUTES = (Weak, ); }; };
 		38E87408274F9AD000975559 /* UserNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E87407274F9AD000975559 /* UserNotificationsManager.swift */; };
 		BD1179202F4E22C100F90001 /* TrioAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179212F4E22C100F90001 /* TrioAlertManager.swift */; };
+		CA01000000000000000010C6 /* CGMManagerAlertOwnership.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA01000000000000000010C5 /* CGMManagerAlertOwnership.swift */; };
 		BD1179222F4E22C100F90001 /* TrioModalAlertScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179232F4E22C100F90001 /* TrioModalAlertScheduler.swift */; };
 		BD1179242F4E22C100F90001 /* TrioUserNotificationAlertScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179252F4E22C100F90001 /* TrioUserNotificationAlertScheduler.swift */; };
 		BD1179322F4E22C100F90001 /* CriticalAlertAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179312F4E22C100F90001 /* CriticalAlertAudioPlayer.swift */; };
@@ -1174,6 +1175,7 @@
 		38E87402274F78C000975559 /* libswiftCoreNFC.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreNFC.tbd; path = usr/lib/swift/libswiftCoreNFC.tbd; sourceTree = SDKROOT; };
 		38E87407274F9AD000975559 /* UserNotificationsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationsManager.swift; sourceTree = "<group>"; };
 		BD1179212F4E22C100F90001 /* TrioAlertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrioAlertManager.swift; sourceTree = "<group>"; };
+		CA01000000000000000010C5 /* CGMManagerAlertOwnership.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMManagerAlertOwnership.swift; sourceTree = "<group>"; };
 		BD1179232F4E22C100F90001 /* TrioModalAlertScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrioModalAlertScheduler.swift; sourceTree = "<group>"; };
 		BD1179252F4E22C100F90001 /* TrioUserNotificationAlertScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrioUserNotificationAlertScheduler.swift; sourceTree = "<group>"; };
 		BD1179312F4E22C100F90001 /* CriticalAlertAudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CriticalAlertAudioPlayer.swift; sourceTree = "<group>"; };
@@ -2386,6 +2388,7 @@
 				BD1179212F4E22C100F90001 /* TrioAlertManager.swift */,
 				BD1179232F4E22C100F90001 /* TrioModalAlertScheduler.swift */,
 				BD1179252F4E22C100F90001 /* TrioUserNotificationAlertScheduler.swift */,
+				CA01000000000000000010C5 /* CGMManagerAlertOwnership.swift */,
 			);
 			path = Alerts;
 			sourceTree = "<group>";
@@ -5078,6 +5081,7 @@
 				DDFF20312DB1D15500AB8A96 /* BluetoothPermissionStepView.swift in Sources */,
 				38E87408274F9AD000975559 /* UserNotificationsManager.swift in Sources */,
 				BD1179202F4E22C100F90001 /* TrioAlertManager.swift in Sources */,
+				CA01000000000000000010C6 /* CGMManagerAlertOwnership.swift in Sources */,
 				BD1179222F4E22C100F90001 /* TrioModalAlertScheduler.swift in Sources */,
 				BD1179242F4E22C100F90001 /* TrioUserNotificationAlertScheduler.swift in Sources */,
 				BD1179322F4E22C100F90001 /* CriticalAlertAudioPlayer.swift in Sources */,

+ 21 - 1
Trio/Sources/Models/GlucoseAlerts/GlucoseAlertConfiguration.swift

@@ -3,13 +3,33 @@ import Foundation
 struct GlucoseAlertConfiguration: Codable, Equatable {
     var dayStart: TimeOfDay
     var nightStart: TimeOfDay
+    /// Force Trio alarms on even when the CGM advertises its own.
+    var forceTrioAlertsWhenCGMProvidesOwn: Bool
 
     init(
         dayStart: TimeOfDay = TimeOfDay(hour: 6, minute: 0),
-        nightStart: TimeOfDay = TimeOfDay(hour: 22, minute: 0)
+        nightStart: TimeOfDay = TimeOfDay(hour: 22, minute: 0),
+        forceTrioAlertsWhenCGMProvidesOwn: Bool = false
     ) {
         self.dayStart = dayStart
         self.nightStart = nightStart
+        self.forceTrioAlertsWhenCGMProvidesOwn = forceTrioAlertsWhenCGMProvidesOwn
+    }
+
+    private enum CodingKeys: String, CodingKey {
+        case dayStart
+        case nightStart
+        case forceTrioAlertsWhenCGMProvidesOwn
+    }
+
+    init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        dayStart = try container.decode(TimeOfDay.self, forKey: .dayStart)
+        nightStart = try container.decode(TimeOfDay.self, forKey: .nightStart)
+        forceTrioAlertsWhenCGMProvidesOwn = try container.decodeIfPresent(
+            Bool.self,
+            forKey: .forceTrioAlertsWhenCGMProvidesOwn
+        ) ?? false
     }
 
     /// Resolve whether `date` falls into the user's "night" window. Mirrors

+ 12 - 0
Trio/Sources/Modules/GlucoseAlerts/View/GlucoseAlertsRootView.swift

@@ -51,11 +51,23 @@ extension GlucoseAlerts {
                     }.listRowBackground(Color.chart)
                 }
 
+                // FIXME: make this into a nice setting with mini and verbose hint
                 Section {
                     Text("Day & Night Windows")
                         .navigationLink(to: .alarmWindows, from: self)
                 }.listRowBackground(Color.chart)
 
+                Section(footer: Text(
+                    "On by default for all CGMs that handle glucose alerts (all Dexcom CGMs, xDrip4iOS). Turn off if you've disabled those and want Trio to alert you instead."
+                )) {
+                    Toggle(isOn: Binding(
+                        get: { !store.configuration.forceTrioAlertsWhenCGMProvidesOwn },
+                        set: { store.configuration.forceTrioAlertsWhenCGMProvidesOwn = !$0 }
+                    )) {
+                        Text("Use CGM App Alerts")
+                    }
+                }.listRowBackground(Color.chart)
+
                 SettingInputSection(
                     decimalValue: $decimalPlaceholder,
                     booleanValue: $state.glucoseBadge,

+ 20 - 0
Trio/Sources/Services/Alerts/CGMManagerAlertOwnership.swift

@@ -0,0 +1,20 @@
+import CGMBLEKit
+import G7SensorKit
+import LibreTransmitter
+import LoopKit
+
+/// Trio-side stand-in for LoopKit next-dev's `CGMManager.providesOwnGlucoseAlerts`.
+/// Collapses to `manager?.providesOwnGlucoseAlerts ?? false` once the fork bumps.
+enum CGMManagerAlertOwnership {
+    static func providesOwnGlucoseAlerts(_ manager: CGMManager?) -> Bool {
+        switch manager {
+        case is G5CGMManager,
+             is G6CGMManager,
+             is G7CGMManager,
+             is LibreTransmitterManagerV3:
+            return true
+        default:
+            return false
+        }
+    }
+}

+ 25 - 0
Trio/Sources/Services/Alerts/GlucoseAlertCoordinator.swift

@@ -25,6 +25,7 @@ final class GlucoseAlertCoordinator: Injectable {
     @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var trioAlertManager: TrioAlertManager!
     @Injected() private var settingsManager: SettingsManager!
+    @Injected() private var fetchGlucoseManager: FetchGlucoseManager!
 
     private let coreDataContext = CoreDataStack.shared.newTaskContext()
     private let evaluationQueue = DispatchQueue(label: "GlucoseAlertCoordinator.queue")
@@ -56,6 +57,11 @@ final class GlucoseAlertCoordinator: Injectable {
         Date().timeIntervalSince(launchedAt) < Self.launchQuietWindow
     }
 
+    private var effectiveTrioAlertsEnabled: Bool {
+        if configurationSnapshot.forceTrioAlertsWhenCGMProvidesOwn { return true }
+        return !CGMManagerAlertOwnership.providesOwnGlucoseAlerts(fetchGlucoseManager?.cgmManager)
+    }
+
     init(resolver: Resolver) {
         injectServices(resolver)
         let store = GlucoseAlertsStore.shared
@@ -83,6 +89,10 @@ final class GlucoseAlertCoordinator: Injectable {
     /// onto `evaluationQueue` to mutate `firingAlertIDs` safely.
     private func evaluateGlucoseAlarms() async {
         guard !isInLaunchQuietWindow else { return }
+        guard effectiveTrioAlertsEnabled else {
+            retractAllFiringIfNeeded()
+            return
+        }
         guard let latestValue = await fetchLatestReadingMgDL() else { return }
         let snapshot = alertsSnapshot
         let configuration = configurationSnapshot
@@ -157,6 +167,10 @@ final class GlucoseAlertCoordinator: Injectable {
 
     private func evaluateForecast(_ determination: Determination) {
         guard !isInLaunchQuietWindow else { return }
+        guard effectiveTrioAlertsEnabled else {
+            retractAllFiringIfNeeded()
+            return
+        }
         let snapshot = alertsSnapshot
         let configuration = configurationSnapshot
         let now = Date()
@@ -234,6 +248,17 @@ final class GlucoseAlertCoordinator: Injectable {
         trioAlertManager.retractAlert(identifier: alertID(for: alarm))
     }
 
+    private func retractAllFiringIfNeeded() {
+        evaluationQueue.async { [weak self] in
+            guard let self, !self.firingAlertIDs.isEmpty else { return }
+            let snapshot = self.alertsSnapshot
+            for alarm in snapshot where self.firingAlertIDs.contains(alarm.id) {
+                self.trioAlertManager.retractAlert(identifier: self.alertID(for: alarm))
+            }
+            self.firingAlertIDs.removeAll()
+        }
+    }
+
     private func shouldRetract(_ alarm: GlucoseAlert, latestMgDL: Decimal) -> Bool {
         dispatchPrecondition(condition: .onQueue(evaluationQueue))
         guard firingAlertIDs.contains(alarm.id) else { return false }