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

refactor Garmin: simplified manager and watch state models

## Overall Impact
- Reduced maintenance burden by removing ~1,300 lines of unused/over-engineered logic
- Improved code clarity and future maintainability
- Fixed known floating-point precision issues in watch state serialization
- Maintained all functional requirements with cleaner implementation"

## Removed Complexity:
- Eliminated complex caching mechanisms (app installation status, watchface change tracking)
- Removed connection state tracking and related caching logic
- Simplified throttle/debounce implementation by removing separate tracking for immediate vs throttled sends
- Removed verbose inline documentation for each @Injected property
- Removed data transmission logic comments (datafield vs watchface app handling now implicit in simpler flow)

## Simplified State Management:
- Removed @unchecked Sendable conformance (simplified thread safety requirements)
- Removed NSLock instances for hash tracking (single-threaded approach simplifies locking)
- Removed separate appStatusCacheLock and hashLock (consolidated to simpler model)
- Cleaned up device connection state tracking

## Cleaner Glucose/Determination Coordination:
- Simplified fallback delay configuration with documented reasoning (~5-24s avg delays, <1% >15s)
- Removed old caching strategy for determination data
- More direct dispatch queue usage for timer management
Robert 4 месяцев назад
Родитель
Сommit
d64095937f

+ 11 - 25
Trio/Sources/Models/GarminWatchState.swift

@@ -1,3 +1,9 @@
+//
+//  GarminWatchState.swift
+//  Trio
+//
+//  Created by Cengiz Deniz on 25.01.25.
+//
 import Foundation
 import SwiftUI
 
@@ -9,38 +15,16 @@ import SwiftUI
 struct GarminWatchState: Hashable, Equatable, Sendable, Encodable {
     /// Timestamp of the glucose reading in milliseconds since Unix epoch
     var date: UInt64?
-
-    /// Sensor glucose value in raw mg/dL (no unit conversion applied)
     var sgv: Int16?
-
-    /// Change in glucose since previous reading as an integer
     var delta: Int16?
-
-    /// Glucose trend direction (e.g., "Flat", "FortyFiveUp", "SingleUp")
     var direction: String?
-
-    /// Signal noise level (optional, typically not used)
     var noise: Double?
-
-    /// Unit hint for the watchface ("mgdl" or "mmol")
     var units_hint: String?
-
-    /// Insulin on board as a decimal value (only in first array entry)
     var iob: Double?
-
-    /// Current temp basal rate in U/hr (only in first array entry)
     var tbr: Double?
-
-    /// Carbs on board as a decimal value (only in first array entry)
     var cob: Double?
-
-    /// Predicted eventual blood glucose (excluded if data type 2 is set to TBR)
     var eventualBG: Int16?
-
-    /// Current insulin sensitivity factor as an integer (only in first array entry)
     var isf: Int16?
-
-    /// AutoISF sensitivity ratio (included only if data type 1 is set to sensRatio)
     var sensRatio: Double?
 
     // MARK: - Display Configuration Fields
@@ -105,6 +89,7 @@ struct GarminWatchState: Hashable, Equatable, Sendable, Encodable {
     }
 
     /// Custom encoding that excludes nil values from the JSON output
+    /// Double values are rounded to 2 decimal places to prevent floating point artifacts
     func encode(to encoder: Encoder) throws {
         var container = encoder.container(keyedBy: CodingKeys.self)
         try container.encodeIfPresent(date, forKey: .date)
@@ -113,12 +98,13 @@ struct GarminWatchState: Hashable, Equatable, Sendable, Encodable {
         try container.encodeIfPresent(direction, forKey: .direction)
         try container.encodeIfPresent(noise, forKey: .noise)
         try container.encodeIfPresent(units_hint, forKey: .units_hint)
-        try container.encodeIfPresent(iob, forKey: .iob)
-        try container.encodeIfPresent(tbr, forKey: .tbr)
+        // Round Double values to 2 decimal places to prevent floating point artifacts like "0.5600000000000001"
+        try container.encodeIfPresent(iob?.roundedDouble(toPlaces: 2), forKey: .iob)
+        try container.encodeIfPresent(tbr?.roundedDouble(toPlaces: 2), forKey: .tbr)
         try container.encodeIfPresent(cob, forKey: .cob)
         try container.encodeIfPresent(eventualBG, forKey: .eventualBG)
         try container.encodeIfPresent(isf, forKey: .isf)
-        try container.encodeIfPresent(sensRatio, forKey: .sensRatio)
+        try container.encodeIfPresent(sensRatio?.roundedDouble(toPlaces: 2), forKey: .sensRatio)
         try container.encodeIfPresent(displayPrimaryAttributeChoice, forKey: .displayPrimaryAttributeChoice)
         try container.encodeIfPresent(displaySecondaryAttributeChoice, forKey: .displaySecondaryAttributeChoice)
     }

+ 1 - 34
Trio/Sources/Modules/WatchConfig/View/WatchConfigGarminView.swift

@@ -19,14 +19,8 @@ struct WatchConfigGarminView: View {
     #if targetEnvironment(simulator)
         /// Adds a mock Garmin device for simulator UI testing
         private func addMockDevice() {
-            // Create a mock IQDevice using a simple class that conforms to the protocol
-            let mockDevice = MockIQDevice(
-                uuid: UUID(),
-                friendlyName: "Mock Garmin Fenix 7",
-                modelName: "fenix7"
-            )
+            let mockDevice = BaseGarminManager.MockIQDevice.createSimulated()
             state.devices.append(mockDevice)
-            // Persist to garmin manager so it survives view reloads
             state.deleteGarminDevice()
         }
     #endif
@@ -87,7 +81,6 @@ struct WatchConfigGarminView: View {
                                 .buttonStyle(.bordered)
                             } else {
                                 Button {
-                                    // Remove all devices
                                     state.devices.removeAll()
                                     state.deleteGarminDevice()
                                 } label: {
@@ -200,29 +193,3 @@ struct WatchConfigGarminView: View {
         }
     }
 }
-
-#if targetEnvironment(simulator)
-    // Mock IQDevice class for simulator testing
-    // Minimal implementation just for UI testing - no actual Garmin functionality
-    class MockIQDevice: IQDevice {
-        private let _uuid: UUID
-        private let _friendlyName: String
-        private let _modelName: String
-
-        override var uuid: UUID { _uuid }
-        override var friendlyName: String { _friendlyName }
-        override var modelName: String { _modelName }
-        var status: IQDeviceStatus { .connected }
-
-        init(uuid: UUID, friendlyName: String, modelName: String) {
-            _uuid = uuid
-            _friendlyName = friendlyName
-            _modelName = modelName
-            super.init()
-        }
-
-        @available(*, unavailable) required init?(coder _: NSCoder) {
-            fatalError("init(coder:) not implemented for mock device")
-        }
-    }
-#endif

Разница между файлами не показана из-за своего большого размера
+ 364 - 1629
Trio/Sources/Services/WatchManager/GarminManager.swift