polscm32 aka Marvout 1 год назад
Родитель
Сommit
9d40d960ac

+ 15 - 0
Trio Watch App Extension/Views/TrioMainWatchView.swift

@@ -29,6 +29,21 @@ struct TrioMainWatchView: View {
                     }
                 }
 
+                VStack {
+                    HStack {
+                        Image(systemName: "drop.fill")
+                        Text("\(state.iob ?? "--") U")
+                            .font(.caption2)
+                            .foregroundStyle(.secondary)
+                    }
+                    HStack {
+                        Image(systemName: "fork.knife")
+                        Text("\(state.cob ?? "--") g")
+                            .font(.caption2)
+                            .foregroundStyle(.secondary)
+                    }
+                }
+
                 HStack(spacing: 20) {
                     Button {
                         showingCarbsSheet = true

+ 11 - 1
Trio Watch App Extension/WatchState.swift

@@ -13,8 +13,10 @@ import WatchConnectivity
 
     var currentGlucose: String = "--"
     var trend: String? = ""
-    var delta: String? = ""
+    var delta: String? = "--"
     var glucoseValues: [(date: Date, glucose: Double)] = []
+    var cob: String? = "--"
+    var iob: String? = "--"
 
     override init() {
         super.init()
@@ -106,6 +108,14 @@ import WatchConnectivity
                 self.delta = delta
             }
 
+            if let iob = message["iob"] as? String {
+                self.iob = iob
+            }
+
+            if let cob = message["cob"] as? String {
+                self.cob = cob
+            }
+
             if let glucoseData = message["glucoseValues"] as? [[String: Any]] {
                 self.glucoseValues = glucoseData.compactMap { data in
                     guard let glucose = data["glucose"] as? Double,

+ 35 - 0
Trio/Sources/Models/WatchData.swift

@@ -0,0 +1,35 @@
+struct WatchState: Hashable, Equatable, Sendable {
+    var currentGlucose: String?
+    var trend: String?
+    var delta: String?
+    var glucoseValues: [(date: Date, glucose: Double)] = []
+    var units: GlucoseUnits = .mmolL
+    var iob: Decimal = 0 // Insulin on Board
+    var cob: Int = 0 // Carbs on Board
+
+    static func == (lhs: WatchState, rhs: WatchState) -> Bool {
+        lhs.currentGlucose == rhs.currentGlucose &&
+            lhs.trend == rhs.trend &&
+            lhs.delta == rhs.delta &&
+            lhs.glucoseValues.count == rhs.glucoseValues.count &&
+            zip(lhs.glucoseValues, rhs.glucoseValues).allSatisfy {
+                $0.0.date == $0.1.date && $0.0.glucose == $0.1.glucose
+            } &&
+            lhs.units == rhs.units &&
+            lhs.iob == rhs.iob &&
+            lhs.cob == rhs.cob
+    }
+
+    func hash(into hasher: inout Hasher) {
+        hasher.combine(currentGlucose)
+        hasher.combine(trend)
+        hasher.combine(delta)
+        for value in glucoseValues {
+            hasher.combine(value.date)
+            hasher.combine(value.glucose)
+        }
+        hasher.combine(units)
+        hasher.combine(iob)
+        hasher.combine(cob)
+    }
+}

+ 7 - 2
Trio/Sources/Models/WatchState.swift

@@ -7,6 +7,8 @@ struct WatchState: Hashable, Equatable, Sendable {
     var delta: String?
     var glucoseValues: [(date: Date, glucose: Double)] = []
     var units: GlucoseUnits = .mmolL
+    var iob: String?
+    var cob: String?
 
     static func == (lhs: WatchState, rhs: WatchState) -> Bool {
         lhs.currentGlucose == rhs.currentGlucose &&
@@ -16,18 +18,21 @@ struct WatchState: Hashable, Equatable, Sendable {
             zip(lhs.glucoseValues, rhs.glucoseValues).allSatisfy {
                 $0.0.date == $0.1.date && $0.0.glucose == $0.1.glucose
             } &&
-            lhs.units == rhs.units
+            lhs.units == rhs.units &&
+            lhs.iob == rhs.iob &&
+            lhs.cob == rhs.cob
     }
 
     func hash(into hasher: inout Hasher) {
         hasher.combine(currentGlucose)
         hasher.combine(trend)
         hasher.combine(delta)
-        // Hash each element individually
         for value in glucoseValues {
             hasher.combine(value.date)
             hasher.combine(value.glucose)
         }
         hasher.combine(units)
+        hasher.combine(iob)
+        hasher.combine(cob)
     }
 }

+ 56 - 15
Trio/Sources/Services/WatchManager/AppleWatchManager.swift

@@ -16,6 +16,7 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
     @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var apsManager: APSManager!
     @Injected() private var settingsManager: SettingsManager!
+    @Injected() private var determinationStorage: DeterminationStorage!
 
     private var units: GlucoseUnits = .mgdL
 
@@ -24,7 +25,7 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
 
     typealias PumpEvent = PumpEventStored.EventType
 
-    let glucoseFetchContext = CoreDataStack.shared.newTaskContext()
+    let backgroundContext = CoreDataStack.shared.newTaskContext()
     let viewContext = CoreDataStack.shared.persistentContainer.viewContext
 
     init(resolver: Resolver) {
@@ -47,12 +48,31 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                 guard let self = self else { return }
                 Task {
                     let state = await self.setupWatchState()
-                    self.sendGlucoseData(state)
+                    self.sendDataToWatch(state)
                 }
             }
             .store(in: &subscriptions)
     }
 
+    private func registerHandlers() {
+        coreDataPublisher?.filterByEntityName("OrefDetermination").sink { [weak self] _ in
+            guard let self = self else { return }
+            Task {
+                let state = await self.setupWatchState()
+                self.sendDataToWatch(state)
+            }
+        }.store(in: &subscriptions)
+
+        // Due to the Batch insert this only is used for observing Deletion of Glucose entries
+        coreDataPublisher?.filterByEntityName("GlucoseStored").sink { [weak self] _ in
+            guard let self = self else { return }
+            Task {
+                let state = await self.setupWatchState()
+                self.sendDataToWatch(state)
+            }
+        }.store(in: &subscriptions)
+    }
+
     /// Sets up the WatchConnectivity session if the device supports it
     private func setupWatchSession() {
         if WCSession.isSupported() {
@@ -78,17 +98,33 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
     }
 
     /// Prepares the current state data to be sent to the Watch
-    /// - Returns: WatchState containing current glucose readings and trends
+    /// - Returns: WatchState containing current glucose readings and trends and determination infos for displaying cob and iob in the view
     private func setupWatchState() async -> WatchState {
-        let ids = await fetchGlucose()
+        // Get NSManagedObjectIDs
+        let glucoseIds = await fetchGlucose()
+        // TODO: - if we want that the watch immediately displays updated cob and iob values when entered via treatment view from phone, we would need to use a predicate here that also filters for NON-ENACTED Determinations
+        let determinationIds = await determinationStorage.fetchLastDeterminationObjectID(
+            predicate: NSPredicate.predicateFor30MinAgoForDetermination
+        )
 
         // Get NSManagedObjects
         let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared
-            .getNSManagedObject(with: ids, context: glucoseFetchContext)
+            .getNSManagedObject(with: glucoseIds, context: backgroundContext)
+        let determinationObjects: [OrefDetermination] = await CoreDataStack.shared
+            .getNSManagedObject(with: determinationIds, context: backgroundContext)
 
-        return await glucoseFetchContext.perform {
+        return await backgroundContext.perform {
             var watchState = WatchState()
 
+            // Set IOB and COB from latest determination
+            if let latestDetermination = determinationObjects.first {
+                let iob = latestDetermination.iob ?? 0
+                watchState.iob = Formatter.decimalFormatterWithTwoFractionDigits.string(from: iob)
+
+                let cob = NSNumber(value: latestDetermination.cob)
+                watchState.cob = Formatter.integerFormatter.string(from: cob)
+            }
+
             guard let latestGlucose = glucoseObjects.first else {
                 return watchState
             }
@@ -130,14 +166,14 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
     private func fetchGlucose() async -> [NSManagedObjectID] {
         let results = await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: GlucoseStored.self,
-            onContext: glucoseFetchContext,
+            onContext: backgroundContext,
             predicate: NSPredicate.glucose,
             key: "date",
             ascending: false,
             fetchLimit: 288
         )
 
-        return await glucoseFetchContext.perform {
+        return await backgroundContext.perform {
             guard let fetchedResults = results as? [GlucoseStored] else { return [] }
 
             return fetchedResults.map(\.objectID)
@@ -146,9 +182,9 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
 
     // MARK: - Send Data to Watch
 
-    /// Sends the current glucose state to the connected Watch
+    /// Sends the state of type WatchState to the connected Watch
     /// - Parameter state: Current WatchState containing glucose data to be sent
-    func sendGlucoseData(_ state: WatchState) {
+    func sendDataToWatch(_ state: WatchState) {
         guard let session = session, session.isReachable else {
             print("⌚️ Watch not reachable")
             return
@@ -163,13 +199,18 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                     "glucose": value.glucose,
                     "date": value.date.timeIntervalSince1970
                 ]
-            }
+            },
+            "iob": state.iob ?? "0",
+            "cob": state.cob ?? "0"
         ]
 
-        print("📱 Sending to watch: currentGlucose: \(state.currentGlucose ?? "nil"), trend: \(state.trend ?? "nil")")
+        print("📱 Sending to watch - Message content:")
+        message.forEach { key, value in
+            print("📱 \(key): \(value) (type: \(type(of: value)))")
+        }
 
         session.sendMessage(message, replyHandler: nil) { error in
-            print("❌ Error sending glucose data: \(error.localizedDescription)")
+            print("❌ Error sending data: \(error.localizedDescription)")
         }
     }
 
@@ -187,7 +228,7 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
         // Try to send initial data after activation
         Task {
             let state = await self.setupWatchState()
-            self.sendGlucoseData(state)
+            self.sendDataToWatch(state)
         }
     }
 
@@ -228,7 +269,7 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
             // Try to send data when connection is established
             Task {
                 let state = await self.setupWatchState()
-                self.sendGlucoseData(state)
+                self.sendDataToWatch(state)
             }
         } else {
             // Try to reconnect after a short delay