Sfoglia il codice sorgente

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

Deniz Cengiz 1 anno fa
parent
commit
8468c366e6

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

@@ -0,0 +1,77 @@
+import SwiftUI
+
+struct TempTargetPresetsView: View {
+    @Environment(\.dismiss) var dismiss
+    let tempTargetPresets: [TempTargetPresetWatch]
+    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: [TempTargetPresetWatch] {
+        tempTargetPresets.sorted { $0.isEnabled && !$1.isEnabled }
+    }
+
+    private var activePreset: TempTargetPresetWatch? {
+        sortedPresets.first { $0.isEnabled }
+    }
+
+    var body: some View {
+        NavigationView {
+            List {
+                if let active = activePreset {
+                    Button("Stop \(active.name)") {
+                        state.sendCancelTempTargetRequest()
+                        dismiss()
+                    }
+                    .foregroundColor(.white)
+                    .listRowBackground(
+                        Color.red
+                            .clipShape(RoundedRectangle(cornerRadius: 8))
+                    )
+                }
+
+                if sortedPresets.isEmpty {
+                    Text("No Temp Target Presets")
+                        .font(.caption)
+                        .foregroundColor(.secondary)
+                } else {
+                    ForEach(sortedPresets, id: \.name) { preset in
+                        Button(action: {
+                            if !preset.isEnabled {
+                                state.sendActivateTempTargetRequest(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("Temp Target Presets")
+        }
+    }
+}

+ 8 - 1
Trio Watch App Extension/Views/TrioMainWatchView.swift

@@ -7,6 +7,7 @@ struct TrioMainWatchView: View {
     // misc
     @State private var currentPage: Int = 0
     @State private var rotationDegrees: Double = 0.0
+    @State private var showingTempTargetSheet = false
 
     // view visbility
     @State private var showingTreatmentMenuSheet: Bool = false
@@ -81,7 +82,7 @@ struct TrioMainWatchView: View {
                     .buttonStyle(WatchOSButtonStyle())
 
                     Button {
-                        // Perform an action here
+                        showingTempTargetSheet = true
                     } label: {
                         Image(systemName: "target")
                             .foregroundStyle(.green.opacity(0.75))
@@ -98,6 +99,12 @@ struct TrioMainWatchView: View {
                 )
             }
         }
+        .sheet(isPresented: $showingTempTargetSheet) {
+            TempTargetPresetsView(
+                tempTargetPresets: state.tempTargetPresets,
+                state: state
+            )
+        }
     }
 
     struct WatchOSButtonStyle: ButtonStyle {

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

@@ -19,6 +19,7 @@ import WatchConnectivity
     var iob: String? = "--"
     var lastLoopTime: String? = "--"
     var overridePresets: [OverridePresetWatch] = []
+    var tempTargetPresets: [TempTargetPresetWatch] = []
 
     override init() {
         super.init()
@@ -97,6 +98,30 @@ import WatchConnectivity
         }
     }
 
+    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)")
+        }
+    }
+
+    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)")
+        }
+    }
+
     // MARK: - WCSessionDelegate
 
     /// Called when the session has completed activation
@@ -165,6 +190,15 @@ import WatchConnectivity
                     return OverridePresetWatch(name: name, isEnabled: isEnabled)
                 }
             }
+
+            if let tempTargetData = message["tempTargetPresets"] as? [[String: Any]] {
+                self.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)
+                }
+            }
         }
     }
 

+ 16 - 6
Trio.xcodeproj/project.pbxproj

@@ -294,8 +294,11 @@
 		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 */; };
+		BD54A95B2D28087C00F9C1EE /* OverridePresetWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD54A95A2D28087700F9C1EE /* OverridePresetWatch.swift */; };
+		BD54A95C2D2808A300F9C1EE /* OverridePresetWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD54A95A2D28087700F9C1EE /* OverridePresetWatch.swift */; };
+		BD54A9712D281A8100F9C1EE /* TempTargetPresetsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD54A9702D281A7A00F9C1EE /* TempTargetPresetsView.swift */; };
+		BD54A9732D281ABC00F9C1EE /* TempTargetPresetWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD54A9722D281A9C00F9C1EE /* TempTargetPresetWatch.swift */; };
+		BD54A9742D281AEF00F9C1EE /* TempTargetPresetWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD54A9722D281A9C00F9C1EE /* TempTargetPresetWatch.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 */; };
@@ -996,7 +999,9 @@
 		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>"; };
+		BD54A95A2D28087700F9C1EE /* OverridePresetWatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverridePresetWatch.swift; sourceTree = "<group>"; };
+		BD54A9702D281A7A00F9C1EE /* TempTargetPresetsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetPresetsView.swift; sourceTree = "<group>"; };
+		BD54A9722D281A9C00F9C1EE /* TempTargetPresetWatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetPresetWatch.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>"; };
@@ -2004,7 +2009,8 @@
 		388E5A5925B6F0250019842D /* Models */ = {
 			isa = PBXGroup;
 			children = (
-				BD54A95A2D28087700F9C1EE /* OverridePreset.swift */,
+				BD54A9722D281A9C00F9C1EE /* TempTargetPresetWatch.swift */,
+				BD54A95A2D28087700F9C1EE /* OverridePresetWatch.swift */,
 				BDA25EFC2D261BF200035F34 /* WatchState.swift */,
 				DD07CA132CE80B73002D45A9 /* TimeInRangeChartStyle.swift */,
 				DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */,
@@ -2467,6 +2473,7 @@
 			isa = PBXGroup;
 			children = (
 				DD246F052D2836AA0027DDE0 /* GlucoseTrendView.swift */,
+				BD54A9702D281A7A00F9C1EE /* TempTargetPresetsView.swift */,
 				DD6F63CB2D27F606007D94CF /* TreatmentMenuView.swift */,
 				BD54A9582D27FB6A00F9C1EE /* OverridePresetsView.swift */,
 				BDA25F212D26D62200035F34 /* BolusInputView.swift */,
@@ -3676,6 +3683,7 @@
 				38B4F3CB25E502E200E76A18 /* WeakObjectSet.swift in Sources */,
 				38E989DD25F5021400C0CED0 /* PumpStatus.swift in Sources */,
 				BDFD165A2AE40438007F0DDA /* TreatmentsRootView.swift in Sources */,
+				BD54A9732D281ABC00F9C1EE /* TempTargetPresetWatch.swift in Sources */,
 				38E98A2525F52C9300C0CED0 /* IssueReporter.swift in Sources */,
 				DD1745522C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift in Sources */,
 				DD2CC85C2D25DA1000445446 /* GlucoseTargetsView.swift in Sources */,
@@ -3765,7 +3773,7 @@
 				DD17454B2C55C62800211FAC /* AutosensSettingsRootView.swift in Sources */,
 				DDF847DF2C5C28780049BB3B /* LiveActivitySettingsProvider.swift in Sources */,
 				DDB37CC52D05048F00D99BF4 /* ContactImageStorage.swift in Sources */,
-				BD54A95B2D28087C00F9C1EE /* OverridePreset.swift in Sources */,
+				BD54A95B2D28087C00F9C1EE /* OverridePresetWatch.swift in Sources */,
 				DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */,
 				BDF34EBE2C0A31D100D51995 /* CustomNotification.swift in Sources */,
 				BDC2EA472C3045AD00E5BBD0 /* Override.swift in Sources */,
@@ -3965,11 +3973,13 @@
 				BDA25F202D26D5FE00035F34 /* CarbsInputView.swift in Sources */,
 				BDA25F1C2D26BD0700035F34 /* TrendShape.swift in Sources */,
 				BDFF7A892D25F97D0016C40C /* TrioWatchApp.swift in Sources */,
-				BD54A95C2D2808A300F9C1EE /* OverridePreset.swift in Sources */,
+				BD54A9742D281AEF00F9C1EE /* TempTargetPresetWatch.swift in Sources */,
+				BD54A95C2D2808A300F9C1EE /* OverridePresetWatch.swift in Sources */,
 				BD54A9592D27FB7800F9C1EE /* OverridePresetsView.swift in Sources */,
 				BDA25F1E2D26D5DD00035F34 /* GlucoseChartView.swift in Sources */,
 				DD6F63CC2D27F615007D94CF /* TreatmentMenuView.swift in Sources */,
 				DD246F062D2836AA0027DDE0 /* GlucoseTrendView.swift in Sources */,
+				BD54A9712D281A8100F9C1EE /* TempTargetPresetsView.swift in Sources */,
 				BDA25EE62D260D5E00035F34 /* WatchState.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;

Trio/Sources/Models/OverridePreset.swift → Trio/Sources/Models/OverridePresetWatch.swift


+ 6 - 0
Trio/Sources/Models/TempTargetPresetWatch.swift

@@ -0,0 +1,6 @@
+import Foundation
+
+struct TempTargetPresetWatch: Hashable, Equatable {
+    let name: String
+    let isEnabled: Bool
+}

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

@@ -10,6 +10,7 @@ struct WatchState: Hashable, Equatable, Sendable {
     var cob: String?
     var lastLoopTime: String?
     var overridePresets: [OverridePresetWatch] = []
+    var tempTargetPresets: [TempTargetPresetWatch] = []
 
     static func == (lhs: WatchState, rhs: WatchState) -> Bool {
         lhs.currentGlucose == rhs.currentGlucose &&
@@ -23,7 +24,8 @@ struct WatchState: Hashable, Equatable, Sendable {
             lhs.iob == rhs.iob &&
             lhs.cob == rhs.cob &&
             lhs.lastLoopTime == rhs.lastLoopTime &&
-            lhs.overridePresets == rhs.overridePresets
+            lhs.overridePresets == rhs.overridePresets &&
+            lhs.tempTargetPresets == rhs.tempTargetPresets
     }
 
     func hash(into hasher: inout Hasher) {
@@ -39,5 +41,6 @@ struct WatchState: Hashable, Equatable, Sendable {
         hasher.combine(cob)
         hasher.combine(lastLoopTime)
         hasher.combine(overridePresets)
+        hasher.combine(tempTargetPresets)
     }
 }

+ 133 - 0
Trio/Sources/Services/WatchManager/AppleWatchManager.swift

@@ -18,6 +18,7 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var determinationStorage: DeterminationStorage!
     @Injected() private var overrideStorage: OverrideStorage!
+    @Injected() private var tempTargetStorage: TempTargetsStorage!
 
     private var units: GlucoseUnits = .mgdL
 
@@ -82,6 +83,14 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                 self.sendDataToWatch(state)
             }
         }.store(in: &subscriptions)
+
+        coreDataPublisher?.filterByEntityName("TempTargetStored").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
@@ -118,6 +127,7 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
             predicate: NSPredicate.predicateFor30MinAgoForDetermination
         )
         let overridePresetIds = await overrideStorage.fetchForOverridePresets()
+        let tempTargetPresetIds = await tempTargetStorage.fetchForTempTargetPresets()
 
         // Get NSManagedObjects
         let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared
@@ -126,6 +136,8 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
             .getNSManagedObject(with: determinationIds, context: backgroundContext)
         let overridePresetObjects: [OverrideStored] = await CoreDataStack.shared
             .getNSManagedObject(with: overridePresetIds, context: backgroundContext)
+        let tempTargetPresetObjects: [TempTargetStored] = await CoreDataStack.shared
+            .getNSManagedObject(with: tempTargetPresetIds, context: backgroundContext)
 
         return await backgroundContext.perform {
             var watchState = WatchState()
@@ -180,6 +192,14 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                 watchState.delta = deltaValue < 0 ? "-\(formattedDelta)" : "+\(formattedDelta)"
             }
 
+            // Set temp target presets with their enabled status
+            watchState.tempTargetPresets = tempTargetPresetObjects.map { tempTarget in
+                TempTargetPresetWatch(
+                    name: tempTarget.name ?? "",
+                    isEnabled: tempTarget.enabled
+                )
+            }
+
             // Set units
             watchState.units = self.units
 
@@ -238,6 +258,12 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                     "name": preset.name,
                     "isEnabled": preset.isEnabled
                 ]
+            },
+            "tempTargetPresets": state.tempTargetPresets.map { preset in
+                [
+                    "name": preset.name,
+                    "isEnabled": preset.isEnabled
+                ]
             }
         ]
 
@@ -299,6 +325,16 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                 print("📱 Received activate override request from watch for preset: \(presetName)")
                 self?.handleActivateOverride(presetName)
             }
+
+            if let presetName = message["activateTempTarget"] as? String {
+                print("📱 Received activate temp target request from watch for preset: \(presetName)")
+                self?.handleActivateTempTarget(presetName)
+            }
+
+            if message["cancelTempTarget"] as? Bool == true {
+                print("📱 Received cancel temp target request from watch")
+                self?.handleCancelTempTarget()
+            }
         }
     }
 
@@ -473,4 +509,101 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
             }
         }
     }
+
+    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()
+                            print("📱 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 {
+                            print("❌ Error cancelling temp target: \(error.localizedDescription)")
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private func handleActivateTempTarget(_ presetName: String) {
+        Task {
+            let context = CoreDataStack.shared.newTaskContext()
+
+            // Fetch all presets to find the one to activate
+            let presetIds = await tempTargetStorage.fetchForTempTargetPresets()
+            let presets: [TempTargetStored] = await CoreDataStack.shared
+                .getNSManagedObject(with: presetIds, context: context)
+
+            // Check for active temp target
+            if let activeTempTargetId = await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 1).first {
+                let activeTempTarget = await context.perform {
+                    context.object(with: activeTempTargetId) as? TempTargetStored
+                }
+
+                // Deactivate if exists
+                if let tempTarget = activeTempTarget {
+                    await context.perform {
+                        tempTarget.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 temp target: \(presetName)")
+
+                        // To activate the temp target also in oref
+                        let tempTarget = TempTarget(
+                            name: presetToActivate.name,
+                            createdAt: Date(),
+                            targetTop: presetToActivate.target?.decimalValue,
+                            targetBottom: presetToActivate.target?.decimalValue,
+                            duration: presetToActivate.duration?.decimalValue ?? 0,
+                            enteredBy: TempTarget.local,
+                            reason: TempTarget.custom,
+                            isPreset: true,
+                            enabled: true,
+                            halfBasalTarget: presetToActivate.halfBasalTarget?.decimalValue
+                        )
+                        self.tempTargetStorage.saveTempTargetsToStorage([tempTarget])
+
+                        // Send notification to update Adjustments UI
+                        Foundation.NotificationCenter.default.post(
+                            name: .didUpdateTempTargetConfiguration,
+                            object: nil
+                        )
+                    } catch {
+                        print("❌ Error activating temp target: \(error.localizedDescription)")
+                    }
+                }
+            }
+        }
+    }
 }