Ivan Valkou 5 سال پیش
والد
کامیت
f20b79c546

+ 19 - 3
FreeAPS.xcodeproj/project.pbxproj

@@ -696,9 +696,8 @@
 			isa = PBXGroup;
 			children = (
 				3811DE2E25C9D49500A708ED /* HomeRootView.swift */,
-				383420D525FFE38C002D46C1 /* LoopView.swift */,
-				38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */,
-				38AAF8702600C1B0004AF583 /* MainChartView.swift */,
+				3833B51E260264AC003021B3 /* Chart */,
+				3833B51F260264B6003021B3 /* Header */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -938,6 +937,23 @@
 			path = APS;
 			sourceTree = "<group>";
 		};
+		3833B51E260264AC003021B3 /* Chart */ = {
+			isa = PBXGroup;
+			children = (
+				38AAF8702600C1B0004AF583 /* MainChartView.swift */,
+			);
+			path = Chart;
+			sourceTree = "<group>";
+		};
+		3833B51F260264B6003021B3 /* Header */ = {
+			isa = PBXGroup;
+			children = (
+				383420D525FFE38C002D46C1 /* LoopView.swift */,
+				38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */,
+			);
+			path = Header;
+			sourceTree = "<group>";
+		};
 		3883582E25EEAFC000E024B2 /* Views */ = {
 			isa = PBXGroup;
 			children = (

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

@@ -8,4 +8,5 @@ protocol HomeProvider: Provider {
     var suggestion: Suggestion? { get }
     func fetchAndLoop()
     func filteredGlucose(hours: Int) -> [BloodGlucose]
+    func pumpHistory(hours: Int) -> [PumpHistoryEvent]
 }

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

@@ -5,6 +5,7 @@ extension Home {
     final class Provider: BaseProvider, HomeProvider {
         @Injected() var apsManager: APSManager!
         @Injected() var glucoseStorage: GlucoseStorage!
+        @Injected() var pumpHistoryStorage: PumpHistoryStorage!
 
         var suggestion: Suggestion? {
             try? storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self)
@@ -19,5 +20,11 @@ extension Home {
                 $0.dateString.addingTimeInterval(hours.hours.timeInterval) > Date()
             }
         }
+
+        func pumpHistory(hours: Int) -> [PumpHistoryEvent] {
+            pumpHistoryStorage.recent().filter {
+                $0.timestamp.addingTimeInterval(hours.hours.timeInterval) > Date()
+            }
+        }
     }
 }

+ 17 - 4
FreeAPS/Sources/Modules/Home/HomeViewModel.swift

@@ -6,24 +6,27 @@ extension Home {
         @Injected() var broadcaster: Broadcaster!
         @Injected() var settingsManager: SettingsManager!
 
-        private(set) var filteredGlucoseHours = 24
+        private(set) var filteredHours = 24
 
         @Published var glucose: [BloodGlucose] = []
         @Published var suggestion: Suggestion?
         @Published var recentGlucose: BloodGlucose?
         @Published var glucoseDelta: Int?
+        @Published var basals: [PumpHistoryEvent] = []
 
         @Published var allowManualTemp = false
         private(set) var units: GlucoseUnits = .mmolL
 
         override func subscribe() {
             setupGlucose()
+            setupBasals()
             suggestion = provider.suggestion
             units = settingsManager.settings.units
             allowManualTemp = !settingsManager.settings.closedLoop
             broadcaster.register(GlucoseObserver.self, observer: self)
             broadcaster.register(SuggestionObserver.self, observer: self)
             broadcaster.register(SettingsObserver.self, observer: self)
+            broadcaster.register(PumpHistoryObserver.self, observer: self)
         }
 
         func addCarbs() {
@@ -51,11 +54,11 @@ extension Home {
         }
 
         func setFilteredGlucoseHours(hours: Int) {
-            filteredGlucoseHours = hours
+            filteredHours = hours
         }
 
         private func setupGlucose() {
-            glucose = provider.filteredGlucose(hours: filteredGlucoseHours)
+            glucose = provider.filteredGlucose(hours: filteredHours)
             recentGlucose = glucose.last
             if glucose.count >= 2 {
                 glucoseDelta = (recentGlucose?.glucose ?? 0) - (glucose[glucose.count - 2].glucose ?? 0)
@@ -63,10 +66,16 @@ extension Home {
                 glucoseDelta = nil
             }
         }
+
+        private func setupBasals() {
+            basals = provider.pumpHistory(hours: filteredHours).filter {
+                $0.type == .tempBasal || $0.type == .tempBasalDuration
+            }
+        }
     }
 }
 
-extension Home.ViewModel: GlucoseObserver, SuggestionObserver, SettingsObserver {
+extension Home.ViewModel: GlucoseObserver, SuggestionObserver, SettingsObserver, PumpHistoryObserver {
     func glucoseDidUpdate(_: [BloodGlucose]) {
         setupGlucose()
     }
@@ -78,4 +87,8 @@ extension Home.ViewModel: GlucoseObserver, SuggestionObserver, SettingsObserver
     func settingsDidChange(_ settings: FreeAPSSettings) {
         allowManualTemp = !settings.closedLoop
     }
+
+    func pumpHistoryDidUpdate(_: [PumpHistoryEvent]) {
+        setupBasals()
+    }
 }

+ 68 - 54
FreeAPS/Sources/Modules/Home/View/MainChartView.swift

@@ -15,12 +15,13 @@ struct MainChartView: View {
         static let basalHeight: CGFloat = 60
         static let topYPadding: CGFloat = 20
         static let bottomYPadding: CGFloat = 50
-
+        static let maxGlucose = 450
         static let yLinesCount = 5
     }
 
     @Binding var glucose: [BloodGlucose]
     @Binding var suggestion: Suggestion?
+    @Binding var basals: [PumpHistoryEvent]
     @Binding var hours: Int
     let units: GlucoseUnits
 
@@ -41,9 +42,12 @@ struct MainChartView: View {
         return formatter
     }
 
+    // MARK: - Views
+
     var body: some View {
         GeometryReader { geo in
             ZStack(alignment: .leading) {
+                // Y grid
                 Path { path in
                     let range = glucoseYRange(fullSize: geo.size)
                     let step = (range.maxY - range.minY) / CGFloat(Config.yLinesCount)
@@ -71,6 +75,7 @@ struct MainChartView: View {
                         }
                     }
                 }
+                // Y glucose labels
                 ForEach(0 ..< Config.yLinesCount) { line -> AnyView in
                     let range = glucoseYRange(fullSize: geo.size)
                     let yStep = (range.maxY - range.minY) / CGFloat(Config.yLinesCount)
@@ -99,6 +104,7 @@ struct MainChartView: View {
         Group {
             VStack {
                 ZStack {
+                    // X grid
                     Path { path in
                         for hour in 0 ..< hours + hours {
                             let x = firstHourPosition(viewWidth: fullSize.width) +
@@ -113,6 +119,7 @@ struct MainChartView: View {
                     predictions(fullSize: fullSize).id("End")
                 }
                 ZStack {
+                    // X time labels
                     ForEach(0 ..< hours + hours) { hour in
                         Text(dateDormatter.string(from: firstHourDate().addingTimeInterval(hour.hours.timeInterval)))
                             .font(.caption)
@@ -145,35 +152,6 @@ struct MainChartView: View {
         }
     }
 
-    private func calculateGlucoseDots(fullSize: CGSize) {
-        glucoseDots = glucose.concurrentMap { value -> CGRect in
-            let position = glucoseToCoordinate(value, fullSize: fullSize)
-            return CGRect(x: position.x - 2, y: position.y - 2, width: 4, height: 4)
-        }
-    }
-
-    private func calculatePredictionDots(fullSize: CGSize, type: PredictionType) {
-        let values: [Int] = { () -> [Int] in
-            switch type {
-            case .iob:
-                return suggestion?.predictions?.iob ?? []
-            case .cob:
-                return suggestion?.predictions?.cob ?? []
-            case .zt:
-                return suggestion?.predictions?.zt ?? []
-            case .uam:
-                return suggestion?.predictions?.uam ?? []
-            }
-        }()
-
-        var index = 0
-        predictionDots[type] = values.map { value -> CGRect in
-            let position = predictionToCoordinate(value, fullSize: fullSize, index: index)
-            index += 1
-            return CGRect(x: position.x - 2, y: position.y - 2, width: 4, height: 4)
-        }
-    }
-
     private func predictions(fullSize: CGSize) -> some View {
         Group {
             Path { path in
@@ -214,6 +192,39 @@ struct MainChartView: View {
         }
     }
 
+    // MARK: - Calculations
+
+    private func calculateGlucoseDots(fullSize: CGSize) {
+        glucoseDots = glucose.concurrentMap { value -> CGRect in
+            let position = glucoseToCoordinate(value, fullSize: fullSize)
+            return CGRect(x: position.x - 2, y: position.y - 2, width: 4, height: 4)
+        }
+    }
+
+    private func calculatePredictionDots(fullSize: CGSize, type: PredictionType) {
+        let values: [Int] = { () -> [Int] in
+            switch type {
+            case .iob:
+                return suggestion?.predictions?.iob ?? []
+            case .cob:
+                return suggestion?.predictions?.cob ?? []
+            case .zt:
+                return suggestion?.predictions?.zt ?? []
+            case .uam:
+                return suggestion?.predictions?.uam ?? []
+            }
+        }()
+
+        var index = 0
+        predictionDots[type] = values.map { value -> CGRect in
+            let position = predictionToCoordinate(value, fullSize: fullSize, index: index)
+            index += 1
+            return CGRect(x: position.x - 2, y: position.y - 2, width: 4, height: 4)
+        }
+    }
+
+    private func calculateBasalPoints(fullSize _: CGSize) {}
+
     private func fullGlucoseWidth(viewWidth: CGFloat) -> CGFloat {
         viewWidth * CGFloat(hours) / CGFloat(Config.screenHours)
     }
@@ -251,7 +262,7 @@ struct MainChartView: View {
             suggestion?.predictions?.uam ?? []
         ]
         .flatMap { $0 }
-        .max() ?? 450
+        .max() ?? Config.maxGlucose
     }
 
     private func minPredValue() -> Int {
@@ -266,46 +277,49 @@ struct MainChartView: View {
     }
 
     private func glucoseToCoordinate(_ glucoseEntry: BloodGlucose, fullSize: CGSize) -> CGPoint {
-        guard let first = glucose.first else {
-            return .zero
-        }
-        let topYPaddint = Config.topYPadding + Config.basalHeight
-        let bottomYPadding = Config.bottomYPadding
-        let maxValue = max(glucose.compactMap(\.glucose).max() ?? 450, maxPredValue())
-        let minValue = min(glucose.compactMap(\.glucose).min() ?? 0, minPredValue())
-        let stepYFraction = (fullSize.height - topYPaddint - bottomYPadding) / CGFloat(maxValue - minValue)
-        let yOffset = CGFloat(minValue) * stepYFraction
-        let xOffset = -first.dateString.timeIntervalSince1970
-        let stepXFraction = fullGlucoseWidth(viewWidth: fullSize.width) / CGFloat(hours.hours.timeInterval)
-        let x = CGFloat(glucoseEntry.dateString.timeIntervalSince1970 + xOffset) * stepXFraction
-        let y = fullSize.height - CGFloat(glucoseEntry.glucose ?? 0) * stepYFraction + yOffset - bottomYPadding
+        let x = timeToXCoordinate(glucoseEntry.dateString.timeIntervalSince1970, fullSize: fullSize)
+        let y = glucoseToYCoordinate(glucoseEntry.glucose ?? 0, fullSize: fullSize)
 
         return CGPoint(x: x, y: y)
     }
 
     private func predictionToCoordinate(_ pred: Int, fullSize: CGSize, index: Int) -> CGPoint {
-        guard let first = glucose.first, let deliveredAt = suggestion?.deliverAt else {
+        guard let deliveredAt = suggestion?.deliverAt else {
             return .zero
         }
+
+        let predTime = deliveredAt.timeIntervalSince1970 + TimeInterval(index) * 5.minutes.timeInterval
+        let x = timeToXCoordinate(predTime, fullSize: fullSize)
+        let y = glucoseToYCoordinate(pred, fullSize: fullSize)
+
+        return CGPoint(x: x, y: y)
+    }
+
+    private func timeToXCoordinate(_ time: TimeInterval, fullSize: CGSize) -> CGFloat {
+        let xOffset = -(
+            glucose.first?.dateString.timeIntervalSince1970 ?? Date()
+                .addingTimeInterval(-1.days.timeInterval).timeIntervalSince1970
+        )
+        let stepXFraction = fullGlucoseWidth(viewWidth: fullSize.width) / CGFloat(hours.hours.timeInterval)
+        let x = CGFloat(time + xOffset) * stepXFraction
+        return x
+    }
+
+    private func glucoseToYCoordinate(_ glucoseValue: Int, fullSize: CGSize) -> CGFloat {
         let topYPaddint = Config.topYPadding + Config.basalHeight
         let bottomYPadding = Config.bottomYPadding
-        let maxValue = max(glucose.compactMap(\.glucose).max() ?? 450, maxPredValue())
+        let maxValue = max(glucose.compactMap(\.glucose).max() ?? Config.maxGlucose, maxPredValue())
         let minValue = min(glucose.compactMap(\.glucose).min() ?? 0, minPredValue())
         let stepYFraction = (fullSize.height - topYPaddint - bottomYPadding) / CGFloat(maxValue - minValue)
         let yOffset = CGFloat(minValue) * stepYFraction
-        let xOffset = -first.dateString.timeIntervalSince1970
-        let stepXFraction = fullGlucoseWidth(viewWidth: fullSize.width) / CGFloat(hours.hours.timeInterval)
-        let predTime = deliveredAt.timeIntervalSince1970 + TimeInterval(index) * 5.minutes.timeInterval
-        let x = CGFloat(predTime + xOffset) * stepXFraction
-        let y = fullSize.height - CGFloat(pred) * stepYFraction + yOffset - bottomYPadding
-
-        return CGPoint(x: x, y: y)
+        let y = fullSize.height - CGFloat(glucoseValue) * stepYFraction + yOffset - bottomYPadding
+        return y
     }
 
     private func glucoseYRange(fullSize: CGSize) -> (minValue: Int, minY: CGFloat, maxValue: Int, maxY: CGFloat) {
         let topYPaddint = Config.topYPadding + Config.basalHeight
         let bottomYPadding = Config.bottomYPadding
-        let maxValue = max(glucose.compactMap(\.glucose).max() ?? 450, maxPredValue())
+        let maxValue = max(glucose.compactMap(\.glucose).max() ?? Config.maxGlucose, maxPredValue())
         let minValue = min(glucose.compactMap(\.glucose).min() ?? 0, minPredValue())
         let stepYFraction = (fullSize.height - topYPaddint - bottomYPadding) / CGFloat(maxValue - minValue)
         let yOffset = CGFloat(minValue) * stepYFraction

FreeAPS/Sources/Modules/Home/View/CurrentGlucoseView.swift → FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift


FreeAPS/Sources/Modules/Home/View/LoopView.swift → FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift


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

@@ -50,7 +50,8 @@ extension Home {
                     MainChartView(
                         glucose: $viewModel.glucose,
                         suggestion: $viewModel.suggestion,
-                        hours: .constant(24),
+                        basals: $viewModel.basals,
+                        hours: .constant(viewModel.filteredHours),
                         units: viewModel.units
                     )