Kaynağa Gözat

Carbs on charts

Ivan Valkou 5 yıl önce
ebeveyn
işleme
24428c1e04

+ 1 - 1
FreeAPS/Sources/Models/CarbsEntry.swift

@@ -1,6 +1,6 @@
 import Foundation
 
-struct CarbsEntry: JSON {
+struct CarbsEntry: JSON, Equatable {
     let createdAt: Date
     let carbs: Decimal
     let enteredBy: String?

+ 1 - 0
FreeAPS/Sources/Modules/Home/HomeDataFlow.swift

@@ -12,4 +12,5 @@ protocol HomeProvider: Provider {
     func pumpSettings() -> PumpSettings
     func basalProfile() -> [BasalProfileEntry]
     func tempTargets(hours: Int) -> [TempTarget]
+    func carbs(hours: Int) -> [CarbsEntry]
 }

+ 7 - 0
FreeAPS/Sources/Modules/Home/HomeProvider.swift

@@ -7,6 +7,7 @@ extension Home {
         @Injected() var glucoseStorage: GlucoseStorage!
         @Injected() var pumpHistoryStorage: PumpHistoryStorage!
         @Injected() var tempTargetsStorage: TempTargetsStorage!
+        @Injected() var carbsStorage: CarbsStorage!
 
         var suggestion: Suggestion? {
             try? storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self)
@@ -34,6 +35,12 @@ extension Home {
             }
         }
 
+        func carbs(hours: Int) -> [CarbsEntry] {
+            carbsStorage.recent().filter {
+                $0.createdAt.addingTimeInterval(hours.hours.timeInterval) > Date()
+            }
+        }
+
         func pumpSettings() -> PumpSettings {
             (try? storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self))
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))

+ 16 - 1
FreeAPS/Sources/Modules/Home/HomeViewModel.swift

@@ -17,6 +17,7 @@ extension Home {
         @Published var maxBasal: Decimal = 2
         @Published var basalProfile: [BasalProfileEntry] = []
         @Published var tempTargets: [TempTarget] = []
+        @Published var carbs: [CarbsEntry] = []
 
         @Published var allowManualTemp = false
         private(set) var units: GlucoseUnits = .mmolL
@@ -28,6 +29,8 @@ extension Home {
             setupPumpSettings()
             setupBasalProfile()
             setupTempTargets()
+            setupCarbs()
+
             suggestion = provider.suggestion
             units = settingsManager.settings.units
             allowManualTemp = !settingsManager.settings.closedLoop
@@ -39,6 +42,7 @@ extension Home {
             broadcaster.register(PumpSettingsObserver.self, observer: self)
             broadcaster.register(BasalProfileObserver.self, observer: self)
             broadcaster.register(TempTargetsObserver.self, observer: self)
+            broadcaster.register(CarbsObserver.self, observer: self)
         }
 
         func addCarbs() {
@@ -114,6 +118,12 @@ extension Home {
                 self.tempTargets = self.provider.tempTargets(hours: self.filteredHours)
             }
         }
+
+        private func setupCarbs() {
+            DispatchQueue.main.async {
+                self.carbs = self.provider.carbs(hours: self.filteredHours)
+            }
+        }
     }
 }
 
@@ -124,7 +134,8 @@ extension Home.ViewModel:
     PumpHistoryObserver,
     PumpSettingsObserver,
     BasalProfileObserver,
-    TempTargetsObserver
+    TempTargetsObserver,
+    CarbsObserver
 {
     func glucoseDidUpdate(_: [BloodGlucose]) {
         setupGlucose()
@@ -154,4 +165,8 @@ extension Home.ViewModel:
     func tempTargetsDidUpdate(_: [TempTarget]) {
         setupTempTargets()
     }
+
+    func carbsDidUpdate(_: [CarbsEntry]) {
+        setupCarbs()
+    }
 }

+ 56 - 9
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -21,6 +21,8 @@ struct MainChartView: View {
         static let yLinesCount = 5
         static let bolusSize: CGFloat = 8
         static let bolusScale: CGFloat = 8
+        static let carbsSize: CGFloat = 10
+        static let carbsScale: CGFloat = 0.5
     }
 
     @Binding var glucose: [BloodGlucose]
@@ -31,6 +33,7 @@ struct MainChartView: View {
     @Binding var maxBasal: Decimal
     @Binding var basalProfile: [BasalProfileEntry]
     @Binding var tempTargets: [TempTarget]
+    @Binding var carbs: [CarbsEntry]
     let units: GlucoseUnits
 
     @State var didAppearTrigger = false
@@ -38,10 +41,11 @@ struct MainChartView: View {
     @State private var predictionDots: [PredictionType: [CGRect]] = [:]
     @State private var bolusDots: [CGRect] = []
     @State private var bolusPath = Path()
-    @State private var bolusLabels = AnyView(EmptyView())
     @State private var tempBasalPath = Path()
     @State private var regularBasalPath = Path()
     @State private var tempTargetsPath = Path()
+    @State private var carbsDots: [CGRect] = []
+    @State private var carbsPath = Path()
 
     private var dateDormatter: DateFormatter {
         let formatter = DateFormatter()
@@ -72,6 +76,13 @@ struct MainChartView: View {
         return formatter
     }
 
+    private var carbsFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        return formatter
+    }
+
     // MARK: - Views
 
     var body: some View {
@@ -91,8 +102,8 @@ struct MainChartView: View {
                     ScrollViewReader { scroll in
                         ZStack(alignment: .top) {
                             tempTargetsView(fullSize: geo.size)
-                            basalChart(fullSize: geo.size)
-                            mainChart(fullSize: geo.size).id("End")
+                            basalView(fullSize: geo.size)
+                            mainView(fullSize: geo.size).id("End")
                                 .onChange(of: glucose) { _ in
                                     scroll.scrollTo("End", anchor: .trailing)
                                 }
@@ -129,7 +140,7 @@ struct MainChartView: View {
         }
     }
 
-    private func basalChart(fullSize: CGSize) -> some View {
+    private func basalView(fullSize: CGSize) -> some View {
         ZStack {
             tempBasalPath.fill(Color.blue)
             tempBasalPath.stroke(Color.blue, lineWidth: 1)
@@ -157,7 +168,7 @@ struct MainChartView: View {
         }
     }
 
-    private func mainChart(fullSize: CGSize) -> some View {
+    private func mainView(fullSize: CGSize) -> some View {
         Group {
             VStack {
                 ZStack {
@@ -172,9 +183,10 @@ struct MainChartView: View {
                         }
                     }
                     .stroke(Color.secondary, lineWidth: 0.2)
+                    carbsView(fullSize: fullSize)
                     bolusView(fullSize: fullSize)
-                    glucosePath(fullSize: fullSize)
-                    predictions(fullSize: fullSize)
+                    glucoseView(fullSize: fullSize)
+                    predictionsView(fullSize: fullSize)
                 }
                 ZStack {
                     // X time labels
@@ -195,7 +207,7 @@ struct MainChartView: View {
         .frame(width: fullGlucoseWidth(viewWidth: fullSize.width) + additionalWidth(viewWidth: fullSize.width))
     }
 
-    private func glucosePath(fullSize: CGSize) -> some View {
+    private func glucoseView(fullSize: CGSize) -> some View {
         Path { path in
             for rect in glucoseDots {
                 path.addEllipse(in: rect)
@@ -232,6 +244,28 @@ struct MainChartView: View {
         }
     }
 
+    private func carbsView(fullSize: CGSize) -> some View {
+        ZStack {
+            carbsPath
+                .fill(Color.orange)
+            carbsPath
+                .stroke(Color.primary, lineWidth: 0.5)
+
+            ForEach(carbsDots.indexed(), id: \.1.minX) { index, rect -> AnyView in
+                let position = CGPoint(x: rect.midX, y: rect.minY - 8)
+                return Text(carbsFormatter.string(from: carbs[index].carbs as NSNumber)!).font(.caption2)
+                    .position(position)
+                    .asAny()
+            }
+        }
+        .onChange(of: carbs) { _ in
+            calculateCarbsDots(fullSize: fullSize)
+        }
+        .onChange(of: didAppearTrigger) { _ in
+            calculateCarbsDots(fullSize: fullSize)
+        }
+    }
+
     private func tempTargetsView(fullSize: CGSize) -> some View {
         ZStack {
             tempTargetsPath
@@ -248,7 +282,7 @@ struct MainChartView: View {
         }
     }
 
-    private func predictions(fullSize: CGSize) -> some View {
+    private func predictionsView(fullSize: CGSize) -> some View {
         Group {
             Path { path in
                 for rect in predictionDots[.iob] ?? [] {
@@ -310,6 +344,19 @@ struct MainChartView: View {
         }
     }
 
+    private func calculateCarbsDots(fullSize: CGSize) {
+        carbsDots = carbs.map { value -> CGRect in
+            let center = timeToInterpolatedPoint(value.createdAt.timeIntervalSince1970, fullSize: fullSize)
+            let size = Config.carbsSize + CGFloat(value.carbs) * Config.carbsScale
+            return CGRect(x: center.x - size / 2, y: center.y - size / 2, width: size, height: size)
+        }
+        carbsPath = Path { path in
+            for rect in carbsDots {
+                path.addEllipse(in: rect)
+            }
+        }
+    }
+
     private func calculatePredictionDots(fullSize: CGSize, type: PredictionType) {
         let values: [Int] = { () -> [Int] in
             switch type {

+ 1 - 0
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -56,6 +56,7 @@ extension Home {
                         maxBasal: $viewModel.maxBasal,
                         basalProfile: $viewModel.basalProfile,
                         tempTargets: $viewModel.tempTargets,
+                        carbs: $viewModel.carbs,
                         units: viewModel.units
                     )