Procházet zdrojové kódy

fix bolusCalculation animation, fix bolusProgress, refactoring

polscm32 aka Marvout před 1 rokem
rodič
revize
ef42b6a4bf

+ 194 - 0
Trio Watch App Extension/WatchState+Requests.swift

@@ -0,0 +1,194 @@
+import Foundation
+import WatchConnectivity
+
+// MARK: - Send Data to Phone
+
+extension WatchState {
+    /// Sends a bolus insulin request to the paired iPhone
+    /// - Parameters:
+    ///   - amount: The insulin amount to be delivered
+    func sendBolusRequest(_ amount: Decimal) {
+        guard let session = session, session.isReachable else { return }
+        isBolusCanceled = false // Reset canceled state when starting new bolus
+        activeBolusAmount = Double(truncating: amount as NSNumber) // Set active bolus amount
+
+        let message: [String: Any] = [
+            "bolus": amount
+        ]
+
+        session.sendMessage(message, replyHandler: nil) { error in
+            print("Error sending bolus request: \(error.localizedDescription)")
+        }
+
+        // Display pending communication animation
+        showCommsAnimation = true
+    }
+
+    /// Sends a carbohydrate entry request to the paired iPhone
+    /// - Parameters:
+    ///   - amount: The amount of carbs in grams
+    ///   - date: The timestamp for the carb entry (defaults to current time)
+    func sendCarbsRequest(_ amount: Int, _ date: Date = Date()) {
+        guard let session = session, session.isReachable else { return }
+
+        let message: [String: Any] = [
+            "carbs": amount,
+            "date": date.timeIntervalSince1970
+        ]
+
+        session.sendMessage(message, replyHandler: nil) { error in
+            print("Error sending carbs request: \(error.localizedDescription)")
+        }
+
+        // Display pending communication animation
+        showCommsAnimation = true
+    }
+
+    /// Sends a meal and bolus insulin combo request to the paired iPhone
+    /// - Parameters:
+    ///   - amount: The insulin amount to be delivered
+    ///   - isExternal: Indicates if the bolus is from an external source
+    func sendMealBolusComboRequest(carbsAmount _: Decimal, bolusAmount: Decimal, _ date: Date = Date()) {
+        guard let session = session, session.isReachable else { return }
+
+        let message: [String: Any] = [
+            "bolus": bolusAmount,
+            "carbs": bolusAmount,
+            "date": date.timeIntervalSince1970
+        ]
+
+        session.sendMessage(message, replyHandler: nil) { error in
+            print("Error sending meal bolus combo request: \(error.localizedDescription)")
+        }
+
+        // Display pending communication animation
+        showCommsAnimation = true
+        isMealBolusCombo = true
+    }
+
+    /// Sends a request to cancel the current override preset to the paired iPhone
+    func sendCancelOverrideRequest() {
+        guard let session = session, session.isReachable else { return }
+
+        let message: [String: Any] = [
+            "cancelOverride": true
+        ]
+
+        session.sendMessage(message, replyHandler: nil) { error in
+            print("⌚️ Error sending cancel override request: \(error.localizedDescription)")
+        }
+
+        // Display pending communication animation
+        showCommsAnimation = true
+    }
+
+    /// Sends a request to activate an override preset to the paired iPhone
+    /// - Parameter presetName: The name of the override preset to activate
+    func sendActivateOverrideRequest(presetName: String) {
+        guard let session = session, session.isReachable else { return }
+
+        let message: [String: Any] = [
+            "activateOverride": presetName
+        ]
+
+        session.sendMessage(message, replyHandler: nil) { error in
+            print("⌚️ Error sending activate override request: \(error.localizedDescription)")
+        }
+
+        // Display pending communication animation
+        showCommsAnimation = true
+    }
+
+    /// Sends a request to cancel the current temporary target to the paired iPhone
+    func sendCancelTempTargetRequest() {
+        guard let session = session, session.isReachable else { return }
+
+        let message: [String: Any] = [
+            "cancelTempTarget": true
+        ]
+
+        session.sendMessage(message, replyHandler: nil) { error in
+            print("⌚️ Error sending cancel temp target request: \(error.localizedDescription)")
+        }
+
+        // Display pending communication animation
+        showCommsAnimation = true
+    }
+
+    /// Sends a request to activate a temporary target preset to the paired iPhone
+    /// - Parameter presetName: The name of the temporary target preset to activate
+    func sendActivateTempTargetRequest(presetName: String) {
+        guard let session = session, session.isReachable else { return }
+
+        let message: [String: Any] = [
+            "activateTempTarget": presetName
+        ]
+
+        session.sendMessage(message, replyHandler: nil) { error in
+            print("⌚️ Error sending activate temp target request: \(error.localizedDescription)")
+        }
+
+        // Display pending communication animation
+        showCommsAnimation = true
+    }
+
+    /// Sends a request to cancel the current bolus delivery to the paired iPhone
+    func sendCancelBolusRequest() {
+        isBolusCanceled = true
+
+        guard let session = session, session.isReachable else { return }
+
+        let message: [String: Any] = [
+            "cancelBolus": true
+        ]
+
+        session.sendMessage(message, replyHandler: nil) { error in
+            print("Error sending cancel bolus request: \(error.localizedDescription)")
+        }
+
+        // Reset when cancelled
+        bolusProgress = 0
+        activeBolusAmount = 0
+
+        // Display pending communication animation
+        showCommsAnimation = true
+    }
+
+    /// Sends a request to calculate a bolus recommendation based on the current carbs amount
+    func requestBolusRecommendation() {
+        guard let session = session, session.isReachable else { return }
+
+        let message: [String: Any] = [
+            WatchMessageKeys.requestBolusRecommendation: true,
+            WatchMessageKeys.carbs: carbsAmount
+        ]
+
+        session.sendMessage(message, replyHandler: nil) { error in
+            print("Error requesting bolus recommendation: \(error.localizedDescription)")
+        }
+
+        if bolusAmount == 0 {
+            showBolusCalculationProgress = true
+        }
+    }
+
+    func requestWatchStateUpdate() {
+        guard let session = session, session.activationState == .activated else {
+            print("⌚️ Session not activated, activating...")
+            session?.activate()
+            return
+        }
+
+        if session.isReachable {
+            print("⌚️ Request an update for watch state from Trio iPhone app...")
+
+            let message = [WatchMessageKeys.requestWatchUpdate: WatchMessageKeys.watchState]
+
+            session.sendMessage(message, replyHandler: nil) { error in
+                print("⌚️ Update request for fresh watch state data: \(error.localizedDescription)")
+            }
+        } else {
+            print("⌚️ Phone not reachable for watch state update")
+        }
+    }
+}

+ 225 - 372
Trio Watch App Extension/WatchState.swift

@@ -8,7 +8,7 @@ import WatchConnectivity
     // MARK: - Properties
 
     /// The WatchConnectivity session instance used for communication
-    private var session: WCSession?
+    var session: WCSession?
     /// Indicates if the paired iPhone is currently reachable
     var isReachable = false
 
@@ -95,168 +95,6 @@ import WatchConnectivity
         }
     }
 
-    // MARK: - Send Data to Phone
-
-    /// Sends a bolus insulin request to the paired iPhone
-    /// - Parameters:
-    ///   - amount: The insulin amount to be delivered
-    func sendBolusRequest(_ amount: Decimal) {
-        guard let session = session, session.isReachable else { return }
-        isBolusCanceled = false // Reset canceled state when starting new bolus
-        activeBolusAmount = Double(truncating: amount as NSNumber) // Set active bolus amount
-
-        let message: [String: Any] = [
-            "bolus": amount
-        ]
-
-        session.sendMessage(message, replyHandler: nil) { error in
-            print("Error sending bolus request: \(error.localizedDescription)")
-        }
-
-        // Display pending communication animation
-        showCommsAnimation = true
-    }
-
-    /// Sends a carbohydrate entry request to the paired iPhone
-    /// - Parameters:
-    ///   - amount: The amount of carbs in grams
-    ///   - date: The timestamp for the carb entry (defaults to current time)
-    func sendCarbsRequest(_ amount: Int, _ date: Date = Date()) {
-        guard let session = session, session.isReachable else { return }
-
-        let message: [String: Any] = [
-            "carbs": amount,
-            "date": date.timeIntervalSince1970
-        ]
-
-        session.sendMessage(message, replyHandler: nil) { error in
-            print("Error sending carbs request: \(error.localizedDescription)")
-        }
-
-        // Display pending communication animation
-        showCommsAnimation = true
-    }
-
-    /// Sends a meal and bolus insulin combo request to the paired iPhone
-    /// - Parameters:
-    ///   - amount: The insulin amount to be delivered
-    ///   - isExternal: Indicates if the bolus is from an external source
-    func sendMealBolusComboRequest(carbsAmount _: Decimal, bolusAmount: Decimal, _ date: Date = Date()) {
-        guard let session = session, session.isReachable else { return }
-
-        let message: [String: Any] = [
-            "bolus": bolusAmount,
-            "carbs": bolusAmount,
-            "date": date.timeIntervalSince1970
-        ]
-
-        session.sendMessage(message, replyHandler: nil) { error in
-            print("Error sending meal bolus combo request: \(error.localizedDescription)")
-        }
-
-        // Display pending communication animation
-        showCommsAnimation = true
-        isMealBolusCombo = true
-    }
-
-    func sendCancelOverrideRequest() {
-        guard let session = session, session.isReachable else { return }
-
-        let message: [String: Any] = [
-            "cancelOverride": true
-        ]
-
-        session.sendMessage(message, replyHandler: nil) { error in
-            print("⌚️ Error sending cancel override request: \(error.localizedDescription)")
-        }
-
-        // Display pending communication animation
-        showCommsAnimation = true
-    }
-
-    func sendActivateOverrideRequest(presetName: String) {
-        guard let session = session, session.isReachable else { return }
-
-        let message: [String: Any] = [
-            "activateOverride": presetName
-        ]
-
-        session.sendMessage(message, replyHandler: nil) { error in
-            print("⌚️ Error sending activate override request: \(error.localizedDescription)")
-        }
-
-        // Display pending communication animation
-        showCommsAnimation = true
-    }
-
-    func sendCancelTempTargetRequest() {
-        guard let session = session, session.isReachable else { return }
-
-        let message: [String: Any] = [
-            "cancelTempTarget": true
-        ]
-
-        session.sendMessage(message, replyHandler: nil) { error in
-            print("⌚️ Error sending cancel temp target request: \(error.localizedDescription)")
-        }
-
-        // Display pending communication animation
-        showCommsAnimation = true
-    }
-
-    func sendActivateTempTargetRequest(presetName: String) {
-        guard let session = session, session.isReachable else { return }
-
-        let message: [String: Any] = [
-            "activateTempTarget": presetName
-        ]
-
-        session.sendMessage(message, replyHandler: nil) { error in
-            print("⌚️ Error sending activate temp target request: \(error.localizedDescription)")
-        }
-
-        // Display pending communication animation
-        showCommsAnimation = true
-    }
-
-    func sendCancelBolusRequest() {
-        isBolusCanceled = true
-
-        guard let session = session, session.isReachable else { return }
-
-        let message: [String: Any] = [
-            "cancelBolus": true
-        ]
-
-        session.sendMessage(message, replyHandler: nil) { error in
-            print("Error sending cancel bolus request: \(error.localizedDescription)")
-        }
-
-        // Reset when cancelled
-        bolusProgress = 0
-        activeBolusAmount = 0
-
-        // Display pending communication animation
-        showCommsAnimation = true
-    }
-
-    func requestBolusRecommendation() {
-        guard let session = session, session.isReachable else { return }
-
-        let message: [String: Any] = [
-            WatchMessageKeys.requestBolusRecommendation: true,
-            WatchMessageKeys.carbs: carbsAmount
-        ]
-
-        session.sendMessage(message, replyHandler: nil) { error in
-            print("Error requesting bolus recommendation: \(error.localizedDescription)")
-        }
-
-        if bolusAmount == 0 {
-            showBolusCalculationProgress = true
-        }
-    }
-
     // MARK: – Handle Acknowledgement Messages FROM Phone
 
     func handleAcknowledgment(success: Bool, message: String, isFinal: Bool = true) {
@@ -284,146 +122,6 @@ import WatchConnectivity
         }
     }
 
-    func requestWatchStateUpdate() {
-        guard let session = session, session.activationState == .activated else {
-            print("⌚️ Session not activated, activating...")
-            session?.activate()
-            return
-        }
-
-        if session.isReachable {
-            print("⌚️ Request an update for watch state from Trio iPhone app...")
-
-            let message = [WatchMessageKeys.requestWatchUpdate: WatchMessageKeys.watchState]
-
-            session.sendMessage(message, replyHandler: nil) { error in
-                print("⌚️ Update request for fresh watch state data: \(error.localizedDescription)")
-            }
-        } else {
-            print("⌚️ Phone not reachable for watch state update")
-        }
-    }
-
-    private func processRawDataForWatchState(_ message: [String: Any]) {
-        if let currentGlucose = message[WatchMessageKeys.currentGlucose] as? String {
-            self.currentGlucose = currentGlucose
-        }
-
-        if let currentGlucoseColorString = message[WatchMessageKeys.currentGlucoseColorString] as? String {
-            self.currentGlucoseColorString = currentGlucoseColorString
-        }
-
-        if let trend = message[WatchMessageKeys.trend] as? String {
-            self.trend = trend
-        }
-
-        if let delta = message[WatchMessageKeys.delta] as? String {
-            self.delta = delta
-        }
-
-        if let iob = message[WatchMessageKeys.iob] as? String {
-            self.iob = iob
-        }
-
-        if let cob = message[WatchMessageKeys.cob] as? String {
-            self.cob = cob
-        }
-
-        if let lastLoopTime = message[WatchMessageKeys.lastLoopTime] as? String {
-            self.lastLoopTime = lastLoopTime
-        }
-
-        if let glucoseData = message[WatchMessageKeys.glucoseValues] as? [[String: Any]] {
-            glucoseValues = glucoseData.compactMap { data in
-                guard let glucose = data["glucose"] as? Double,
-                      let timestamp = data["date"] as? TimeInterval,
-                      let colorString = data["color"] as? String
-                else { return nil }
-
-                return (
-                    Date(timeIntervalSince1970: timestamp),
-                    glucose,
-                    colorString.toColor() // Convert colorString to Color
-                )
-            }
-            .sorted { $0.date < $1.date }
-        }
-
-        if let overrideData = message[WatchMessageKeys.overridePresets] as? [[String: Any]] {
-            overridePresets = overrideData.compactMap { data in
-                guard let name = data["name"] as? String,
-                      let isEnabled = data["isEnabled"] as? Bool
-                else { return nil }
-                return OverridePresetWatch(name: name, isEnabled: isEnabled)
-            }
-        }
-
-        if let tempTargetData = message[WatchMessageKeys.tempTargetPresets] as? [[String: Any]] {
-            tempTargetPresets = tempTargetData.compactMap { data in
-                guard let name = data["name"] as? String,
-                      let isEnabled = data["isEnabled"] as? Bool
-                else { return nil }
-                return TempTargetPresetWatch(name: name, isEnabled: isEnabled)
-            }
-        }
-
-        if let bolusProgress = message[WatchMessageKeys.bolusProgress] as? Double {
-            if !isBolusCanceled {
-                self.bolusProgress = bolusProgress
-            }
-        }
-
-        if let bolusWasCanceled = message[WatchMessageKeys.bolusCanceled] as? Bool, bolusWasCanceled {
-            bolusProgress = 0
-            activeBolusAmount = 0
-//            return
-        }
-
-        if let maxBolusValue = message[WatchMessageKeys.maxBolus] {
-            print("⌚️ Received maxBolus: \(maxBolusValue) of type \(type(of: maxBolusValue))")
-            if let decimalValue = (maxBolusValue as? NSNumber)?.decimalValue {
-                maxBolus = decimalValue
-                print("⌚️ Converted maxBolus to: \(decimalValue)")
-            }
-        }
-
-        if let maxCarbsValue = message[WatchMessageKeys.maxCarbs] {
-            if let decimalValue = (maxCarbsValue as? NSNumber)?.decimalValue {
-                maxCarbs = decimalValue
-            }
-        }
-
-        if let maxFatValue = message[WatchMessageKeys.maxFat] {
-            if let decimalValue = (maxFatValue as? NSNumber)?.decimalValue {
-                maxFat = decimalValue
-            }
-        }
-
-        if let maxProteinValue = message[WatchMessageKeys.maxProtein] {
-            if let decimalValue = (maxProteinValue as? NSNumber)?.decimalValue {
-                maxProtein = decimalValue
-            }
-        }
-
-        if let maxIOBValue = message[WatchMessageKeys.maxIOB] {
-            if let decimalValue = (maxIOBValue as? NSNumber)?.decimalValue {
-                maxIOB = decimalValue
-            }
-        }
-
-        if let maxCOBValue = message[WatchMessageKeys.maxCOB] {
-            if let decimalValue = (maxCOBValue as? NSNumber)?.decimalValue {
-                maxCOB = decimalValue
-            }
-        }
-
-        if let bolusIncrement = message[WatchMessageKeys.bolusIncrement] {
-            if let decimalValue = (bolusIncrement as? NSNumber)?.decimalValue {
-                self.bolusIncrement = decimalValue
-            }
-        }
-    }
-
     // MARK: - WCSessionDelegate
 
     /// Called when the session has completed activation
@@ -448,8 +146,7 @@ import WatchConnectivity
         }
     }
 
-    /// Handles incoming messages from the paired iPhone
-    /// Updates local glucose data, trend, and delta information
+    /// Handles incoming messages from the paired iPhone when Phone is in the foreground
     func session(_: WCSession, didReceiveMessage message: [String: Any]) {
         print("⌚️ Watch received data: \(message)")
 
@@ -469,6 +166,7 @@ import WatchConnectivity
                     self.showSyncingAnimation = false
                 }
             }
+            return
         }
 
         // Else if the message is an "ack" at the top level
@@ -483,7 +181,37 @@ import WatchConnectivity
                 self.showSyncingAnimation = false
             }
             processWatchMessage(message)
+            return
 
+                    // Recommended bolus is also not part of the WatchState message, hence the extra condition here
+        } else if
+            let recommendedBolus = message[WatchMessageKeys.recommendedBolus] as? NSNumber
+        {
+            print("⌚️ Received recommended bolus: \(recommendedBolus)")
+            self.recommendedBolus = recommendedBolus.decimalValue
+            showBolusCalculationProgress = false
+            return
+
+                    // Handle bolus progress updates
+        } else if
+            let progress = message[WatchMessageKeys.bolusProgress] as? Double
+        {
+            DispatchQueue.main.async {
+                if !self.isBolusCanceled {
+                    self.bolusProgress = progress
+                }
+            }
+            return
+
+                    // Handle bolus cancellation
+        } else if
+            message[WatchMessageKeys.bolusCanceled] as? Bool == true
+        {
+            DispatchQueue.main.async {
+                self.bolusProgress = 0
+                self.activeBolusAmount = 0
+            }
+            return
         } else {
             print("⌚️ Faulty data. Skipping...")
             DispatchQueue.main.async {
@@ -492,6 +220,7 @@ import WatchConnectivity
         }
     }
 
+    /// Handles incoming messages from the paired iPhone when Phone is in the background
     func session(_: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
         print("⌚️ Watch received data: \(userInfo)")
 
@@ -511,6 +240,7 @@ import WatchConnectivity
                     self.showSyncingAnimation = false
                 }
             }
+            return
         }
 
         // Else if the message is an "ack" at the top level
@@ -525,7 +255,37 @@ import WatchConnectivity
                 self.showSyncingAnimation = false
             }
             processWatchMessage(userInfo)
+            return
+
+                    // Recommended bolus is also not part of the WatchState message, hence the extra condition here
+        } else if
+            let recommendedBolus = userInfo[WatchMessageKeys.recommendedBolus] as? NSNumber
+        {
+            print("⌚️ Received recommended bolus: \(recommendedBolus)")
+            self.recommendedBolus = recommendedBolus.decimalValue
+            showBolusCalculationProgress = false
+            return
 
+                    // Handle bolus progress updates
+        } else if
+            let progress = userInfo[WatchMessageKeys.bolusProgress] as? Double
+        {
+            DispatchQueue.main.async {
+                if !self.isBolusCanceled {
+                    self.bolusProgress = progress
+                }
+            }
+            return
+
+                    // Handle bolus cancellation
+        } else if
+            userInfo[WatchMessageKeys.bolusCanceled] as? Bool == true
+        {
+            DispatchQueue.main.async {
+                self.bolusProgress = 0
+                self.activeBolusAmount = 0
+            }
+            return
         } else {
             print("⌚️ Faulty data. Skipping...")
             DispatchQueue.main.async {
@@ -534,6 +294,65 @@ import WatchConnectivity
         }
     }
 
+    /// Called when the reachability status of the paired iPhone changes
+    /// Updates the local reachability status
+    func sessionReachabilityDidChange(_ session: WCSession) {
+        DispatchQueue.main.async {
+            print("⌚️ Watch reachability changed: \(session.isReachable)")
+
+            if session.isReachable {
+                // request fresh data from watch
+                self.requestWatchStateUpdate()
+
+                // reset input amounts
+                self.bolusAmount = 0
+                self.carbsAmount = 0
+                // reset auth progress
+                self.confirmationProgress = 0
+            }
+        }
+    }
+
+    /// Handles incoming messages that either contain an acknowledgement or fresh watchState data  (<15 min)
+    private func processWatchMessage(_ message: [String: Any]) {
+        DispatchQueue.main.async {
+            // 1) Acknowledgment logic
+            if let acknowledged = message[WatchMessageKeys.acknowledged] as? Bool,
+               let ackMessage = message[WatchMessageKeys.message] as? String
+            {
+                DispatchQueue.main.async {
+                    self.showSyncingAnimation = false
+                }
+
+                print("⌚️ Received acknowledgment: \(ackMessage), success: \(acknowledged)")
+
+                switch ackMessage {
+                case "Saving carbs...":
+                    self.isMealBolusCombo = true
+                    self.mealBolusStep = .savingCarbs
+                    self.showCommsAnimation = true
+                    self.handleAcknowledgment(success: acknowledged, message: ackMessage, isFinal: false)
+                case "Enacting bolus...":
+                    self.isMealBolusCombo = true
+                    self.mealBolusStep = .enactingBolus
+                    self.showCommsAnimation = true
+                    self.handleAcknowledgment(success: acknowledged, message: ackMessage, isFinal: false)
+                case "Carbs and bolus logged successfully":
+                    self.isMealBolusCombo = false
+                    self.handleAcknowledgment(success: acknowledged, message: ackMessage, isFinal: true)
+                default:
+                    self.isMealBolusCombo = false
+                    self.handleAcknowledgment(success: acknowledged, message: ackMessage, isFinal: true)
+                }
+            }
+
+            // 2) Raw watchState data
+            if let watchStateData = message[WatchMessageKeys.watchState] as? [String: Any] {
+                self.scheduleUIUpdate(with: watchStateData)
+            }
+        }
+    }
+
     /// Accumulate new data, set isSyncing, and debounce final update
     private func scheduleUIUpdate(with newData: [String: Any]) {
         // 1) Mark as syncing
@@ -579,88 +398,122 @@ import WatchConnectivity
         }
     }
 
-    private func processWatchMessage(_ message: [String: Any]) {
-        DispatchQueue.main.async {
-            // 1) Acknowledgment logic
-            if let acknowledged = message[WatchMessageKeys.acknowledged] as? Bool,
-               let ackMessage = message[WatchMessageKeys.message] as? String
-            {
-                DispatchQueue.main.async {
-                    self.showSyncingAnimation = false
-                }
+    /// Updates the UI properties
+    private func processRawDataForWatchState(_ message: [String: Any]) {
+        if let currentGlucose = message[WatchMessageKeys.currentGlucose] as? String {
+            self.currentGlucose = currentGlucose
+        }
 
-                print("⌚️ Received acknowledgment: \(ackMessage), success: \(acknowledged)")
+        if let currentGlucoseColorString = message[WatchMessageKeys.currentGlucoseColorString] as? String {
+            self.currentGlucoseColorString = currentGlucoseColorString
+        }
 
-                switch ackMessage {
-                case "Saving carbs...":
-                    self.isMealBolusCombo = true
-                    self.mealBolusStep = .savingCarbs
-                    self.showCommsAnimation = true
-                    self.handleAcknowledgment(success: acknowledged, message: ackMessage, isFinal: false)
-                case "Enacting bolus...":
-                    self.isMealBolusCombo = true
-                    self.mealBolusStep = .enactingBolus
-                    self.showCommsAnimation = true
-                    self.handleAcknowledgment(success: acknowledged, message: ackMessage, isFinal: false)
-                case "Carbs and bolus logged successfully":
-                    self.isMealBolusCombo = false
-                    self.handleAcknowledgment(success: acknowledged, message: ackMessage, isFinal: true)
-                default:
-                    self.isMealBolusCombo = false
-                    self.handleAcknowledgment(success: acknowledged, message: ackMessage, isFinal: true)
-                }
+        if let trend = message[WatchMessageKeys.trend] as? String {
+            self.trend = trend
+        }
+
+        if let delta = message[WatchMessageKeys.delta] as? String {
+            self.delta = delta
+        }
+
+        if let iob = message[WatchMessageKeys.iob] as? String {
+            self.iob = iob
+        }
+
+        if let cob = message[WatchMessageKeys.cob] as? String {
+            self.cob = cob
+        }
+
+        if let lastLoopTime = message[WatchMessageKeys.lastLoopTime] as? String {
+            self.lastLoopTime = lastLoopTime
+        }
+
+        if let glucoseData = message[WatchMessageKeys.glucoseValues] as? [[String: Any]] {
+            glucoseValues = glucoseData.compactMap { data in
+                guard let glucose = data["glucose"] as? Double,
+                      let timestamp = data["date"] as? TimeInterval,
+                      let colorString = data["color"] as? String
+                else { return nil }
+
+                return (
+                    Date(timeIntervalSince1970: timestamp),
+                    glucose,
+                    colorString.toColor() // Convert colorString to Color
+                )
             }
+            .sorted { $0.date < $1.date }
+        }
 
-            // 2) Bolus progress
-            if let progress = message[WatchMessageKeys.bolusProgress] as? Double {
-                if !self.isBolusCanceled {
-                    self.bolusProgress = progress
-                }
-                DispatchQueue.main.async {
-                    self.showSyncingAnimation = false
-                }
+        if let overrideData = message[WatchMessageKeys.overridePresets] as? [[String: Any]] {
+            overridePresets = overrideData.compactMap { data in
+                guard let name = data["name"] as? String,
+                      let isEnabled = data["isEnabled"] as? Bool
+                else { return nil }
+                return OverridePresetWatch(name: name, isEnabled: isEnabled)
             }
+        }
 
-            // 3) Bolus cancellation
-            if message[WatchMessageKeys.bolusCanceled] as? Bool == true {
-                self.bolusProgress = 0
-                self.activeBolusAmount = 0
-                DispatchQueue.main.async {
-                    self.showSyncingAnimation = false
-                }
+        if let tempTargetData = message[WatchMessageKeys.tempTargetPresets] as? [[String: Any]] {
+            tempTargetPresets = tempTargetData.compactMap { data in
+                guard let name = data["name"] as? String,
+                      let isEnabled = data["isEnabled"] as? Bool
+                else { return nil }
+                return TempTargetPresetWatch(name: name, isEnabled: isEnabled)
             }
+        }
 
-            // 4) Recommended bolus
-            if let recommendedBolus = message[WatchMessageKeys.recommendedBolus] as? NSNumber {
-                self.recommendedBolus = recommendedBolus.decimalValue
-                self.showBolusCalculationProgress = false
-                DispatchQueue.main.async {
-                    self.showSyncingAnimation = false
-                }
+        if let bolusProgress = message[WatchMessageKeys.bolusProgress] as? Double {
+            if !isBolusCanceled {
+                self.bolusProgress = bolusProgress
             }
+        }
 
-            // 5) Raw watchState data
-            if let watchStateData = message[WatchMessageKeys.watchState] as? [String: Any] {
-                self.scheduleUIUpdate(with: watchStateData)
+        if let bolusWasCanceled = message[WatchMessageKeys.bolusCanceled] as? Bool, bolusWasCanceled {
+            bolusProgress = 0
+            activeBolusAmount = 0
+        }
+
+        if let maxBolusValue = message[WatchMessageKeys.maxBolus] {
+            print("⌚️ Received maxBolus: \(maxBolusValue) of type \(type(of: maxBolusValue))")
+            if let decimalValue = (maxBolusValue as? NSNumber)?.decimalValue {
+                maxBolus = decimalValue
+                print("⌚️ Converted maxBolus to: \(decimalValue)")
             }
         }
-    }
 
-    /// Called when the reachability status of the paired iPhone changes
-    /// Updates the local reachability status
-    func sessionReachabilityDidChange(_ session: WCSession) {
-        DispatchQueue.main.async {
-            print("⌚️ Watch reachability changed: \(session.isReachable)")
+        if let maxCarbsValue = message[WatchMessageKeys.maxCarbs] {
+            if let decimalValue = (maxCarbsValue as? NSNumber)?.decimalValue {
+                maxCarbs = decimalValue
+            }
+        }
 
-            if session.isReachable {
-                // request fresh data from watch
-                self.requestWatchStateUpdate()
+        if let maxFatValue = message[WatchMessageKeys.maxFat] {
+            if let decimalValue = (maxFatValue as? NSNumber)?.decimalValue {
+                maxFat = decimalValue
+            }
+        }
 
-                // reset input amounts
-                self.bolusAmount = 0
-                self.carbsAmount = 0
-                // reset auth progress
-                self.confirmationProgress = 0
+        if let maxProteinValue = message[WatchMessageKeys.maxProtein] {
+            if let decimalValue = (maxProteinValue as? NSNumber)?.decimalValue {
+                maxProtein = decimalValue
+            }
+        }
+
+        if let maxIOBValue = message[WatchMessageKeys.maxIOB] {
+            if let decimalValue = (maxIOBValue as? NSNumber)?.decimalValue {
+                maxIOB = decimalValue
+            }
+        }
+
+        if let maxCOBValue = message[WatchMessageKeys.maxCOB] {
+            if let decimalValue = (maxCOBValue as? NSNumber)?.decimalValue {
+                maxCOB = decimalValue
+            }
+        }
+
+        if let bolusIncrement = message[WatchMessageKeys.bolusIncrement] {
+            if let decimalValue = (bolusIncrement as? NSNumber)?.decimalValue {
+                self.bolusIncrement = decimalValue
             }
         }
     }

+ 5 - 1
Trio.xcodeproj/project.pbxproj

@@ -321,6 +321,7 @@
 		BDA25F202D26D5FE00035F34 /* CarbsInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA25F1F2D26D5FB00035F34 /* CarbsInputView.swift */; };
 		BDA25F222D26D62800035F34 /* BolusInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA25F212D26D62200035F34 /* BolusInputView.swift */; };
 		BDA6CC882CAF219B00F942F9 /* TempTargetSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA6CC872CAF219800F942F9 /* TempTargetSetup.swift */; };
+		BDAE40002D372BAD009C12B1 /* WatchState+Requests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDAE3FFF2D372BA8009C12B1 /* WatchState+Requests.swift */; };
 		BDB3C1192C03DD1000CEEAA1 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */; };
 		BDB899882C564509006F3298 /* ForecastChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB899872C564509006F3298 /* ForecastChart.swift */; };
 		BDB8998A2C565D0C006F3298 /* CarbsGlucose+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */; };
@@ -1036,6 +1037,7 @@
 		BDA25F1F2D26D5FB00035F34 /* CarbsInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbsInputView.swift; sourceTree = "<group>"; };
 		BDA25F212D26D62200035F34 /* BolusInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusInputView.swift; sourceTree = "<group>"; };
 		BDA6CC872CAF219800F942F9 /* TempTargetSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetSetup.swift; sourceTree = "<group>"; };
+		BDAE3FFF2D372BA8009C12B1 /* WatchState+Requests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WatchState+Requests.swift"; sourceTree = "<group>"; };
 		BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtension.swift; sourceTree = "<group>"; };
 		BDB899872C564509006F3298 /* ForecastChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastChart.swift; sourceTree = "<group>"; };
 		BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CarbsGlucose+helper.swift"; sourceTree = "<group>"; };
@@ -2562,9 +2564,10 @@
 		BDFF7A9C2D25FA730016C40C /* Trio Watch App Extension */ = {
 			isa = PBXGroup;
 			children = (
+				BDA25EE52D260D5800035F34 /* WatchState.swift */,
+				BDAE3FFF2D372BA8009C12B1 /* WatchState+Requests.swift */,
 				DD3A3CEC2D29CFBA00AE478E /* Helper */,
 				BDA25F1A2D26BCE800035F34 /* Views */,
-				BDA25EE52D260D5800035F34 /* WatchState.swift */,
 				BDFF7A9E2D25FA970016C40C /* Preview Content */,
 				BDFF7A832D25F97D0016C40C /* Assets.xcassets */,
 				BDFF7A852D25F97D0016C40C /* TrioWatchApp.swift */,
@@ -4062,6 +4065,7 @@
 				DD8262CB2D289297009F6F62 /* BolusConfirmationView.swift in Sources */,
 				BD54A9712D281A8100F9C1EE /* TempTargetPresetsView.swift in Sources */,
 				DD3A3CE92D29C97800AE478E /* Helper+ButtonStyles.swift in Sources */,
+				BDAE40002D372BAD009C12B1 /* WatchState+Requests.swift in Sources */,
 				BDA25EE62D260D5E00035F34 /* WatchState.swift in Sources */,
 				BD04ECCE2D29952A008C5FEB /* BolusProgressOverlay.swift in Sources */,
 				DD09D5C92D29F3D0000D82C9 /* AcknowledgementPendingView.swift in Sources */,