Forráskód Böngészése

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

polscm32 aka Marvout 1 éve
szülő
commit
e1bec1ab1d

+ 1 - 8
Trio Watch App Extension/Helper/Helper+ButtonStyles.swift

@@ -1,11 +1,6 @@
 import SwiftUI
 
 struct WatchOSButtonStyle: ButtonStyle {
-    var backgroundGradient = LinearGradient(colors: [
-        Color(red: 0.721, green: 0.341, blue: 1),
-        Color(red: 0.486, green: 0.545, blue: 0.953),
-        Color(red: 0.262, green: 0.733, blue: 0.914)
-    ], startPoint: .topLeading, endPoint: .bottomTrailing)
     var foregroundColor: Color = .white
     var fontSize: Font = .title2
 
@@ -19,9 +14,7 @@ struct WatchOSButtonStyle: ButtonStyle {
             .font(fontSize)
             .fontWeight(is40mm ? .medium : .semibold)
             .padding(is40mm ? 6 : 8)
-            .background(
-                backgroundGradient.opacity(configuration.isPressed ? 0.8 : 1.0)
-            )
+            .background(Color.tabBar.opacity(configuration.isPressed ? 0.8 : 1.0))
             .clipShape(Circle())
             .animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
     }

+ 3 - 14
Trio Watch App Extension/Views/GlucoseChartView.swift

@@ -5,7 +5,7 @@ import SwiftUI
 // MARK: - Current Glucose View
 
 struct GlucoseChartView: View {
-    let glucoseValues: [(date: Date, glucose: Double)]
+    let glucoseValues: [(date: Date, glucose: Double, color: Color)]
     @State private var timeWindow: TimeWindow = .threeHours
 
     enum TimeWindow: Int {
@@ -25,22 +25,11 @@ struct GlucoseChartView: View {
     }
 
     // TODO: should we only change the x axis here like we do in the main chart instead of filtering the values?
-    private var filteredValues: [(date: Date, glucose: Double)] {
+    private var filteredValues: [(date: Date, glucose: Double, color: Color)] {
         let cutoffDate = Date().addingTimeInterval(-Double(timeWindow.rawValue) * 3600)
         return glucoseValues.filter { $0.date > cutoffDate }
     }
 
-    // TODO: replace hard coded values with actual settings and add dynamic color
-    private func glucoseColor(_ value: Double) -> Color {
-        if value > 180 {
-            return .orange
-        } else if value < 70 {
-            return .loopRed
-        } else {
-            return .loopGreen
-        }
-    }
-
     var glucosePointSize: CGFloat {
         switch timeWindow {
         case .threeHours: return 18
@@ -62,7 +51,7 @@ struct GlucoseChartView: View {
                             x: .value("Time", reading.date),
                             y: .value("Glucose", reading.glucose)
                         )
-                        .foregroundStyle(glucoseColor(reading.glucose))
+                        .foregroundStyle(reading.color)
                         .symbolSize(glucosePointSize)
                     }
                 }

+ 5 - 3
Trio Watch App Extension/WatchState.swift

@@ -1,4 +1,5 @@
 import Foundation
+import SwiftUI
 import WatchConnectivity
 
 /// WatchState manages the communication between the Watch app and the iPhone app using WatchConnectivity.
@@ -14,7 +15,7 @@ import WatchConnectivity
     var currentGlucose: String = "--"
     var trend: String? = ""
     var delta: String? = "--"
-    var glucoseValues: [(date: Date, glucose: Double)] = []
+    var glucoseValues: [(date: Date, glucose: Double, color: Color)] = []
     var cob: String? = "--"
     var iob: String? = "--"
     var lastLoopTime: String? = "--"
@@ -317,10 +318,11 @@ import WatchConnectivity
             if let glucoseData = message["glucoseValues"] as? [[String: Any]] {
                 self.glucoseValues = glucoseData.compactMap { data in
                     guard let glucose = data["glucose"] as? Double,
-                          let timestamp = data["date"] as? TimeInterval
+                          let timestamp = data["date"] as? TimeInterval,
+                          let color = data["color"] as? Color
                     else { return nil }
 
-                    return (Date(timeIntervalSince1970: timestamp), glucose)
+                    return (Date(timeIntervalSince1970: timestamp), glucose, color)
                 }
                 .sorted { $0.date < $1.date }
             }

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

@@ -1,10 +1,11 @@
 import Foundation
+import SwiftUI
 
 struct WatchState: Hashable, Equatable, Sendable {
     var currentGlucose: String?
     var trend: String?
     var delta: String?
-    var glucoseValues: [(date: Date, glucose: Double)] = []
+    var glucoseValues: [(date: Date, glucose: Double, color: Color)] = []
     var units: GlucoseUnits = .mgdL
     var iob: String?
     var cob: String?
@@ -26,7 +27,7 @@ struct WatchState: Hashable, Equatable, Sendable {
             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
+                $0.0.date == $0.1.date && $0.0.glucose == $0.1.glucose && $0.0.color == $0.1.color
             } &&
             lhs.units == rhs.units &&
             lhs.iob == rhs.iob &&
@@ -49,6 +50,7 @@ struct WatchState: Hashable, Equatable, Sendable {
         for value in glucoseValues {
             hasher.combine(value.date)
             hasher.combine(value.glucose)
+            hasher.combine(value.color)
         }
         hasher.combine(units)
         hasher.combine(iob)

+ 108 - 5
Trio/Sources/Services/WatchManager/AppleWatchManager.swift

@@ -14,14 +14,19 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
     private var session: WCSession?
 
     @Injected() var broadcaster: Broadcaster!
-    @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var apsManager: APSManager!
     @Injected() private var settingsManager: SettingsManager!
+    @Injected() private var fileStorage: FileStorage!
+    @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var determinationStorage: DeterminationStorage!
     @Injected() private var overrideStorage: OverrideStorage!
     @Injected() private var tempTargetStorage: TempTargetsStorage!
 
     private var units: GlucoseUnits = .mgdL
+    private var glucoseColorScheme: GlucoseColorScheme = .staticColor
+    private var lowGlucose: Decimal = 70.0
+    private var highGlucose: Decimal = 180.0
+    private var currentGlucoseTarget: Decimal = 100.0
 
     private var coreDataPublisher: AnyPublisher<Set<NSManagedObject>, Never>?
     private var subscriptions = Set<AnyCancellable>()
@@ -35,7 +40,14 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
         super.init()
         injectServices(resolver)
         setupWatchSession()
+
         units = settingsManager.settings.units
+        glucoseColorScheme = settingsManager.settings.glucoseColorScheme
+        lowGlucose = settingsManager.settings.low
+        highGlucose = settingsManager.settings.high
+        Task {
+            currentGlucoseTarget = await getCurrentGlucoseTarget() ?? Decimal(100)
+        }
         broadcaster.register(SettingsObserver.self, observer: self)
         broadcaster.register(PumpSettingsObserver.self, observer: self)
         broadcaster.register(PreferencesObserver.self, observer: self)
@@ -178,13 +190,46 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
 
             // Map glucose values
             watchState.glucoseValues = glucoseObjects.compactMap { glucose in
-                guard let date = glucose.date else { return nil }
-                return (date: date, glucose: Double(glucose.glucose))
+
+                var glucoseValue: Double
+                if self.units == .mgdL {
+                    glucoseValue = Double(glucose.glucose)
+                } else {
+                    let mgdlValue = Decimal(glucose.glucose)
+                    glucoseValue = Double(truncating: mgdlValue.asMmolL as NSNumber)
+                }
+
+                // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
+                let hardCodedLow = Decimal(55)
+                let hardCodedHigh = Decimal(220)
+                let isDynamicColorScheme = self.glucoseColorScheme == .dynamicColor
+
+                let highGlucoseValue = isDynamicColorScheme ? hardCodedHigh : self.highGlucose
+                let lowGlucoseValue = isDynamicColorScheme ? hardCodedLow : self.lowGlucose
+                let highGlucoseColorValue = self.units == .mgdL ? highGlucoseValue : highGlucoseValue.asMmolL
+                let lowGlucoseColorValue = self.units == .mgdL ? lowGlucoseValue : lowGlucoseValue.asMmolL
+                let targetGlucose = self.units == .mgdL ? self.currentGlucoseTarget : self.currentGlucoseTarget.asMmolL
+
+                let glucoseColor = Trio.getDynamicGlucoseColor(
+                    glucoseValue: Decimal(glucose.glucose),
+                    highGlucoseColorValue: highGlucoseColorValue,
+                    lowGlucoseColorValue: lowGlucoseColorValue,
+                    targetGlucose: targetGlucose,
+                    glucoseColorScheme: self.glucoseColorScheme
+                )
+
+                return (date: glucose.date ?? Date(), glucose: glucoseValue, color: glucoseColor)
             }
             .sorted { $0.date < $1.date }
 
             // Set current glucose with proper formatting
-            watchState.currentGlucose = "\(latestGlucose.glucose)"
+            if self.units == .mgdL {
+                watchState.currentGlucose = "\(latestGlucose.glucose)"
+            } else {
+                let mgdlValue = Decimal(latestGlucose.glucose)
+                let latestGlucoseValue = Double(truncating: mgdlValue.asMmolL as NSNumber)
+                watchState.currentGlucose = "\(latestGlucoseValue)"
+            }
 
             // Convert direction to trend string
             watchState.trend = latestGlucose.direction
@@ -265,7 +310,8 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
             "glucoseValues": state.glucoseValues.map { value in
                 [
                     "glucose": value.glucose,
-                    "date": value.date.timeIntervalSince1970
+                    "date": value.date.timeIntervalSince1970,
+                    "color": value.color
                 ]
             },
             "overridePresets": state.overridePresets.map { preset in
@@ -778,9 +824,66 @@ extension BaseWatchManager: SettingsObserver, PumpSettingsObserver, PreferencesO
 
     // to update the rest
     func settingsDidChange(_: TrioSettings) {
+        units = settingsManager.settings.units
+        glucoseColorScheme = settingsManager.settings.glucoseColorScheme
+        lowGlucose = settingsManager.settings.low
+        highGlucose = settingsManager.settings.high
+
         Task {
             let state = await self.setupWatchState()
             self.sendDataToWatch(state)
         }
     }
 }
+
+extension BaseWatchManager {
+    /// Retrieves the current glucose target based on the time of day.
+    private func getCurrentGlucoseTarget() async -> Decimal? {
+        let now = Date()
+        let calendar = Calendar.current
+        let dateFormatter = DateFormatter()
+        dateFormatter.dateFormat = "HH:mm"
+        dateFormatter.timeZone = TimeZone.current
+
+        let bgTargets = await fileStorage.retrieveAsync(OpenAPS.Settings.bgTargets, as: BGTargets.self)
+            ?? BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
+            ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])
+        let entries: [(start: String, value: Decimal)] = bgTargets.targets.map { ($0.start, $0.low) }
+
+        for (index, entry) in entries.enumerated() {
+            guard let entryTime = dateFormatter.date(from: entry.start) else {
+                print("Invalid entry start time: \(entry.start)")
+                continue
+            }
+
+            let entryComponents = calendar.dateComponents([.hour, .minute, .second], from: entryTime)
+            let entryStartTime = calendar.date(
+                bySettingHour: entryComponents.hour!,
+                minute: entryComponents.minute!,
+                second: entryComponents.second!,
+                of: now
+            )!
+
+            let entryEndTime: Date
+            if index < entries.count - 1,
+               let nextEntryTime = dateFormatter.date(from: entries[index + 1].start)
+            {
+                let nextEntryComponents = calendar.dateComponents([.hour, .minute, .second], from: nextEntryTime)
+                entryEndTime = calendar.date(
+                    bySettingHour: nextEntryComponents.hour!,
+                    minute: nextEntryComponents.minute!,
+                    second: nextEntryComponents.second!,
+                    of: now
+                )!
+            } else {
+                entryEndTime = calendar.date(byAdding: .day, value: 1, to: entryStartTime)!
+            }
+
+            if now >= entryStartTime, now < entryEndTime {
+                return entry.value
+            }
+        }
+
+        return nil
+    }
+}