Просмотр исходного кода

Merge branch 'watch' of github.com:polscm32/Trio-dev into watch

Deniz Cengiz 1 год назад
Родитель
Сommit
fa590b7a38

+ 0 - 165
Trio Watch App Extension/ContentView.swift

@@ -1,165 +0,0 @@
-import Charts
-import SwiftUI
-
-struct ContentView: View {
-    @State private var state = WatchState()
-    @State private var showingCarbsSheet = false
-    @State private var showingBolusSheet = false
-    @State private var currentPage: Double = 0
-    @State private var rotationDegrees: Double = 0.0
-
-    var body: some View {
-        TabView(selection: $currentPage) {
-            // Page 1: Current glucose and trend
-            ZStack {
-                TrendShape(rotationDegrees: rotationDegrees)
-                    .animation(.spring(response: 0.5, dampingFraction: 0.6), value: rotationDegrees)
-
-                VStack(alignment: .center) {
-                    Text(state.currentGlucose)
-                        .fontWeight(.bold)
-                        .font(.system(size: 40))
-
-                    if let delta = state.delta {
-                        Text(delta)
-                            .font(.caption2)
-                            .foregroundStyle(.secondary)
-                    }
-                }
-            }
-            .tag(0.0)
-            .onChange(of: state.trend) { newTrend in
-                withAnimation {
-                    updateRotation(for: newTrend)
-                }
-            }
-            .toolbar {
-                ToolbarItem(placement: .bottomBar) {
-                    Button {
-                        showingCarbsSheet = true
-                    } label: {
-                        Image(systemName: "fork.knife")
-                    }
-                }
-
-                ToolbarItem(placement: .bottomBar) {
-                    Button {
-                        showingBolusSheet = true
-                    } label: {
-                        Image(systemName: "drop.fill")
-                    }
-                }
-            }
-
-            // Page 2: Glucose chart
-            GlucoseChartView(glucoseValues: state.glucoseValues)
-                .tag(1.0)
-        }
-        .tabViewStyle(.verticalPage)
-        .navigationBarHidden(true)
-        .digitalCrownRotation($currentPage, from: 0, through: 1, by: 1)
-        .sheet(isPresented: $showingCarbsSheet) {
-            CarbsInputView(state: state)
-        }
-        .sheet(isPresented: $showingBolusSheet) {
-            BolusInputView(state: state)
-        }
-    }
-
-    private func updateRotation(for trend: String?) {
-        switch trend {
-        case "↑",
-             "↑↑": // DoubleUp, SingleUp
-            rotationDegrees = -90
-        case "↗": // FortyFiveUp
-            rotationDegrees = -45
-        case "→": // Flat
-            rotationDegrees = 0
-        case "↘": // FortyFiveDown
-            rotationDegrees = 45
-        case "↓",
-             "↓↓": // SingleDown, DoubleDown
-            rotationDegrees = 90
-        default:
-            rotationDegrees = 0
-        }
-    }
-}
-
-struct GlucoseChartView: View {
-    let glucoseValues: [(date: Date, glucose: Double)]
-    @State private var timeWindow: TimeWindow = .threeHours
-
-    enum TimeWindow: Int {
-        case threeHours = 3
-        case sixHours = 6
-        case twelveHours = 12
-        case twentyFourHours = 24
-
-        var next: TimeWindow {
-            switch self {
-            case .threeHours: return .sixHours
-            case .sixHours: return .twelveHours
-            case .twelveHours: return .twentyFourHours
-            case .twentyFourHours: return .threeHours
-            }
-        }
-    }
-
-    private var filteredValues: [(date: Date, glucose: Double)] {
-        let cutoffDate = Date().addingTimeInterval(-Double(timeWindow.rawValue) * 3600)
-        return glucoseValues.filter { $0.date > cutoffDate }
-    }
-
-    private func glucoseColor(_ value: Double) -> Color {
-        if value > 180 {
-            return .orange
-        } else if value < 70 {
-            return .red
-        } else {
-            return .green
-        }
-    }
-
-    var body: some View {
-        Chart {
-            ForEach(filteredValues, id: \.date) { reading in
-                LineMark(
-                    x: .value("Time", reading.date),
-                    y: .value("Glucose", reading.glucose)
-                )
-                .foregroundStyle(Color.accentColor.gradient)
-                .lineStyle(StrokeStyle(lineWidth: 2))
-
-                PointMark(
-                    x: .value("Time", reading.date),
-                    y: .value("Glucose", reading.glucose)
-                )
-                .foregroundStyle(glucoseColor(reading.glucose))
-                .symbolSize(40) // Kleinere Punkte
-            }
-        }
-        .chartXAxis {
-            AxisMarks(values: .automatic(desiredCount: 4)) { _ in
-                AxisValueLabel(format: .dateTime.hour())
-            }
-        }
-        .chartYAxis {
-            AxisMarks(position: .leading)
-        }
-        .padding()
-        .onTapGesture {
-            withAnimation {
-                timeWindow = timeWindow.next
-            }
-        }
-        .overlay(alignment: .topLeading) {
-            Text("\(timeWindow.rawValue)h")
-                .font(.caption2)
-                .foregroundStyle(.secondary)
-                .padding(.leading)
-        }
-    }
-}
-
-// Rest der View-Komponenten bleiben unverändert...

+ 77 - 0
Trio Watch App Extension/Views/OverridePresetsView.swift

@@ -0,0 +1,77 @@
+import SwiftUI
+
+struct OverridePresetsView: View {
+    @Environment(\.dismiss) var dismiss
+    let overridePresets: [OverridePresetWatch]
+    let state: WatchState
+
+    private let activePresetGradient = LinearGradient(
+        colors: [
+            Color(red: 0.262745098, green: 0.7333333333, blue: 0.9137254902), // #43BBE9
+            Color(red: 0.3411764706, green: 0.6666666667, blue: 0.9254901961) // #57AAEC
+        ],
+        startPoint: .leading,
+        endPoint: .trailing
+    )
+
+    private var sortedPresets: [OverridePresetWatch] {
+        overridePresets.sorted { $0.isEnabled && !$1.isEnabled }
+    }
+
+    private var activeOverride: OverridePresetWatch? {
+        sortedPresets.first { $0.isEnabled }
+    }
+
+    var body: some View {
+        NavigationView {
+            List {
+                if let active = activeOverride {
+                    Button("Stop \(active.name)") {
+                        state.sendCancelOverrideRequest()
+                        dismiss()
+                    }
+                    .foregroundColor(.white)
+                    .listRowBackground(
+                        Color.red
+                            .clipShape(RoundedRectangle(cornerRadius: 8))
+                    )
+                }
+
+                if sortedPresets.isEmpty {
+                    Text("No Override Presets")
+                        .font(.caption)
+                        .foregroundColor(.secondary)
+                } else {
+                    ForEach(sortedPresets, id: \.name) { preset in
+                        Button(action: {
+                            if !preset.isEnabled {
+                                state.sendActivateOverrideRequest(presetName: preset.name)
+                            }
+                            dismiss()
+                        }) {
+                            HStack {
+                                Text(preset.name)
+                                    .font(.caption)
+
+                                if preset.isEnabled {
+                                    Spacer()
+                                    Text("is running")
+                                        .font(.caption2)
+                                        .foregroundStyle(.white)
+                                }
+                            }
+                        }
+                        .listRowBackground(
+                            preset.isEnabled ?
+                                activePresetGradient
+                                .clipShape(RoundedRectangle(cornerRadius: 8))
+                                : nil
+                        )
+                        .foregroundColor(preset.isEnabled ? .white : .primary)
+                    }
+                }
+            }
+            .navigationTitle("Override Presets")
+        }
+    }
+}

+ 10 - 2
Trio Watch App Extension/Views/TrioMainWatchView.swift

@@ -4,6 +4,7 @@ import SwiftUI
 struct TrioMainWatchView: View {
     @State private var state = WatchState()
     @State private var isTreatmentMenuSheetPresented: Bool = false
+    @State private var showingOverrideSheet = false
     @State private var currentPage: Double = 0
     @State private var rotationDegrees: Double = 0.0
 
@@ -71,9 +72,10 @@ struct TrioMainWatchView: View {
             .toolbar {
                 ToolbarItemGroup(placement: .bottomBar) {
                     Button {
-                        // Perform an action here.
+                        showingOverrideSheet = true
                     } label: {
-                        Image(systemName: "clock.arrow.2.circlepath").foregroundStyle(Color.primary, Color.purple)
+                        Image(systemName: "clock.arrow.2.circlepath")
+                            .foregroundStyle(Color.primary, Color.purple)
                     }
 
                     Button {
@@ -115,6 +117,12 @@ struct TrioMainWatchView: View {
         .sheet(isPresented: $isTreatmentMenuSheetPresented) {
             TreatmentMenuView().presentationBackground(.clear)
         }
+        .sheet(isPresented: $showingOverrideSheet) {
+            OverridePresetsView(
+                overridePresets: state.overridePresets,
+                state: state
+            )
+        }
     }
 
     struct WatchOSButtonStyle: ButtonStyle {

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

@@ -18,6 +18,7 @@ import WatchConnectivity
     var cob: String? = "--"
     var iob: String? = "--"
     var lastLoopTime: String? = "--"
+    var overridePresets: [OverridePresetWatch] = []
 
     override init() {
         super.init()
@@ -72,6 +73,30 @@ import WatchConnectivity
         }
     }
 
+    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)")
+        }
+    }
+
+    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)")
+        }
+    }
+
     // MARK: - WCSessionDelegate
 
     /// Called when the session has completed activation
@@ -131,6 +156,15 @@ import WatchConnectivity
                 }
                 .sorted { $0.date < $1.date }
             }
+
+            if let overrideData = message["overridePresets"] as? [[String: Any]] {
+                self.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)
+                }
+            }
         }
     }
 

+ 10 - 0
Trio.xcodeproj/project.pbxproj

@@ -293,6 +293,9 @@
 		BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3CC0712B0B89D50013189E /* MainChartView.swift */; };
 		BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */; };
 		BD4ED4FD2CF9D5E8000EDC9C /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4ED4FC2CF9D5E8000EDC9C /* AppState.swift */; };
+		BD54A9592D27FB7800F9C1EE /* OverridePresetsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD54A9582D27FB6A00F9C1EE /* OverridePresetsView.swift */; };
+		BD54A95B2D28087C00F9C1EE /* OverridePreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD54A95A2D28087700F9C1EE /* OverridePreset.swift */; };
+		BD54A95C2D2808A300F9C1EE /* OverridePreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD54A95A2D28087700F9C1EE /* OverridePreset.swift */; };
 		BD6EB2D62C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */; };
 		BD793CB02CE7C61500D669AC /* OverrideRunStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */; };
 		BD793CB22CE8033500D669AC /* TempTargetRunStored.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD793CB12CE8032E00D669AC /* TempTargetRunStored.swift */; };
@@ -991,6 +994,8 @@
 		BD3CC0712B0B89D50013189E /* MainChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartView.swift; sourceTree = "<group>"; };
 		BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataObserver.swift; sourceTree = "<group>"; };
 		BD4ED4FC2CF9D5E8000EDC9C /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
+		BD54A9582D27FB6A00F9C1EE /* OverridePresetsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverridePresetsView.swift; sourceTree = "<group>"; };
+		BD54A95A2D28087700F9C1EE /* OverridePreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverridePreset.swift; sourceTree = "<group>"; };
 		BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityWidgetConfiguration.swift; sourceTree = "<group>"; };
 		BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideRunStored+helper.swift"; sourceTree = "<group>"; };
 		BD793CB12CE8032E00D669AC /* TempTargetRunStored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetRunStored.swift; sourceTree = "<group>"; };
@@ -1997,6 +2002,7 @@
 		388E5A5925B6F0250019842D /* Models */ = {
 			isa = PBXGroup;
 			children = (
+				BD54A95A2D28087700F9C1EE /* OverridePreset.swift */,
 				BDA25EFC2D261BF200035F34 /* WatchState.swift */,
 				DD07CA132CE80B73002D45A9 /* TimeInRangeChartStyle.swift */,
 				DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */,
@@ -2459,6 +2465,7 @@
 			isa = PBXGroup;
 			children = (
 				DD6F63CB2D27F606007D94CF /* TreatmentMenuView.swift */,
+				BD54A9582D27FB6A00F9C1EE /* OverridePresetsView.swift */,
 				BDA25F212D26D62200035F34 /* BolusInputView.swift */,
 				BDA25F1F2D26D5FB00035F34 /* CarbsInputView.swift */,
 				BDA25F1D2D26D5D800035F34 /* GlucoseChartView.swift */,
@@ -3755,6 +3762,7 @@
 				DD17454B2C55C62800211FAC /* AutosensSettingsRootView.swift in Sources */,
 				DDF847DF2C5C28780049BB3B /* LiveActivitySettingsProvider.swift in Sources */,
 				DDB37CC52D05048F00D99BF4 /* ContactImageStorage.swift in Sources */,
+				BD54A95B2D28087C00F9C1EE /* OverridePreset.swift in Sources */,
 				DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */,
 				BDF34EBE2C0A31D100D51995 /* CustomNotification.swift in Sources */,
 				BDC2EA472C3045AD00E5BBD0 /* Override.swift in Sources */,
@@ -3954,6 +3962,8 @@
 				BDA25F202D26D5FE00035F34 /* CarbsInputView.swift in Sources */,
 				BDA25F1C2D26BD0700035F34 /* TrendShape.swift in Sources */,
 				BDFF7A892D25F97D0016C40C /* TrioWatchApp.swift in Sources */,
+				BD54A95C2D2808A300F9C1EE /* OverridePreset.swift in Sources */,
+				BD54A9592D27FB7800F9C1EE /* OverridePresetsView.swift in Sources */,
 				BDA25F1E2D26D5DD00035F34 /* GlucoseChartView.swift in Sources */,
 				DD6F63CC2D27F615007D94CF /* TreatmentMenuView.swift in Sources */,
 				BDA25EE62D260D5E00035F34 /* WatchState.swift in Sources */,

+ 20 - 0
Trio/Sources/APS/Storage/OverrideStorage.swift

@@ -13,6 +13,7 @@ protocol OverrideStorage {
     func getOverridesNotYetUploadedToNightscout() async -> [NightscoutExercise]
     func getOverrideRunsNotYetUploadedToNightscout() async -> [NightscoutExercise]
     func getPresetOverridesForNightscout() async -> [NightscoutPresetOverride]
+    func fetchLatestActiveOverride() async -> NSManagedObjectID?
 }
 
 final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
@@ -288,4 +289,23 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
             }
         }
     }
+
+    func fetchLatestActiveOverride() async -> NSManagedObjectID? {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: OverrideStored.self,
+            onContext: backgroundContext,
+            predicate: NSPredicate.lastActiveOverride,
+            key: "date",
+            ascending: false,
+            fetchLimit: 1
+        )
+
+        return await backgroundContext.perform {
+            guard let fetchedResults = results as? [OverrideStored],
+                  let latestOverride = fetchedResults.first
+            else { return nil }
+
+            return latestOverride.objectID
+        }
+    }
 }

+ 13 - 0
Trio/Sources/Models/OverridePreset.swift

@@ -0,0 +1,13 @@
+//
+//  OverridePreset.swift
+//  Trio
+//
+//  Created by Marvin Polscheit on 03.01.25.
+//
+import Foundation
+import SwiftUI
+
+struct OverridePresetWatch: Hashable, Equatable {
+    let name: String
+    let isEnabled: Bool
+}

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

@@ -1,35 +0,0 @@
-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)
-    }
-}

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

@@ -1,6 +1,5 @@
 import Foundation
 
-// TODO: expand this for cob,iob etc
 struct WatchState: Hashable, Equatable, Sendable {
     var currentGlucose: String?
     var trend: String?
@@ -10,6 +9,7 @@ struct WatchState: Hashable, Equatable, Sendable {
     var iob: String?
     var cob: String?
     var lastLoopTime: String?
+    var overridePresets: [OverridePresetWatch] = []
 
     static func == (lhs: WatchState, rhs: WatchState) -> Bool {
         lhs.currentGlucose == rhs.currentGlucose &&
@@ -22,7 +22,8 @@ struct WatchState: Hashable, Equatable, Sendable {
             lhs.units == rhs.units &&
             lhs.iob == rhs.iob &&
             lhs.cob == rhs.cob &&
-            lhs.lastLoopTime == rhs.lastLoopTime
+            lhs.lastLoopTime == rhs.lastLoopTime &&
+            lhs.overridePresets == rhs.overridePresets
     }
 
     func hash(into hasher: inout Hasher) {
@@ -37,5 +38,6 @@ struct WatchState: Hashable, Equatable, Sendable {
         hasher.combine(iob)
         hasher.combine(cob)
         hasher.combine(lastLoopTime)
+        hasher.combine(overridePresets)
     }
 }

+ 120 - 2
Trio/Sources/Services/WatchManager/AppleWatchManager.swift

@@ -17,6 +17,7 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
     @Injected() private var apsManager: APSManager!
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var determinationStorage: DeterminationStorage!
+    @Injected() private var overrideStorage: OverrideStorage!
 
     private var units: GlucoseUnits = .mgdL
 
@@ -34,7 +35,7 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
         setupWatchSession()
         units = settingsManager.settings.units
 
-        // Observer for OrefDetermination
+        // Observer for OrefDetermination and adjustments
         coreDataPublisher =
             changedObjectsOnManagedObjectContextDidSavePublisher()
                 .receive(on: DispatchQueue.global(qos: .background))
@@ -52,6 +53,8 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                 }
             }
             .store(in: &subscriptions)
+
+        registerHandlers()
     }
 
     private func registerHandlers() {
@@ -71,6 +74,14 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                 self.sendDataToWatch(state)
             }
         }.store(in: &subscriptions)
+
+        coreDataPublisher?.filterByEntityName("OverrideStored").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
@@ -106,12 +117,15 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
         let determinationIds = await determinationStorage.fetchLastDeterminationObjectID(
             predicate: NSPredicate.predicateFor30MinAgoForDetermination
         )
+        let overridePresetIds = await overrideStorage.fetchForOverridePresets()
 
         // Get NSManagedObjects
         let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared
             .getNSManagedObject(with: glucoseIds, context: backgroundContext)
         let determinationObjects: [OrefDetermination] = await CoreDataStack.shared
             .getNSManagedObject(with: determinationIds, context: backgroundContext)
+        let overridePresetObjects: [OverrideStored] = await CoreDataStack.shared
+            .getNSManagedObject(with: overridePresetIds, context: backgroundContext)
 
         return await backgroundContext.perform {
             var watchState = WatchState()
@@ -133,6 +147,14 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                 watchState.cob = Formatter.integerFormatter.string(from: cob)
             }
 
+            // Set override presets with their enabled status
+            watchState.overridePresets = overridePresetObjects.map { override in
+                OverridePresetWatch(
+                    name: override.name ?? "",
+                    isEnabled: override.enabled
+                )
+            }
+
             guard let latestGlucose = glucoseObjects.first else {
                 return watchState
             }
@@ -209,7 +231,14 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                 ]
             },
             "iob": state.iob ?? "0",
-            "cob": state.cob ?? "0"
+            "cob": state.cob ?? "0",
+            "lastLoopTime": state.lastLoopTime ?? "--",
+            "overridePresets": state.overridePresets.map { preset in
+                [
+                    "name": preset.name,
+                    "isEnabled": preset.isEnabled
+                ]
+            }
         ]
 
         print("📱 Sending to watch - Message content:")
@@ -260,6 +289,16 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                 print("📱 Received carbs request from watch: \(carbsAmount)g at \(date)")
                 self?.handleCarbsRequest(carbsAmount, date)
             }
+
+            if message["cancelOverride"] as? Bool == true {
+                print("📱 Received cancel override request from watch")
+                self?.handleCancelOverride()
+            }
+
+            if let presetName = message["activateOverride"] as? String {
+                print("📱 Received activate override request from watch for preset: \(presetName)")
+                self?.handleActivateOverride(presetName)
+            }
         }
     }
 
@@ -355,4 +394,83 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
             }
         }
     }
+
+    private func handleCancelOverride() {
+        Task {
+            let context = CoreDataStack.shared.newTaskContext()
+
+            if let overrideId = await overrideStorage.fetchLatestActiveOverride() {
+                let override = await context.perform {
+                    context.object(with: overrideId) as? OverrideStored
+                }
+
+                await context.perform {
+                    if let activeOverride = override {
+                        activeOverride.enabled = false
+
+                        do {
+                            guard context.hasChanges else { return }
+                            try context.save()
+                            print("📱 Successfully cancelled override")
+
+                            // Send notification to update Adjustments UI
+                            Foundation.NotificationCenter.default.post(
+                                name: .didUpdateOverrideConfiguration,
+                                object: nil
+                            )
+                        } catch {
+                            print("❌ Error cancelling override: \(error.localizedDescription)")
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private func handleActivateOverride(_ presetName: String) {
+        Task {
+            let context = CoreDataStack.shared.newTaskContext()
+
+            // Fetch all presets to find the one to activate
+            let presetIds = await overrideStorage.fetchForOverridePresets()
+            let presets: [OverrideStored] = await CoreDataStack.shared
+                .getNSManagedObject(with: presetIds, context: context)
+
+            // Check for active override
+            if let activeOverrideId = await overrideStorage.fetchLatestActiveOverride() {
+                let activeOverride = await context.perform {
+                    context.object(with: activeOverrideId) as? OverrideStored
+                }
+
+                // Deactivate if exists
+                if let override = activeOverride {
+                    await context.perform {
+                        override.enabled = false
+                    }
+                }
+            }
+
+            // Activate the selected preset
+            await context.perform {
+                if let presetToActivate = presets.first(where: { $0.name == presetName }) {
+                    presetToActivate.enabled = true
+                    presetToActivate.date = Date()
+
+                    do {
+                        guard context.hasChanges else { return }
+                        try context.save()
+                        print("📱 Successfully activated override: \(presetName)")
+
+                        // Send notification to update Adjustments UI
+                        Foundation.NotificationCenter.default.post(
+                            name: .didUpdateOverrideConfiguration,
+                            object: nil
+                        )
+                    } catch {
+                        print("❌ Error activating override: \(error.localizedDescription)")
+                    }
+                }
+            }
+        }
+    }
 }