polscm32 aka Marvout 1 год назад
Родитель
Сommit
9fdae8a533
1 измененных файлов с 86 добавлено и 0 удалено
  1. 86 0
      FreeAPS/Sources/Modules/Bolus/View/ForecastChart.swift

+ 86 - 0
FreeAPS/Sources/Modules/Bolus/View/ForecastChart.swift

@@ -9,6 +9,8 @@ struct ForecastChart: View {
 
     @State private var startMarker = Date(timeIntervalSinceNow: -4 * 60 * 60)
 
+    @State var selection: Date? = nil
+
     private var endMarker: Date {
         state
             .forecastDisplayType == .lines ? Date(timeIntervalSinceNow: TimeInterval(hours: 3)) :
@@ -32,6 +34,12 @@ struct ForecastChart: View {
         return formatter
     }
 
+    private var selectedGlucose: GlucoseStored? {
+        guard let selection = selection else { return nil }
+        let range = selection.addingTimeInterval(-150) ... selection.addingTimeInterval(150)
+        return state.glucoseFromPersistence.first { $0.date.map(range.contains) ?? false }
+    }
+
     var body: some View {
         VStack {
             forecastChartLabels
@@ -114,7 +122,43 @@ struct ForecastChart: View {
             } else {
                 drawForecastsCone()
             }
+
+            if let selectedGlucose {
+                RuleMark(x: .value("Selection", selectedGlucose.date ?? Date.now, unit: .minute))
+                    .foregroundStyle(Color.tabBar)
+                    .lineStyle(.init(lineWidth: 2))
+                    .annotation(
+                        position: .top,
+                        overflowResolution: .init(x: .fit(to: .chart), y: .disabled)
+                    ) {
+                        selectionPopover
+                    }
+
+                PointMark(
+                    x: .value("Time", selectedGlucose.date ?? Date.now, unit: .minute),
+                    y: .value("Value", selectedGlucose.glucose)
+                )
+                .zIndex(-1)
+                .symbolSize(CGSize(width: 15, height: 15))
+                .foregroundStyle(
+                    Decimal(selectedGlucose.glucose) > state.highGlucose ? Color.orange
+                        .opacity(0.8) :
+                        (
+                            Decimal(selectedGlucose.glucose) < state.lowGlucose ? Color.red.opacity(0.8) : Color.green
+                                .opacity(0.8)
+                        )
+                )
+
+                PointMark(
+                    x: .value("Time", selectedGlucose.date ?? Date.now, unit: .minute),
+                    y: .value("Value", selectedGlucose.glucose)
+                )
+                .zIndex(-1)
+                .symbolSize(CGSize(width: 6, height: 6))
+                .foregroundStyle(Color.primary)
+            }
         }
+        .chartXSelection(value: $selection)
         .chartXAxis { forecastChartXAxis }
         .chartXScale(domain: startMarker ... endMarker)
         .chartYAxis { forecastChartYAxis }
@@ -122,6 +166,48 @@ struct ForecastChart: View {
         .backport.chartForegroundStyleScale(state: state)
     }
 
+    @ViewBuilder var selectionPopover: some View {
+        if let sgv = selectedGlucose?.glucose {
+            VStack(alignment: .leading) {
+                HStack {
+                    Image(systemName: "clock")
+                    Text(selectedGlucose?.date?.formatted(.dateTime.hour().minute(.twoDigits)) ?? "")
+                        .font(.footnote).bold()
+                }.font(.footnote).padding(.bottom, 5)
+
+                // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
+                let hardCodedLow = Decimal(55)
+                let hardCodedHigh = Decimal(220)
+                let isDynamicColorScheme = state.glucoseColorScheme == .dynamicColor
+
+                let glucoseColor = FreeAPS.getDynamicGlucoseColor(
+                    glucoseValue: Decimal(sgv),
+                    highGlucoseColorValue: isDynamicColorScheme ? hardCodedHigh : state.highGlucose,
+                    lowGlucoseColorValue: isDynamicColorScheme ? hardCodedLow : state.lowGlucose,
+                    targetGlucose: state.currentBGTarget,
+                    glucoseColorScheme: state.glucoseColorScheme
+                )
+                HStack {
+                    Text(state.units == .mgdL ? Decimal(sgv).description : Decimal(sgv).formattedAsMmolL)
+                        .bold()
+                        + Text(" \(state.units.rawValue)")
+                }.foregroundStyle(
+                    Color(glucoseColor)
+                ).font(.footnote)
+            }
+            .padding(7)
+            .background {
+                RoundedRectangle(cornerRadius: 4)
+                    .fill(Color.chart.opacity(0.85))
+                    .shadow(color: Color.secondary, radius: 2)
+                    .overlay(
+                        RoundedRectangle(cornerRadius: 4)
+                            .stroke(Color.secondary, lineWidth: 2)
+                    )
+            }
+        }
+    }
+
     private func drawGlucose() -> some ChartContent {
         ForEach(state.glucoseFromPersistence) { item in
             let glucoseToDisplay = state.units == .mgdL ? Decimal(item.glucose) : Decimal(item.glucose).asMmolL