소스 검색

Use same selection interval for cob/iob as for glucose; Refactoring; use 'syringe.fill' instead of 'drop.fill' for Bolus shortcut

polscm32 aka Marvout 1 년 전
부모
커밋
73cd0bc038

+ 21 - 9
FreeAPS.xcodeproj/project.pbxproj

@@ -344,6 +344,7 @@
 		BDC2EA472C3045AD00E5BBD0 /* Override.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC2EA462C3045AD00E5BBD0 /* Override.swift */; };
 		BDCAF2382C639F35002DC907 /* SettingItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCAF2372C639F35002DC907 /* SettingItems.swift */; };
 		BDCD47AF2C1F3F1700F8BCD5 /* OverrideStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCD47AE2C1F3F1700F8BCD5 /* OverrideStored+helper.swift */; };
+		BDDAF9EF2D00554500B34E7A /* SelectionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDDAF9EE2D00553E00B34E7A /* SelectionPopoverView.swift */; };
 		BDF34EBE2C0A31D100D51995 /* CustomNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34EBD2C0A31D000D51995 /* CustomNotification.swift */; };
 		BDF34F832C10C5B600D51995 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F822C10C5B600D51995 /* DataManager.swift */; };
 		BDF34F852C10C62E00D51995 /* GlucoseData.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F842C10C62E00D51995 /* GlucoseData.swift */; };
@@ -1031,6 +1032,7 @@
 		BDC2EA462C3045AD00E5BBD0 /* Override.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Override.swift; sourceTree = "<group>"; };
 		BDCAF2372C639F35002DC907 /* SettingItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingItems.swift; sourceTree = "<group>"; };
 		BDCD47AE2C1F3F1700F8BCD5 /* OverrideStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideStored+helper.swift"; sourceTree = "<group>"; };
+		BDDAF9EE2D00553E00B34E7A /* SelectionPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionPopoverView.swift; sourceTree = "<group>"; };
 		BDF34EBD2C0A31D000D51995 /* CustomNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNotification.swift; sourceTree = "<group>"; };
 		BDF34F822C10C5B600D51995 /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = "<group>"; };
 		BDF34F842C10C62E00D51995 /* GlucoseData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseData.swift; sourceTree = "<group>"; };
@@ -1852,15 +1854,7 @@
 			isa = PBXGroup;
 			children = (
 				BD3CC0712B0B89D50013189E /* MainChartView.swift */,
-				582DF9742C8CDB92001F516D /* GlucoseChartView.swift */,
-				582DF9762C8CDBE7001F516D /* InsulinView.swift */,
-				582DF97A2C8CE209001F516D /* CarbView.swift */,
-				58D08B212C8DAA8E00AA37D3 /* OverrideView.swift */,
-				58D08B2F2C8DEA7500AA37D3 /* ForecastView.swift */,
-				58D08B312C8DF88900AA37D3 /* DummyCharts.swift */,
-				58D08B332C8DF9A700AA37D3 /* CobIobChart.swift */,
-				58D08B372C8DFB6000AA37D3 /* BasalChart.swift */,
-				58D08B392C8DFECD00AA37D3 /* TempTargets.swift */,
+				BDDAF9F12D0055CC00B34E7A /* ChartElements */,
 			);
 			path = Chart;
 			sourceTree = "<group>";
@@ -2490,6 +2484,23 @@
 			path = View;
 			sourceTree = "<group>";
 		};
+		BDDAF9F12D0055CC00B34E7A /* ChartElements */ = {
+			isa = PBXGroup;
+			children = (
+				BDDAF9EE2D00553E00B34E7A /* SelectionPopoverView.swift */,
+				582DF9742C8CDB92001F516D /* GlucoseChartView.swift */,
+				582DF9762C8CDBE7001F516D /* InsulinView.swift */,
+				582DF97A2C8CE209001F516D /* CarbView.swift */,
+				58D08B212C8DAA8E00AA37D3 /* OverrideView.swift */,
+				58D08B2F2C8DEA7500AA37D3 /* ForecastView.swift */,
+				58D08B312C8DF88900AA37D3 /* DummyCharts.swift */,
+				58D08B332C8DF9A700AA37D3 /* CobIobChart.swift */,
+				58D08B372C8DFB6000AA37D3 /* BasalChart.swift */,
+				58D08B392C8DFECD00AA37D3 /* TempTargets.swift */,
+			);
+			path = ChartElements;
+			sourceTree = "<group>";
+		};
 		BDF34F882C10C65E00D51995 /* Data */ = {
 			isa = PBXGroup;
 			children = (
@@ -3783,6 +3794,7 @@
 				DDE1795A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift in Sources */,
 				DDE1795B2C910127003CDDB7 /* CarbEntryStored+CoreDataProperties.swift in Sources */,
 				DDE1795E2C910127003CDDB7 /* PumpEventStored+CoreDataClass.swift in Sources */,
+				BDDAF9EF2D00554500B34E7A /* SelectionPopoverView.swift in Sources */,
 				DDE1795F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift in Sources */,
 				DDE179602C910127003CDDB7 /* StatsData+CoreDataClass.swift in Sources */,
 				DDE179612C910127003CDDB7 /* StatsData+CoreDataProperties.swift in Sources */,

+ 6 - 0
FreeAPS/Sources/Helpers/Decimal+Extensions.swift

@@ -13,6 +13,12 @@ extension Int {
     }
 }
 
+extension Int16 {
+    var minutes: TimeInterval {
+        TimeInterval(self) * 60
+    }
+}
+
 extension CGFloat {
     init(_ decimal: Decimal) {
         self.init(Double(decimal))

+ 9 - 0
FreeAPS/Sources/Helpers/Formatters.swift

@@ -70,6 +70,15 @@ extension Formatter {
 
         return formatter
     }
+
+    static let bolusFormatter: NumberFormatter = {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.minimumIntegerDigits = 0
+        formatter.maximumFractionDigits = 2
+        formatter.decimalSeparator = "."
+        return formatter
+    }()
 }
 
 extension JSONDecoder.DateDecodingStrategy {

+ 118 - 16
FreeAPS/Sources/Helpers/MainChartHelper.swift

@@ -1,5 +1,7 @@
+import Charts
 import CoreData
 import Foundation
+import SwiftUICore
 
 enum MainChartHelper {
     // Calculates the glucose value thats the nearest to parameter 'time'
@@ -47,22 +49,6 @@ enum MainChartHelper {
         static let minGlucose = 45
     }
 
-    static var bolusFormatter: NumberFormatter {
-        let formatter = NumberFormatter()
-        formatter.numberStyle = .decimal
-        formatter.minimumIntegerDigits = 0
-        formatter.maximumFractionDigits = 2
-        formatter.decimalSeparator = "."
-        return formatter
-    }
-
-    static var carbsFormatter: NumberFormatter {
-        let formatter = NumberFormatter()
-        formatter.numberStyle = .decimal
-        formatter.maximumFractionDigits = 0
-        return formatter
-    }
-
     static func bolusOffset(units: GlucoseUnits) -> Decimal {
         units == .mgdL ? 30 : 1.66
     }
@@ -105,3 +91,119 @@ enum MainChartHelper {
         return nil
     }
 }
+
+// MARK: - Rule Marks and Charts configurations
+
+extension MainChartView {
+    func drawCurrentTimeMarker() -> some ChartContent {
+        RuleMark(
+            x: .value(
+                "",
+                Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970)),
+                unit: .second
+            )
+        ).lineStyle(.init(lineWidth: 2, dash: [3])).foregroundStyle(Color(.systemGray2))
+    }
+
+    func drawStartRuleMark() -> some ChartContent {
+        RuleMark(
+            x: .value(
+                "",
+                startMarker,
+                unit: .second
+            )
+        ).foregroundStyle(Color.clear)
+    }
+
+    func drawEndRuleMark() -> some ChartContent {
+        RuleMark(
+            x: .value(
+                "",
+                endMarker,
+                unit: .second
+            )
+        ).foregroundStyle(Color.clear)
+    }
+
+    func basalChartPlotStyle(_ plotContent: ChartPlotContent) -> some View {
+        plotContent
+            .rotationEffect(.degrees(180))
+            .scaleEffect(x: -1, y: 1)
+    }
+
+    var mainChartXAxis: some AxisContent {
+        AxisMarks(values: .stride(by: .hour, count: screenHours > 6 ? (screenHours > 12 ? 4 : 2) : 1)) { _ in
+            if displayXgridLines {
+                AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+            } else {
+                AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+            }
+        }
+    }
+
+    var basalChartXAxis: some AxisContent {
+        AxisMarks(values: .stride(by: .hour, count: screenHours > 6 ? (screenHours > 12 ? 4 : 2) : 1)) { _ in
+            if displayXgridLines {
+                AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+            } else {
+                AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+            }
+            AxisValueLabel(format: .dateTime.hour(.defaultDigits(amPM: .narrow)), anchor: .top)
+                .font(.footnote).foregroundStyle(Color.primary)
+        }
+    }
+
+    var mainChartYAxis: some AxisContent {
+        AxisMarks(position: .trailing) { value in
+
+            if displayYgridLines {
+                AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+            } else {
+                AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+            }
+
+            if let glucoseValue = value.as(Double.self), glucoseValue > 0 {
+                /// fix offset between the two charts...
+                if units == .mmolL {
+                    AxisTick(length: 7, stroke: .init(lineWidth: 7)).foregroundStyle(Color.clear)
+                }
+                AxisValueLabel().font(.footnote).foregroundStyle(Color.primary)
+            }
+        }
+    }
+
+    var cobIobChartYAxis: some AxisContent {
+        AxisMarks(position: .trailing) { _ in
+            if displayYgridLines {
+                AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+            } else {
+                AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+            }
+        }
+    }
+}
+
+// MARK: - Calculations and formatting
+
+extension MainChartView {
+    func fullWidth(viewWidth: CGFloat) -> CGFloat {
+        viewWidth * CGFloat(hours) / CGFloat(min(max(screenHours, 2), 24))
+    }
+
+    // Update start and  end marker to fix scroll update problem with x axis
+    func updateStartEndMarkers() {
+        startMarker = Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970 - 86400))
+
+        let threeHourSinceNow = Date(timeIntervalSinceNow: TimeInterval(hours: 3))
+
+        // min is 1.5h -> (1.5*1h = 1.5*(5*12*60))
+        let dynamicFutureDateForCone = Date(timeIntervalSinceNow: TimeInterval(
+            Int(1.5) * 5 * state
+                .minCount * 60
+        ))
+
+        endMarker = state
+            .forecastDisplayType == .lines ? threeHourSinceNow : dynamicFutureDateForCone <= threeHourSinceNow ?
+            dynamicFutureDateForCone.addingTimeInterval(TimeInterval(minutes: 30)) : threeHourSinceNow
+    }
+}

FreeAPS/Sources/Modules/Home/View/Chart/BasalChart.swift → FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/BasalChart.swift


+ 1 - 1
FreeAPS/Sources/Modules/Home/View/Chart/CarbView.swift

@@ -39,7 +39,7 @@ struct CarbView: ChartContent {
                         .rotationEffect(.degrees(180))
                 }
                 .annotation(position: .bottom) {
-                    Text(MainChartHelper.carbsFormatter.string(from: carbAmount as NSNumber)!).font(.caption2)
+                    Text(Formatter.integerFormatter.string(from: carbAmount as NSNumber)!).font(.caption2)
                         .foregroundStyle(Color.primary)
                 }
             }

+ 1 - 1
FreeAPS/Sources/Modules/Home/View/Chart/CobIobChart.swift

@@ -47,7 +47,7 @@ extension MainChartView {
         .frame(minHeight: geo.size.height * 0.12)
         .frame(width: fullWidth(viewWidth: screenSize.width))
         .chartXScale(domain: startMarker ... endMarker)
-        .backport.chartXSelection(value: $selection)
+        .chartXSelection(value: $selection)
         .chartXAxis { basalChartXAxis }
         .chartYAxis { cobIobChartYAxis }
         .chartYScale(domain: combinedYDomain())

FreeAPS/Sources/Modules/Home/View/Chart/DummyCharts.swift → FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/DummyCharts.swift


FreeAPS/Sources/Modules/Home/View/Chart/ForecastView.swift → FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/ForecastView.swift


FreeAPS/Sources/Modules/Home/View/Chart/GlucoseChartView.swift → FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/GlucoseChartView.swift


+ 1 - 1
FreeAPS/Sources/Modules/Home/View/Chart/InsulinView.swift

@@ -32,7 +32,7 @@ struct InsulinView: ChartContent {
                     Image(systemName: "arrowtriangle.down.fill").font(.system(size: size)).foregroundStyle(Color.insulin)
                 }
                 .annotation(position: .top) {
-                    Text(MainChartHelper.bolusFormatter.string(from: amount) ?? "")
+                    Text(Formatter.bolusFormatter.string(from: amount) ?? "")
                         .font(.caption2)
                         .foregroundStyle(Color.primary)
                 }

FreeAPS/Sources/Modules/Home/View/Chart/OverrideView.swift → FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/OverrideView.swift


+ 111 - 0
FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/SelectionPopoverView.swift

@@ -0,0 +1,111 @@
+import Charts
+import Foundation
+import SwiftUICore
+
+struct SelectionPopoverView: ChartContent {
+    let selectedGlucose: GlucoseStored
+    let selectedIOBValue: OrefDetermination?
+    let selectedCOBValue: OrefDetermination?
+    let units: GlucoseUnits
+    let highGlucose: Decimal
+    let lowGlucose: Decimal
+    let currentGlucoseTarget: Decimal
+    let glucoseColorScheme: GlucoseColorScheme
+
+    private var glucoseToDisplay: Decimal {
+        units == .mgdL ? Decimal(selectedGlucose.glucose) : Decimal(selectedGlucose.glucose).asMmolL
+    }
+
+    private var pointMarkColor: Color {
+        let hardCodedLow = Decimal(55)
+        let hardCodedHigh = Decimal(220)
+        let isDynamicColorScheme = glucoseColorScheme == .dynamicColor
+
+        return FreeAPS.getDynamicGlucoseColor(
+            glucoseValue: Decimal(selectedGlucose.glucose),
+            highGlucoseColorValue: isDynamicColorScheme ? hardCodedHigh : highGlucose,
+            lowGlucoseColorValue: isDynamicColorScheme ? hardCodedLow : lowGlucose,
+            targetGlucose: currentGlucoseTarget,
+            glucoseColorScheme: glucoseColorScheme
+        )
+    }
+
+    var body: some ChartContent {
+        RuleMark(x: .value("Selection", selectedGlucose.date ?? Date.now, unit: .minute))
+            .foregroundStyle(Color.tabBar)
+            .offset(yStart: 70)
+            .lineStyle(.init(lineWidth: 2))
+            .annotation(
+                position: .top,
+                alignment: .center,
+                overflowResolution: .init(x: .fit(to: .chart), y: .fit(to: .chart))
+            ) {
+                selectionPopover
+            }
+
+        PointMark(
+            x: .value("Time", selectedGlucose.date ?? Date.now, unit: .minute),
+            y: .value("Value", glucoseToDisplay)
+        )
+        .zIndex(-1)
+        .symbolSize(CGSize(width: 15, height: 15))
+        .foregroundStyle(pointMarkColor)
+
+        PointMark(
+            x: .value("Time", selectedGlucose.date ?? Date.now, unit: .minute),
+            y: .value("Value", glucoseToDisplay)
+        )
+        .zIndex(-1)
+        .symbolSize(CGSize(width: 6, height: 6))
+        .foregroundStyle(Color.primary)
+    }
+
+    @ViewBuilder var selectionPopover: some View {
+        VStack(alignment: .leading) {
+            HStack {
+                Image(systemName: "clock")
+                Text(selectedGlucose.date?.formatted(.dateTime.hour().minute(.twoDigits)) ?? "")
+                    .font(.body).bold()
+            }
+            .font(.body).padding(.bottom, 5)
+
+            HStack {
+                Text(units == .mgdL ? glucoseToDisplay.description : glucoseToDisplay.formattedAsMmolL)
+                    .bold()
+                    + Text(" \(units.rawValue)")
+            }
+            .foregroundStyle(pointMarkColor)
+            .font(.body)
+
+            if let selectedIOBValue, let iob = selectedIOBValue.iob {
+                HStack {
+                    Image(systemName: "syringe.fill").frame(width: 15)
+                    Text(Formatter.bolusFormatter.string(from: iob) ?? "")
+                        .bold()
+                        + Text(NSLocalizedString(" U", comment: "Insulin unit"))
+                }
+                .foregroundStyle(Color.insulin).font(.body)
+            }
+
+            if let selectedCOBValue {
+                HStack {
+                    Image(systemName: "fork.knife").frame(width: 15)
+                    Text(Formatter.integerFormatter.string(from: selectedCOBValue.cob as NSNumber) ?? "")
+                        .bold()
+                        + Text(NSLocalizedString(" g", comment: "gram of carbs"))
+                }
+                .foregroundStyle(Color.orange).font(.body)
+            }
+        }
+        .padding()
+        .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)
+                )
+        }
+    }
+}

FreeAPS/Sources/Modules/Home/View/Chart/TempTargets.swift → FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/TempTargets.swift


+ 14 - 222
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -56,13 +56,13 @@ struct MainChartView: View {
 
     var selectedCOBValue: OrefDetermination? {
         guard let selection = selection else { return nil }
-        let range = selection.addingTimeInterval(-120) ... selection.addingTimeInterval(120)
+        let range = selection.addingTimeInterval(-150) ... selection.addingTimeInterval(150)
         return findDetermination(in: range)
     }
 
     var selectedIOBValue: OrefDetermination? {
         guard let selection = selection else { return nil }
-        let range = selection.addingTimeInterval(-120) ... selection.addingTimeInterval(120)
+        let range = selection.addingTimeInterval(-150) ... selection.addingTimeInterval(150)
         return findDetermination(in: range)
     }
 
@@ -171,43 +171,17 @@ extension MainChartView {
                 )
 
                 /// show glucose value when hovering over it
-                if #available(iOS 17, *) {
-                    if let selectedGlucose {
-                        RuleMark(x: .value("Selection", selectedGlucose.date ?? now, unit: .minute))
-                            .foregroundStyle(Color.tabBar)
-                            .offset(yStart: 70)
-                            .lineStyle(.init(lineWidth: 2))
-                            .annotation(
-                                position: .top,
-                                alignment: .center,
-                                overflowResolution: .init(x: .fit(to: .chart), y: .fit(to: .chart))
-                            ) {
-                                selectionPopover
-                            }
-
-                        PointMark(
-                            x: .value("Time", selectedGlucose.date ?? now, unit: .minute),
-                            y: .value("Value", selectedGlucose.glucose)
-                        )
-                        .zIndex(-1)
-                        .symbolSize(CGSize(width: 15, height: 15))
-                        .foregroundStyle(
-                            Decimal(selectedGlucose.glucose) > highGlucose ? Color.orange
-                                .opacity(0.8) :
-                                (
-                                    Decimal(selectedGlucose.glucose) < lowGlucose ? Color.red.opacity(0.8) : Color.green
-                                        .opacity(0.8)
-                                )
-                        )
-
-                        PointMark(
-                            x: .value("Time", selectedGlucose.date ?? now, unit: .minute),
-                            y: .value("Value", selectedGlucose.glucose)
-                        )
-                        .zIndex(-1)
-                        .symbolSize(CGSize(width: 6, height: 6))
-                        .foregroundStyle(Color.primary)
-                    }
+                if let selectedGlucose {
+                    SelectionPopoverView(
+                        selectedGlucose: selectedGlucose,
+                        selectedIOBValue: selectedIOBValue,
+                        selectedCOBValue: selectedCOBValue,
+                        units: units,
+                        highGlucose: highGlucose,
+                        lowGlucose: lowGlucose,
+                        currentGlucoseTarget: currentGlucoseTarget,
+                        glucoseColorScheme: glucoseColorScheme
+                    )
                 }
             }
             .id("MainChart")
@@ -222,7 +196,7 @@ extension MainChartView {
             .chartXAxis { mainChartXAxis }
             .chartYAxis { mainChartYAxis }
             .chartYAxis(.hidden)
-            .backport.chartXSelection(value: $selection)
+            .chartXSelection(value: $selection)
             .chartYScale(
                 domain: units == .mgdL ? state.minYAxisValue ... state.maxYAxisValue : state.minYAxisValue
                     .asMmolL ... state.maxYAxisValue.asMmolL
@@ -230,186 +204,4 @@ extension MainChartView {
             .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(.body).bold()
-                }.font(.body).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 = glucoseColorScheme == .dynamicColor
-
-                let glucoseColor = FreeAPS.getDynamicGlucoseColor(
-                    glucoseValue: Decimal(sgv),
-                    highGlucoseColorValue: isDynamicColorScheme ? hardCodedHigh : highGlucose,
-                    lowGlucoseColorValue: isDynamicColorScheme ? hardCodedLow : lowGlucose,
-                    targetGlucose: currentGlucoseTarget,
-                    glucoseColorScheme: glucoseColorScheme
-                )
-                HStack {
-                    Text(units == .mgdL ? Decimal(sgv).description : Decimal(sgv).formattedAsMmolL)
-                        .bold()
-                        + Text(" \(units.rawValue)")
-                }.foregroundStyle(
-                    Color(glucoseColor)
-                ).font(.body)
-
-                if let selectedIOBValue, let iob = selectedIOBValue.iob {
-                    HStack {
-                        Image(systemName: "syringe.fill").frame(width: 15)
-                        Text(MainChartHelper.bolusFormatter.string(from: iob) ?? "")
-                            .bold()
-                            + Text(NSLocalizedString(" U", comment: "Insulin unit"))
-                    }.foregroundStyle(Color.insulin).font(.body)
-                }
-
-                if let selectedCOBValue {
-                    HStack {
-                        Image(systemName: "fork.knife").frame(width: 15)
-                        Text(MainChartHelper.carbsFormatter.string(from: selectedCOBValue.cob as NSNumber) ?? "")
-                            .bold()
-                            + Text(NSLocalizedString(" g", comment: "gram of carbs"))
-                    }.foregroundStyle(Color.orange).font(.body)
-                }
-            }
-            .padding()
-            .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)
-                    )
-            }
-        }
-    }
-}
-
-// MARK: - Rule Marks and Charts configurations
-
-extension MainChartView {
-    func drawCurrentTimeMarker() -> some ChartContent {
-        RuleMark(
-            x: .value(
-                "",
-                Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970)),
-                unit: .second
-            )
-        ).lineStyle(.init(lineWidth: 2, dash: [3])).foregroundStyle(Color(.systemGray2))
-    }
-
-    func drawStartRuleMark() -> some ChartContent {
-        RuleMark(
-            x: .value(
-                "",
-                startMarker,
-                unit: .second
-            )
-        ).foregroundStyle(Color.clear)
-    }
-
-    func drawEndRuleMark() -> some ChartContent {
-        RuleMark(
-            x: .value(
-                "",
-                endMarker,
-                unit: .second
-            )
-        ).foregroundStyle(Color.clear)
-    }
-
-    func basalChartPlotStyle(_ plotContent: ChartPlotContent) -> some View {
-        plotContent
-            .rotationEffect(.degrees(180))
-            .scaleEffect(x: -1, y: 1)
-    }
-
-    var mainChartXAxis: some AxisContent {
-        AxisMarks(values: .stride(by: .hour, count: screenHours > 6 ? (screenHours > 12 ? 4 : 2) : 1)) { _ in
-            if displayXgridLines {
-                AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
-            } else {
-                AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
-            }
-        }
-    }
-
-    var basalChartXAxis: some AxisContent {
-        AxisMarks(values: .stride(by: .hour, count: screenHours > 6 ? (screenHours > 12 ? 4 : 2) : 1)) { _ in
-            if displayXgridLines {
-                AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
-            } else {
-                AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
-            }
-            AxisValueLabel(format: .dateTime.hour(.defaultDigits(amPM: .narrow)), anchor: .top)
-                .font(.footnote).foregroundStyle(Color.primary)
-        }
-    }
-
-    var mainChartYAxis: some AxisContent {
-        AxisMarks(position: .trailing) { value in
-
-            if displayYgridLines {
-                AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
-            } else {
-                AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
-            }
-
-            if let glucoseValue = value.as(Double.self), glucoseValue > 0 {
-                /// fix offset between the two charts...
-                if units == .mmolL {
-                    AxisTick(length: 7, stroke: .init(lineWidth: 7)).foregroundStyle(Color.clear)
-                }
-                AxisValueLabel().font(.footnote).foregroundStyle(Color.primary)
-            }
-        }
-    }
-
-    var cobIobChartYAxis: some AxisContent {
-        AxisMarks(position: .trailing) { _ in
-            if displayYgridLines {
-                AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
-            } else {
-                AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
-            }
-        }
-    }
-}
-
-// MARK: - Calculations and formatting
-
-extension MainChartView {
-    func fullWidth(viewWidth: CGFloat) -> CGFloat {
-        viewWidth * CGFloat(hours) / CGFloat(min(max(screenHours, 2), 24))
-    }
-
-    // Update start and  end marker to fix scroll update problem with x axis
-    func updateStartEndMarkers() {
-        startMarker = Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970 - 86400))
-
-        let threeHourSinceNow = Date(timeIntervalSinceNow: TimeInterval(hours: 3))
-
-        // min is 1.5h -> (1.5*1h = 1.5*(5*12*60))
-        let dynamicFutureDateForCone = Date(timeIntervalSinceNow: TimeInterval(
-            Int(1.5) * 5 * state
-                .minCount * 60
-        ))
-
-        endMarker = state
-            .forecastDisplayType == .lines ? threeHourSinceNow : dynamicFutureDateForCone <= threeHourSinceNow ?
-            dynamicFutureDateForCone.addingTimeInterval(TimeInterval(minutes: 30)) : threeHourSinceNow
-    }
-}
-
-extension Int16 {
-    var minutes: TimeInterval {
-        TimeInterval(self) * 60
-    }
 }

+ 1 - 1
FreeAPS/Sources/Shortcuts/AppShortcuts.swift

@@ -10,7 +10,7 @@ struct AppShortcuts: AppShortcutsProvider {
                 "Enacts a \(.applicationName) Bolus"
             ],
             shortTitle: "Bolus",
-            systemImageName: "drop.fill"
+            systemImageName: "syringe.fill"
         )
         AppShortcut(
             intent: ApplyTempPresetIntent(),

+ 0 - 8
FreeAPS/Sources/Views/ViewModifiers.swift

@@ -171,14 +171,6 @@ struct Backport<Content: View> {
 }
 
 extension Backport {
-    @ViewBuilder func chartXSelection(value: Binding<Date?>) -> some View {
-        if #available(iOS 17, *) {
-            content.chartXSelection(value: value)
-        } else {
-            content
-        }
-    }
-
     @ViewBuilder func chartForegroundStyleScale(state: any StateModel) -> some View {
         if (state as? Treatments.StateModel)?.forecastDisplayType == ForecastDisplayType.lines ||
             (state as? Home.StateModel)?.forecastDisplayType == ForecastDisplayType.lines