瀏覽代碼

refactoring

polscm32 1 年之前
父節點
當前提交
785197d7da

+ 14 - 7
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -806,16 +806,23 @@ extension Bolus.StateModel {
             return
         }
 
-        minCount = min(12, nonEmptyArrays.map(\.count).min() ?? 0)
+        minCount = max(12, nonEmptyArrays.map(\.count).min() ?? 0)
         guard minCount > 0 else { return }
 
-        minForecast = (0 ..< minCount).map { index in
-            nonEmptyArrays.compactMap { $0.indices.contains(index) ? $0[index] : nil }.min() ?? 0
-        }
+        let (minResult, maxResult) = await Task.detached {
+            let minForecast = (0 ..< self.minCount).map { index in
+                nonEmptyArrays.compactMap { $0.indices.contains(index) ? $0[index] : nil }.min() ?? 0
+            }
 
-        maxForecast = (0 ..< minCount).map { index in
-            nonEmptyArrays.compactMap { $0.indices.contains(index) ? $0[index] : nil }.max() ?? 0
-        }
+            let maxForecast = (0 ..< self.minCount).map { index in
+                nonEmptyArrays.compactMap { $0.indices.contains(index) ? $0[index] : nil }.max() ?? 0
+            }
+
+            return (minForecast, maxForecast)
+        }.value
+
+        minForecast = minResult
+        maxForecast = maxResult
     }
 }
 

+ 8 - 20
FreeAPS/Sources/Modules/Bolus/View/ForeCastChart.swift

@@ -13,7 +13,10 @@ struct ForeCastChart: View {
     private var endMarker: Date {
         state
             .displayForecastsAsLines ? Date(timeIntervalSinceNow: TimeInterval(hours: 3)) :
-            Date(timeIntervalSinceNow: TimeInterval(2 * 5 * state.minCount * 60)) // min is 2h -> (2*1h = 2*(5*12*60))
+            Date(timeIntervalSinceNow: TimeInterval(
+                Int(1.5) * 5 * state
+                    .minCount * 60
+            )) // min is 1.5h -> (1.5*1h = 1.5*(5*12*60))
     }
 
     var body: some View {
@@ -119,10 +122,10 @@ struct ForeCastChart: View {
 
         // Prepare the prediction data with only the first 36 values, i.e. 3 hours in the future
         let predictionData = [
-            ("IOB", predictions?.iob?.prefix(36)),
-            ("ZT", predictions?.zt?.prefix(36)),
-            ("COB", predictions?.cob?.prefix(36)),
-            ("UAM", predictions?.uam?.prefix(36))
+            ("iob", predictions?.iob?.prefix(36)),
+            ("zt", predictions?.zt?.prefix(36)),
+            ("cob", predictions?.cob?.prefix(36)),
+            ("uam", predictions?.uam?.prefix(36))
         ]
 
         return ForEach(predictionData, id: \.0) { name, values in
@@ -165,18 +168,3 @@ struct ForeCastChart: View {
         }
     }
 }
-
-extension Backport {
-    @ViewBuilder func chartForegroundStyleScale(state: Bolus.StateModel) -> some View {
-        if state.displayForecastsAsLines {
-            content.chartForegroundStyleScale([
-                "IOB": .blue,
-                "UAM": Color.uam,
-                "ZT": Color.zt,
-                "COB": .orange
-            ])
-        } else {
-            content
-        }
-    }
-}

+ 24 - 9
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -85,6 +85,7 @@ extension Home {
         @Published var minForecast: [Int] = []
         @Published var maxForecast: [Int] = []
         @Published var minCount: Int = 12 // count of Forecasts drawn in 5 min distances, i.e. 12 means a min of 1 hour
+        @Published var displayForecastsAsLines: Bool = false
 
         let context = CoreDataStack.shared.newTaskContext()
         let viewContext = CoreDataStack.shared.persistentContainer.viewContext
@@ -134,6 +135,8 @@ extension Home {
             tins = settingsManager.settings.tins
             cgmAvailable = fetchGlucoseManager.cgmGlucoseSourceType != CGMType.none
 
+            displayForecastsAsLines = settingsManager.settings.displayForecastsAsLines
+
             broadcaster.register(GlucoseObserver.self, observer: self)
             broadcaster.register(DeterminationObserver.self, observer: self)
             broadcaster.register(SettingsObserver.self, observer: self)
@@ -466,6 +469,7 @@ extension Home.StateModel:
         displayXgridLines = settingsManager.settings.xGridLines
         displayYgridLines = settingsManager.settings.yGridLines
         thresholdLines = settingsManager.settings.rulerMarks
+        displayForecastsAsLines = settingsManager.settings.displayForecastsAsLines
         tins = settingsManager.settings.tins
         cgmAvailable = (fetchGlucoseManager.cgmGlucoseSourceType != CGMType.none)
         displayPumpStatusHighlightMessage()
@@ -964,7 +968,7 @@ extension Home.StateModel {
                 // Fetch the forecast object
                 forecast = try context.existingObject(with: data.forecastID) as? Forecast
 
-                // Fetch the forecast values
+                // Fetch the first 3h of forecast values
                 for forecastValueID in data.forecastValueIDs.prefix(36) {
                     if let forecastValue = try context.existingObject(with: forecastValueID) as? ForecastValue {
                         forecastValues.append(forecastValue)
@@ -1014,16 +1018,27 @@ extension Home.StateModel {
             return
         }
 
-        minCount = min(12, allForecastValues.map(\.count).min() ?? 0)
+        minCount = max(12, allForecastValues.map(\.count).min() ?? 0)
         guard minCount > 0 else { return }
 
-        // Calculate min and max forecast values
-        minForecast = (0 ..< minCount).map { index in
-            allForecastValues.compactMap { $0.indices.contains(index) ? Int($0[index].value) : nil }.min() ?? 0
-        }
+        // Copy allForecastValues to a local constant for thread safety
+        let localAllForecastValues = allForecastValues
 
-        maxForecast = (0 ..< minCount).map { index in
-            allForecastValues.compactMap { $0.indices.contains(index) ? Int($0[index].value) : nil }.max() ?? 0
-        }
+        // Calculate min and max forecast values in a background task
+        let (minResult, maxResult) = await Task.detached {
+            let minForecast = (0 ..< self.minCount).map { index in
+                localAllForecastValues.compactMap { $0.indices.contains(index) ? Int($0[index].value) : nil }.min() ?? 0
+            }
+
+            let maxForecast = (0 ..< self.minCount).map { index in
+                localAllForecastValues.compactMap { $0.indices.contains(index) ? Int($0[index].value) : nil }.max() ?? 0
+            }
+
+            return (minForecast, maxForecast)
+        }.value
+
+        // Update the properties on the main thread
+        minForecast = minResult
+        maxForecast = maxResult
     }
 }

+ 53 - 11
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -59,7 +59,7 @@ struct MainChartView: View {
     @State private var chartTempTargets: [ChartTempTarget] = []
     @State private var count: Decimal = 1
     @State private var startMarker =
-        Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970 - 60 * 60 * 24)) // 24 hours
+        Date(timeIntervalSinceNow: TimeInterval(hours: -24))
     @State private var minValue: Decimal = 45
     @State private var maxValue: Decimal = 270
     @State private var selection: Date? = nil
@@ -76,7 +76,12 @@ struct MainChartView: View {
     @Environment(\.calendar) var calendar
 
     private var endMarker: Date {
-        Date(timeIntervalSinceNow: TimeInterval(2 * 5 * state.minCount * 60)) // min is 2h -> (2*1h = 2*(5*12*60))
+        state
+            .displayForecastsAsLines ? Date(timeIntervalSinceNow: TimeInterval(hours: 3)) :
+            Date(timeIntervalSinceNow: TimeInterval(
+                Int(1.5) * 5 * state
+                    .minCount * 60
+            )) // min is 1.5h -> (1.5*1h = 1.5*(5*12*60))
     }
 
     private var bolusFormatter: NumberFormatter {
@@ -199,6 +204,29 @@ extension Backport {
             content
         }
     }
+
+    @ViewBuilder func chartForegroundStyleScale(state: any StateModel) -> some View {
+        if (state as? Bolus.StateModel)?.displayForecastsAsLines == true ||
+            (state as? Home.StateModel)?.displayForecastsAsLines == true
+        {
+            let modifiedContent = content
+                .chartForegroundStyleScale([
+                    "iob": .blue,
+                    "uam": Color.uam,
+                    "zt": Color.zt,
+                    "cob": .orange
+                ])
+
+            if state is Home.StateModel {
+                modifiedContent
+                    .chartLegend(.hidden)
+            } else {
+                modifiedContent
+            }
+        } else {
+            content
+        }
+    }
 }
 
 extension MainChartView {
@@ -262,11 +290,16 @@ extension MainChartView {
                 drawTempTargets()
                 drawActiveOverrides()
                 drawOverrideRunStored()
-                drawForecasts()
                 drawGlucose(dummy: false)
                 drawManualGlucose()
                 drawCarbs()
 
+                if state.displayForecastsAsLines {
+                    drawForecastsLines()
+                } else {
+                    drawForecastsCone()
+                }
+
                 /// show glucose value when hovering over it
                 if #available(iOS 17, *) {
                     if let selectedGlucose {
@@ -316,13 +349,7 @@ extension MainChartView {
             .chartYAxis(.hidden)
             .backport.chartXSelection(value: $selection)
             .chartYScale(domain: minValue ... maxValue)
-            .chartForegroundStyleScale([
-                "zt": Color.zt,
-                "uam": Color.uam,
-                "cob": .orange,
-                "iob": .blue
-            ])
-            .chartLegend(.hidden)
+            .backport.chartForegroundStyleScale(state: state)
         }
     }
 
@@ -535,7 +562,7 @@ extension MainChartView {
         return currentTime.addingTimeInterval(timeInterval)
     }
 
-    private func drawForecasts() -> some ChartContent {
+    private func drawForecastsCone() -> some ChartContent {
         // Draw AreaMark for the forecast bounds
         ForEach(0 ..< max(state.minForecast.count, state.maxForecast.count), id: \.self) { index in
             if index < state.minForecast.count, index < state.maxForecast.count {
@@ -555,6 +582,21 @@ extension MainChartView {
         }
     }
 
+    private func drawForecastsLines() -> some ChartContent {
+        ForEach(state.preprocessedData, id: \.id) { tuple in
+            let forecastValue = tuple.forecastValue
+            let forecast = tuple.forecast
+            let valueAsDecimal = Decimal(forecastValue.value)
+            let displayValue = units == .mmolL ? valueAsDecimal.asMmolL : valueAsDecimal
+
+            LineMark(
+                x: .value("Time", timeForIndex(forecastValue.index)),
+                y: .value("Value", displayValue)
+            )
+            .foregroundStyle(by: .value("Predictions", forecast.type ?? ""))
+        }
+    }
+
     private func drawCurrentTimeMarker() -> some ChartContent {
         RuleMark(
             x: .value(