Преглед на файлове

Extend acknowledgements
* Add acknowledgement handling for Override and Temp Target preset start and stop
* Add acknowledgement for bolus cancellation
* Extend APSManager enactBolus() and cancelBolus() with callback function parameter to handle watch message acknowledgements

Deniz Cengiz преди 1 година
родител
ревизия
cb63c8ac5b

+ 4 - 0
Trio Watch App Extension/Views/AcknowledgementPendingView.swift

@@ -3,6 +3,7 @@ import SwiftUI
 struct AcknowledgementPendingView: View {
     @Binding var navigationPath: NavigationPath
     let state: WatchState
+    @Binding var shouldNavigateToRoot: Bool
 
     var trioBackgroundColor = LinearGradient(
         gradient: Gradient(colors: [Color.bgDarkBlue, Color.bgDarkerDarkBlue]),
@@ -47,5 +48,8 @@ struct AcknowledgementPendingView: View {
                 navigationPath.removeLast(navigationPath.count)
             }
         }
+        .onDisappear {
+            state.shouldNavigateToRoot = true
+        }
     }
 }

+ 4 - 2
Trio Watch App Extension/Views/BolusConfirmationView.swift

@@ -101,8 +101,10 @@ struct BolusConfirmationView: View {
         .blur(radius: state.showBolusProgressOverlay ? 3 : 0)
         .overlay {
             if state.showBolusProgressOverlay {
-                BolusProgressOverlay(state: state)
-                    .transition(.opacity)
+                BolusProgressOverlay(state: state) {
+                    state.shouldNavigateToRoot = false
+                    navigationPath.append(NavigationDestinations.acknowledgmentPending)
+                }.transition(.opacity)
             }
         }
     }

+ 4 - 2
Trio Watch App Extension/Views/BolusInputView.swift

@@ -139,8 +139,10 @@ struct BolusInputView: View {
         .blur(radius: state.showBolusProgressOverlay ? 3 : 0)
         .overlay {
             if state.showBolusProgressOverlay {
-                BolusProgressOverlay(state: state)
-                    .transition(.opacity)
+                BolusProgressOverlay(state: state) {
+                    state.shouldNavigateToRoot = false
+                    navigationPath.append(NavigationDestinations.acknowledgmentPending)
+                }.transition(.opacity)
             }
         }
     }

+ 2 - 0
Trio Watch App Extension/Views/BolusProgressOverlay.swift

@@ -2,6 +2,7 @@ import SwiftUI
 
 struct BolusProgressOverlay: View {
     let state: WatchState
+    let onCancelBolus: () -> Void
 
     private let progressGradient = LinearGradient(
         colors: [
@@ -38,6 +39,7 @@ struct BolusProgressOverlay: View {
 
                 Button(action: {
                     state.sendCancelBolusRequest()
+                    onCancelBolus()
                 }) {
                     Text("Cancel Bolus")
                 }

+ 4 - 4
Trio Watch App Extension/Views/OverridePresetsView.swift

@@ -1,9 +1,9 @@
 import SwiftUI
 
 struct OverridePresetsView: View {
-    @Environment(\.dismiss) var dismiss
-    let overridePresets: [OverridePresetWatch]
     let state: WatchState
+    let overridePresets: [OverridePresetWatch]
+    var onPresetAction: () -> Void // Callback to handle selection of preset, or cancellation, and dismiss the sheet
 
     private let activePresetGradient = LinearGradient(
         colors: [
@@ -28,7 +28,7 @@ struct OverridePresetsView: View {
                 if let active = activeOverride {
                     Button("Stop \(active.name)") {
                         state.sendCancelOverrideRequest()
-                        dismiss()
+                        onPresetAction()
                     }
                     .foregroundColor(.white)
                     .listRowBackground(
@@ -47,7 +47,7 @@ struct OverridePresetsView: View {
                             if !preset.isEnabled {
                                 state.sendActivateOverrideRequest(presetName: preset.name)
                             }
-                            dismiss()
+                            onPresetAction()
                         }) {
                             HStack {
                                 Text(preset.name)

+ 4 - 4
Trio Watch App Extension/Views/TempTargetPresetsView.swift

@@ -1,9 +1,9 @@
 import SwiftUI
 
 struct TempTargetPresetsView: View {
-    @Environment(\.dismiss) var dismiss
-    let tempTargetPresets: [TempTargetPresetWatch]
     let state: WatchState
+    let tempTargetPresets: [TempTargetPresetWatch]
+    var onPresetAction: () -> Void // Callback to handle selection of preset, or cancellation, and dismiss the sheet
 
     private let activePresetGradient = LinearGradient(
         colors: [
@@ -28,7 +28,7 @@ struct TempTargetPresetsView: View {
                 if let active = activePreset {
                     Button("Stop \(active.name)") {
                         state.sendCancelTempTargetRequest()
-                        dismiss()
+                        onPresetAction()
                     }
                     .foregroundColor(.white)
                     .listRowBackground(
@@ -47,7 +47,7 @@ struct TempTargetPresetsView: View {
                             if !preset.isEnabled {
                                 state.sendActivateTempTargetRequest(presetName: preset.name)
                             }
-                            dismiss()
+                            onPresetAction()
                         }) {
                             HStack {
                                 Text(preset.name)

+ 18 - 9
Trio Watch App Extension/Views/TrioMainWatchView.swift

@@ -119,22 +119,29 @@ struct TrioMainWatchView: View {
             }
             .sheet(isPresented: $showingOverrideSheet) {
                 OverridePresetsView(
-                    overridePresets: state.overridePresets,
-                    state: state
-                )
+                    state: state,
+                    overridePresets: state.overridePresets
+                ) {
+                    showingOverrideSheet = false
+                    navigationPath.append(NavigationDestinations.acknowledgmentPending)
+                }
             }
             .sheet(isPresented: $showingTempTargetSheet) {
                 TempTargetPresetsView(
-                    tempTargetPresets: state.tempTargetPresets,
-                    state: state
-                )
+                    state: state,
+                    tempTargetPresets: state.tempTargetPresets
+                ) {
+                    showingTempTargetSheet = false
+                    navigationPath.append(NavigationDestinations.acknowledgmentPending)
+                }
             }
             .navigationDestination(for: NavigationDestinations.self) { destination in
                 switch destination {
                 case .acknowledgmentPending:
                     AcknowledgementPendingView(
                         navigationPath: $navigationPath,
-                        state: state
+                        state: state,
+                        shouldNavigateToRoot: $state.shouldNavigateToRoot
                     )
                 case .carbsInput:
                     CarbsInputView(
@@ -166,8 +173,10 @@ struct TrioMainWatchView: View {
         .blur(radius: state.showBolusProgressOverlay ? 3 : 0)
         .overlay {
             if state.showBolusProgressOverlay {
-                BolusProgressOverlay(state: state)
-                    .transition(.opacity)
+                BolusProgressOverlay(state: state) {
+                    state.shouldNavigateToRoot = false
+                    navigationPath.append(NavigationDestinations.acknowledgmentPending)
+                }.transition(.opacity)
             }
         }
     }

+ 17 - 0
Trio Watch App Extension/WatchState.swift

@@ -46,6 +46,8 @@ import WatchConnectivity
     var showAcknowledgmentBanner: Bool = false
     var acknowledgementStatus: AcknowledgementStatus = .pending
     var acknowledgmentMessage: String = ""
+    var shouldNavigateToRoot: Bool = true
+
     // Meal bolus-specific properties
     var mealBolusStep: MealBolusStep = .savingCarbs
     var isMealBolusCombo: Bool = false
@@ -146,6 +148,9 @@ import WatchConnectivity
         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) {
@@ -158,6 +163,9 @@ import WatchConnectivity
         session.sendMessage(message, replyHandler: nil) { error in
             print("⌚️ Error sending activate override request: \(error.localizedDescription)")
         }
+
+        // Display pending communication animation
+        showCommsAnimation = true
     }
 
     func sendCancelTempTargetRequest() {
@@ -170,6 +178,9 @@ import WatchConnectivity
         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) {
@@ -182,6 +193,9 @@ import WatchConnectivity
         session.sendMessage(message, replyHandler: nil) { error in
             print("⌚️ Error sending activate temp target request: \(error.localizedDescription)")
         }
+
+        // Display pending communication animation
+        showCommsAnimation = true
     }
 
     func sendCancelBolusRequest() {
@@ -200,6 +214,9 @@ import WatchConnectivity
         // Reset when cancelled
         bolusProgress = 0
         activeBolusAmount = 0
+
+        // Display pending communication animation
+        showCommsAnimation = true
     }
 
     // MARK: – Handle Acknowledgement Messages FROM Phone

+ 9 - 4
Trio/Sources/APS/APSManager.swift

@@ -8,7 +8,7 @@ import Swinject
 
 protocol APSManager {
     func heartbeat(date: Date)
-    func enactBolus(amount: Double, isSMB: Bool) async
+    func enactBolus(amount: Double, isSMB: Bool, callback: ((Bool, String) -> Void)?) async
     var pumpManager: PumpManagerUI? { get set }
     var bluetoothManager: BluetoothStateManager? { get }
     var pumpDisplayState: CurrentValueSubject<PumpDisplayState?, Never> { get }
@@ -25,7 +25,7 @@ protocol APSManager {
     func simulateDetermineBasal(carbs: Decimal, iob: Decimal) async -> Determination?
     func roundBolus(amount: Decimal) -> Decimal
     var lastError: CurrentValueSubject<Error?, Never> { get }
-    func cancelBolus() async
+    func cancelBolus(_ callback: ((Bool, String) -> Void)?) async
 }
 
 enum APSError: LocalizedError {
@@ -434,7 +434,7 @@ final class BaseAPSManager: APSManager, Injectable {
 
     private var bolusReporter: DoseProgressReporter?
 
-    func enactBolus(amount: Double, isSMB: Bool) async {
+    func enactBolus(amount: Double, isSMB: Bool, callback: ((Bool, String) -> Void)?) async {
         if amount <= 0 {
             return
         }
@@ -446,6 +446,7 @@ final class BaseAPSManager: APSManager, Injectable {
                     $0.bolusDidFail()
                 }
             }
+            callback?(false, "Error! Failed to enact bolus.")
             return
         }
 
@@ -462,6 +463,7 @@ final class BaseAPSManager: APSManager, Injectable {
                 await determineBasalSync()
             }
             bolusProgress.send(0)
+            callback?(true, "Bolus enacted successfully.")
         } catch {
             warning(.apsManager, "Bolus failed with error: \(error.localizedDescription)")
             processError(APSError.pumpError(error))
@@ -472,18 +474,21 @@ final class BaseAPSManager: APSManager, Injectable {
                     }
                 }
             }
+            callback?(false, "Error! Failed to enact bolus.")
         }
     }
 
-    func cancelBolus() async {
+    func cancelBolus(_ callback: ((Bool, String) -> Void)?) async {
         guard let pump = pumpManager, pump.status.pumpStatus.bolusing else { return }
         debug(.apsManager, "Cancel bolus")
         do {
             _ = try await pump.cancelBolus()
             debug(.apsManager, "Bolus cancelled")
+            callback?(true, "Bolus cancelled successfully.")
         } catch {
             debug(.apsManager, "Bolus cancellation failed with error: \(error.localizedDescription)")
             processError(APSError.pumpError(error))
+            callback?(false, "Error! Bolus cancellation failed.")
         }
         bolusReporter?.removeObserver(self)
         bolusReporter = nil

+ 1 - 1
Trio/Sources/Modules/Home/HomeStateModel.swift

@@ -407,7 +407,7 @@ extension Home {
 
         func cancelBolus() {
             Task {
-                await apsManager.cancelBolus()
+                await apsManager.cancelBolus(nil)
 
                 // perform determine basal sync, otherwise you have could end up with too much iob when opening the calculator again
                 await apsManager.determineBasalSync()

+ 1 - 1
Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift

@@ -498,7 +498,7 @@ extension Treatments {
             do {
                 let authenticated = try await unlockmanager.unlock()
                 if authenticated {
-                    await apsManager.enactBolus(amount: maxAmount, isSMB: false)
+                    await apsManager.enactBolus(amount: maxAmount, isSMB: false, callback: nil)
                 } else {
                     print("authentication failed")
                 }

+ 1 - 1
Trio/Sources/Services/RemoteControl/TrioRemoteControl+Bolus.swift

@@ -47,7 +47,7 @@ extension TrioRemoteControl {
             return
         }
 
-        await apsManager.enactBolus(amount: Double(truncating: bolusAmount as NSNumber), isSMB: false)
+        await apsManager.enactBolus(amount: Double(truncating: bolusAmount as NSNumber), isSMB: false, callback: nil)
 
         debug(
             .remoteControl,

+ 70 - 43
Trio/Sources/Services/WatchManager/AppleWatchManager.swift

@@ -384,7 +384,10 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
             // Handle bolus cancellation
             if message["cancelBolus"] as? Bool == true {
                 Task {
-                    await self?.apsManager.cancelBolus()
+                    await self?.apsManager.cancelBolus { [self] success, message in
+                        // Acknowledge success or error of bolus
+                        self?.sendAcknowledgment(toWatch: success, message: message)
+                    }
                     debug(.watchManager, "📱 Bolus cancelled from watch")
 
                     // perform determine basal sync, otherwise you have could end up with too much iob when opening the calculator again
@@ -422,10 +425,11 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
     /// - Parameter amount: The requested bolus amount in units
     private func handleBolusRequest(_ amount: Decimal) {
         Task {
-            await apsManager.enactBolus(amount: Double(amount), isSMB: false)
+            await apsManager.enactBolus(amount: Double(amount), isSMB: false) { success, message in
+                // Acknowledge success or error of bolus
+                self.sendAcknowledgment(toWatch: success, message: message)
+            }
             debug(.watchManager, "📱 Enacted bolus via APS Manager: \(amount)U")
-            // Acknowledge success
-            self.sendAcknowledgment(toWatch: true, message: "Enacted bolus successfully")
         }
     }
 
@@ -452,7 +456,7 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                     debug(.watchManager, "📱 Saved carbs from watch: \(amount)g at \(date)")
 
                     // Acknowledge success
-                    self.sendAcknowledgment(toWatch: true, message: "Carbs logged successfully")
+                    self.sendAcknowledgment(toWatch: true, message: "Carbs logged successfully.")
                 } catch {
                     debug(.watchManager, "❌ Error saving carbs: \(error.localizedDescription)")
 
@@ -494,10 +498,13 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
 
                 // Enact bolus via APS Manager
                 let bolusDouble = NSDecimalNumber(decimal: bolusAmount).doubleValue
-                await apsManager.enactBolus(amount: bolusDouble, isSMB: false)
+                await apsManager.enactBolus(amount: bolusDouble, isSMB: false) { success, message in
+                    // Acknowledge success or error of bolus
+                    self.sendAcknowledgment(toWatch: success, message: message)
+                }
                 debug(.watchManager, "📱 Enacted bolus from watch via APS Manager: \(bolusDouble) U")
                 // Notify Watch: "Carbs and bolus logged successfully"
-                sendAcknowledgment(toWatch: true, message: "Carbs and Bolus logged successfully")
+                sendAcknowledgment(toWatch: true, message: "Carbs and Bolus logged successfully.")
 
             } catch {
                 debug(.watchManager, "❌ Error processing combined request: \(error.localizedDescription)")
@@ -522,15 +529,20 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                         do {
                             guard context.hasChanges else { return }
                             try context.save()
-                            debug(.watchManager, "📱 Successfully cancelled override")
+                            debug(.watchManager, "📱 Successfully stopped override")
 
                             // Send notification to update Adjustments UI
                             Foundation.NotificationCenter.default.post(
                                 name: .didUpdateOverrideConfiguration,
                                 object: nil
                             )
+
+                            // Acknowledge cancellation success
+                            self.sendAcknowledgment(toWatch: true, message: "Stopped Override successfully.")
                         } catch {
                             debug(.watchManager, "❌ Error cancelling override: \(error.localizedDescription)")
+                            // Acknowledge cancellation error
+                            self.sendAcknowledgment(toWatch: false, message: "Error stopping Override.")
                         }
                     }
                 }
@@ -577,43 +589,13 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                             name: .didUpdateOverrideConfiguration,
                             object: nil
                         )
+
+                        // Acknowledge activation success
+                        self.sendAcknowledgment(toWatch: true, message: "Started Override \"\(presetName)\" successfully.")
                     } catch {
                         debug(.watchManager, "❌ Error activating override: \(error.localizedDescription)")
-                    }
-                }
-            }
-        }
-    }
-
-    private func handleCancelTempTarget() {
-        Task {
-            let context = CoreDataStack.shared.newTaskContext()
-
-            if let tempTargetId = await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 1).first {
-                let tempTarget = await context.perform {
-                    context.object(with: tempTargetId) as? TempTargetStored
-                }
-
-                await context.perform {
-                    if let activeTempTarget = tempTarget {
-                        activeTempTarget.enabled = false
-
-                        do {
-                            guard context.hasChanges else { return }
-                            try context.save()
-                            debug(.watchManager, "📱 Successfully cancelled temp target")
-
-                            // To cancel the temp target also for oref
-                            self.tempTargetStorage.saveTempTargetsToStorage([TempTarget.cancel(at: Date())])
-
-                            // Send notification to update Adjustments UI
-                            Foundation.NotificationCenter.default.post(
-                                name: .didUpdateTempTargetConfiguration,
-                                object: nil
-                            )
-                        } catch {
-                            debug(.watchManager, "❌ Error cancelling temp target: \(error.localizedDescription)")
-                        }
+                        // Acknowledge activation error
+                        self.sendAcknowledgment(toWatch: false, message: "Error activating Override \"\(presetName)\".")
                     }
                 }
             }
@@ -680,8 +662,53 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                             name: .didUpdateTempTargetConfiguration,
                             object: nil
                         )
+
+                        // Acknowledge activation success
+                        self.sendAcknowledgment(toWatch: true, message: "Started Temp Target \"\(presetName)\" successfully.")
                     } catch {
                         debug(.watchManager, "❌ Error activating temp target: \(error.localizedDescription)")
+                        // Acknowledge activation error
+                        self.sendAcknowledgment(toWatch: false, message: "Error activating Temp Target \"\(presetName)\".")
+                    }
+                }
+            }
+        }
+    }
+
+    private func handleCancelTempTarget() {
+        Task {
+            let context = CoreDataStack.shared.newTaskContext()
+
+            if let tempTargetId = await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 1).first {
+                let tempTarget = await context.perform {
+                    context.object(with: tempTargetId) as? TempTargetStored
+                }
+
+                await context.perform {
+                    if let activeTempTarget = tempTarget {
+                        activeTempTarget.enabled = false
+
+                        do {
+                            guard context.hasChanges else { return }
+                            try context.save()
+                            debug(.watchManager, "📱 Successfully cancelled temp target")
+
+                            // To cancel the temp target also for oref
+                            self.tempTargetStorage.saveTempTargetsToStorage([TempTarget.cancel(at: Date())])
+
+                            // Send notification to update Adjustments UI
+                            Foundation.NotificationCenter.default.post(
+                                name: .didUpdateTempTargetConfiguration,
+                                object: nil
+                            )
+
+                            // Acknowledge cancellation success
+                            self.sendAcknowledgment(toWatch: true, message: "Stopped Temp Target successfully.")
+                        } catch {
+                            debug(.watchManager, "❌ Error stopping temp target: \(error.localizedDescription)")
+                            // Acknowledge cancellation error
+                            self.sendAcknowledgment(toWatch: false, message: "Error stopping Temp Target.")
+                        }
                     }
                 }
             }

+ 1 - 1
Trio/Sources/Shortcuts/Bolus/BolusIntentRequest.swift

@@ -23,7 +23,7 @@ import Foundation
             } else {
                 bolusQuantity = apsManager.roundBolus(amount: Decimal(bolusAmount))
             }
-            await apsManager.enactBolus(amount: Double(bolusQuantity), isSMB: false)
+            await apsManager.enactBolus(amount: Double(bolusQuantity), isSMB: false, callback: nil)
             return LocalizedStringResource(
                 "A bolus command of \(bolusQuantity.formatted()) U of insulin was sent.",
                 table: "ShortcutsDetail"