Ver código fonte

Allow to configure the content of the third part of the watch faces
Add steps display

Pierre L 3 anos atrás
pai
commit
94b17819e6

+ 2 - 1
FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json

@@ -26,5 +26,6 @@
     "individualAdjustmentFactor": 0.5,
     "timeCap": 8,
     "minuteInterval": 30,
-    "delay": 60
+    "delay": 60,
+    "displayOnWatch": "BGTarget"
 }

+ 7 - 0
FreeAPS/Sources/Models/FreeAPSSettings.swift

@@ -12,6 +12,7 @@ struct FreeAPSSettings: JSON, Equatable {
     var insulinReqFraction: Decimal = 0.7
     var skipBolusScreenAfterCarbs: Bool = false
     var displayHR: Bool = false
+    var displayOnWatch: AwConfig = .BGTarget
     var cgm: CGMType = .nightscout
     var uploadGlucose: Bool = false
     var useCalendar: Bool = false
@@ -81,6 +82,12 @@ extension FreeAPSSettings: Decodable {
 
         if let displayHR = try? container.decode(Bool.self, forKey: .displayHR) {
             settings.displayHR = displayHR
+            // compatibility if displayOnWatch is not available in json files
+            settings.displayOnWatch = (displayHR == true) ? AwConfig.HR : AwConfig.BGTarget
+        }
+
+        if let displayOnWatch = try? container.decode(AwConfig.self, forKey: .displayOnWatch) {
+            settings.displayOnWatch = displayOnWatch
         }
 
         if let cgm = try? container.decode(CGMType.self, forKey: .cgm) {

+ 1 - 1
FreeAPS/Sources/Modules/WatchConfig/View/WatchConfigRootView.swift

@@ -14,7 +14,7 @@ extension WatchConfig {
                         label: Text("Display on Watch")
                     ) {
                         ForEach(AwConfig.allCases) { v in
-                            Text(v.rawValue).tag(v)
+                            Text(v.displayName).tag(v)
                         }
                     }
                 }

+ 25 - 23
FreeAPS/Sources/Modules/WatchConfig/WatchConfigStateModel.swift

@@ -1,10 +1,22 @@
 import ConnectIQ
 import SwiftUI
 
-enum AwConfig: String, CaseIterable, Identifiable {
-    var id: Self { self }
-    case HR = "Heart Rate"
-    case BGTarget = "Glucose Target"
+enum AwConfig: String, JSON, CaseIterable, Identifiable, Codable {
+    var id: String { rawValue }
+    case HR
+    case BGTarget
+    case steps
+
+    var displayName: String {
+        switch self {
+        case .BGTarget:
+            return "Glucose Target"
+        case .HR:
+            return "Heart Rate"
+        case .steps:
+            return "Steps"
+        }
+    }
 }
 
 extension WatchConfig {
@@ -12,32 +24,22 @@ extension WatchConfig {
         @Injected() private var garmin: GarminManager!
         @Published var devices: [IQDevice] = []
         @Published var selectedAwConfig: AwConfig = .HR
-        @Published var displayHR = false
 
         private(set) var preferences = Preferences()
 
         override func subscribe() {
             preferences = provider.preferences
-            switch settingsManager.settings.displayHR {
-            case true:
-                selectedAwConfig = .HR
-            case false:
-                selectedAwConfig = .BGTarget
-            }
 
-            $selectedAwConfig.removeDuplicates()
-                .map {
-                    switch $0 {
-                    case .HR:
-                        return true
-                    case .BGTarget:
-                        return false
-                    }
+            subscribeSetting(\.displayOnWatch, on: $selectedAwConfig) { selectedAwConfig = $0 }
+            didSet: { [weak self] value in
+                // for compatibility with old displayHR
+                switch value {
+                case .HR:
+                    self?.settingsManager.settings.displayHR = true
+                default:
+                    self?.settingsManager.settings.displayHR = false
                 }
-                .sink { [weak self] value in
-                    self?.settingsManager.settings.displayHR = value
-                }
-                .store(in: &lifetime)
+            }
 
             devices = garmin.devices
         }

+ 1 - 1
FreeAPS/Sources/Services/WatchManager/WatchManager.swift

@@ -94,7 +94,7 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
                 }
             self.state.bolusAfterCarbs = !self.settingsManager.settings.skipBolusScreenAfterCarbs
 
-            self.state.displayHR = self.settingsManager.settings.displayHR
+            self.state.displayOnWatch = self.settingsManager.settings.displayOnWatch
 
             let eBG = self.evetualBGStraing()
             self.state.eventualBG = eBG.map { "⇢ " + $0 }

+ 1 - 1
FreeAPSWatch WatchKit Extension/DataFlow.swift

@@ -20,7 +20,7 @@ struct WatchState: Codable {
     var bolusAfterCarbs: Bool?
     var eventualBG: String?
     var eventualBGRaw: String?
-    var displayHR: Bool?
+    var displayOnWatch: AwConfig?
 }
 
 struct TempTargetWatchPreset: Codable, Identifiable {

+ 64 - 5
FreeAPSWatch WatchKit Extension/Views/MainView.swift

@@ -13,6 +13,7 @@ struct MainView: View {
     @State var isTargetsActive = false
     @State var isBolusActive = false
     @State private var pulse = 0
+    @State private var steps = 0
 
     @GestureState var isDetectingLongPress = false
     @State var completedLongPress = false
@@ -118,7 +119,8 @@ struct MainView: View {
                     .scaledToFill()
                     .minimumScaleFactor(0.5)
 
-                if state.displayHR {
+                switch state.displayOnWatch {
+                case .HR:
                     Spacer()
                     HStack {
                         if completedLongPress {
@@ -146,14 +148,25 @@ struct MainView: View {
                             .gesture(longPress)
                         }
                     }
-
-                } else if let eventualBG = state.eventualBG.nonEmpty {
+                case .BGTarget:
+                    if let eventualBG = state.eventualBG.nonEmpty {
+                        Spacer()
+                        HStack {
+                            Text(eventualBG)
+                                .font(.caption2)
+                                .scaledToFill()
+                                .foregroundColor(.secondary)
+                                .minimumScaleFactor(0.5)
+                        }
+                    }
+                case .steps:
                     Spacer()
                     HStack {
-                        Text(eventualBG)
+                        Text("🦶" + " \(steps)")
+                            .fontWeight(.regular)
                             .font(.caption2)
                             .scaledToFill()
-                            .foregroundColor(.secondary)
+                            .foregroundColor(.white)
                             .minimumScaleFactor(0.5)
                     }
                 }
@@ -255,15 +268,61 @@ struct MainView: View {
     func start() {
         autorizeHealthKit()
         startHeartRateQuery(quantityTypeIdentifier: .heartRate)
+        startStepsQuery(quantityTypeIdentifier: .stepCount)
     }
 
     func autorizeHealthKit() {
         let healthKitTypes: Set = [
+            HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!,
             HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!
         ]
         healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { _, _ in }
     }
 
+    private func startStepsQuery(quantityTypeIdentifier _: HKQuantityTypeIdentifier) {
+        let type = HKQuantityType.quantityType(forIdentifier: .stepCount)!
+        let now = Date()
+        let startOfDay = Calendar.current.startOfDay(for: now)
+        var interval = DateComponents()
+        interval.day = 1
+        let query = HKStatisticsCollectionQuery(
+            quantityType: type,
+            quantitySamplePredicate: nil,
+            options: [.cumulativeSum],
+            anchorDate: startOfDay,
+            intervalComponents: interval
+        )
+
+        query.initialResultsHandler = { _, result, _ in
+            var resultCount = 0.0
+            guard let result = result else {
+                self.steps = 0
+                return
+            }
+            result.enumerateStatistics(from: startOfDay, to: now) { statistics, _ in
+
+                if let sum = statistics.sumQuantity() {
+                    // Get steps (they are of double type)
+                    resultCount = sum.doubleValue(for: HKUnit.count())
+                } // end if
+                // Return
+                self.steps = Int(resultCount)
+            }
+        }
+
+        query.statisticsUpdateHandler = {
+            _, statistics, _, _ in
+
+            // If new statistics are available
+            if let sum = statistics?.sumQuantity() {
+                let resultCount = sum.doubleValue(for: HKUnit.count())
+                // Return
+                self.steps = Int(resultCount)
+            } // end if
+        }
+        healthStore.execute(query)
+    }
+
     private func startHeartRateQuery(quantityTypeIdentifier: HKQuantityTypeIdentifier) {
         let devicePredicate = HKQuery.predicateForObjects(from: [HKDevice.local()])
         let updateHandler: (HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, Error?) -> Void = {

+ 9 - 2
FreeAPSWatch WatchKit Extension/WatchStateModel.swift

@@ -3,6 +3,13 @@ import Foundation
 import SwiftUI
 import WatchConnectivity
 
+enum AwConfig: String, CaseIterable, Identifiable, Codable {
+    var id: String { rawValue }
+    case HR
+    case BGTarget
+    case steps
+}
+
 class WatchStateModel: NSObject, ObservableObject {
     var session: WCSession
 
@@ -23,7 +30,7 @@ class WatchStateModel: NSObject, ObservableObject {
     @Published var isCarbsViewActive = false
     @Published var isTempTargetViewActive = false
     @Published var isBolusViewActive = false
-    @Published var displayHR = false
+    @Published var displayOnWatch: AwConfig = .BGTarget
     @Published var eventualBG = ""
     @Published var isConfirmationViewActive = false {
         didSet {
@@ -160,7 +167,7 @@ class WatchStateModel: NSObject, ObservableObject {
         bolusAfterCarbs = state.bolusAfterCarbs ?? true
         lastUpdate = Date()
         eventualBG = state.eventualBG ?? ""
-        displayHR = state.displayHR ?? false
+        displayOnWatch = state.displayOnWatch ?? .BGTarget
     }
 }