Forráskód Böngészése

Merge pull request #192 from polscm32/bgTargets

Only process GlucoseTargets when they have changed
Deniz Cengiz 1 éve
szülő
commit
ad4ee9435e

+ 8 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -319,6 +319,8 @@
 		BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */; };
 		BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3CC0712B0B89D50013189E /* MainChartView.swift */; };
 		BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */; };
+		BD4E1A7A2D3681B700D21626 /* GlucoseTargetSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4E1A792D3681AD00D21626 /* GlucoseTargetSetup.swift */; };
+		BD4E1A7C2D3686D900D21626 /* StartEndMarkerSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4E1A7B2D3686D400D21626 /* StartEndMarkerSetup.swift */; };
 		BD4ED4FD2CF9D5E8000EDC9C /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4ED4FC2CF9D5E8000EDC9C /* AppState.swift */; };
 		BD6EB2D62C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */; };
 		BD793CB02CE7C61500D669AC /* OverrideRunStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */; };
@@ -1022,6 +1024,8 @@
 		BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxToggleStyle.swift; sourceTree = "<group>"; };
 		BD3CC0712B0B89D50013189E /* MainChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartView.swift; sourceTree = "<group>"; };
 		BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataObserver.swift; sourceTree = "<group>"; };
+		BD4E1A792D3681AD00D21626 /* GlucoseTargetSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseTargetSetup.swift; sourceTree = "<group>"; };
+		BD4E1A7B2D3686D400D21626 /* StartEndMarkerSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartEndMarkerSetup.swift; sourceTree = "<group>"; };
 		BD4ED4FC2CF9D5E8000EDC9C /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
 		BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityWidgetConfiguration.swift; sourceTree = "<group>"; };
 		BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideRunStored+helper.swift"; sourceTree = "<group>"; };
@@ -2353,6 +2357,8 @@
 		58645B972CA2D16A008AFCE7 /* HomeStateModel+Setup */ = {
 			isa = PBXGroup;
 			children = (
+				BD4E1A7B2D3686D400D21626 /* StartEndMarkerSetup.swift */,
+				BD4E1A792D3681AD00D21626 /* GlucoseTargetSetup.swift */,
 				BDA6CC872CAF219800F942F9 /* TempTargetSetup.swift */,
 				58645B982CA2D1A4008AFCE7 /* GlucoseSetup.swift */,
 				58645B9A2CA2D24F008AFCE7 /* CarbSetup.swift */,
@@ -3618,6 +3624,7 @@
 				190EBCC829FF13AA00BA767D /* UserInterfaceSettingsStateModel.swift in Sources */,
 				DDF847EA2C5DABAC0049BB3B /* WatchConfigGarminView.swift in Sources */,
 				38BF021F25E7F0DE00579895 /* DeviceDataManager.swift in Sources */,
+				BD4E1A7A2D3681B700D21626 /* GlucoseTargetSetup.swift in Sources */,
 				BDB3C1192C03DD1000CEEAA1 /* UserDefaultsExtension.swift in Sources */,
 				38A504A425DD9C4000C5B9E8 /* UserDefaultsExtensions.swift in Sources */,
 				38FE826A25CC82DB001FF17A /* NetworkService.swift in Sources */,
@@ -3803,6 +3810,7 @@
 				19D4E4EB29FC6A9F00351451 /* Charts.swift in Sources */,
 				BDC531162D10629000088832 /* ContactPicture.swift in Sources */,
 				FEFFA7A22929FE49007B8193 /* UIDevice+Extensions.swift in Sources */,
+				BD4E1A7C2D3686D900D21626 /* StartEndMarkerSetup.swift in Sources */,
 				F90692D3274B9A130037068D /* AppleHealthKitRootView.swift in Sources */,
 				BDF34F852C10C62E00D51995 /* GlucoseData.swift in Sources */,
 				19E1F7EC29D082FE005C8D20 /* IconConfigStateModel.swift in Sources */,

+ 2 - 23
FreeAPS/Sources/Helpers/MainChartHelper.swift

@@ -109,7 +109,7 @@ extension MainChartView {
         RuleMark(
             x: .value(
                 "",
-                startMarker,
+                state.startMarker,
                 unit: .second
             )
         ).foregroundStyle(Color.clear)
@@ -119,7 +119,7 @@ extension MainChartView {
         RuleMark(
             x: .value(
                 "",
-                endMarker,
+                state.endMarker,
                 unit: .second
             )
         ).foregroundStyle(Color.clear)
@@ -181,29 +181,8 @@ extension MainChartView {
             }
         }
     }
-}
-
-// 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
-    }
 }

+ 95 - 0
FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/GlucoseTargetSetup.swift

@@ -0,0 +1,95 @@
+import Foundation
+
+extension Home.StateModel {
+    /**
+     Processes raw glucose target data into a list of target profiles for visualization.
+
+     - Parameters:
+        - rawTargets: The raw glucose target data containing offset and glucose values.
+        - startMarker: The reference date to start the target profiles from.
+     - Returns: An array of `TargetProfile` objects, each representing a glucose target range starting from the day of the startMarker and ending two days later.
+
+     The function:
+     - Converts glucose targets into profiles covering three consecutive days (day of startMarker, day after startMarker and day after that).
+     - Calculates start and end times for each target based on the offsets provided.
+     - Handles conversions between mg/dL and mmol/L as per user settings.
+     - Ensures targets span across midnight to avoid data cutoff.
+     */
+    func processFetchedTargets(_ rawTargets: BGTargets, startMarker: Date) -> [TargetProfile] {
+        var targetProfiles: [TargetProfile] = []
+
+        // Ensure there are targets to process
+        guard !rawTargets.targets.isEmpty else {
+            print("Warning: No targets to process in rawTargets.")
+            return []
+        }
+
+        let targets = rawTargets.targets
+
+        // Base date is the start of the day for the startMarker
+        let baseDate = Calendar.current.startOfDay(for: startMarker)
+
+        // Process each target three times
+        for index in 0 ..< (targets.count * 3) {
+            // Calculate the day offset (0 for today, 1 for tomorrow, 2 for day after)
+            let dayOffset = index / targets.count
+            let targetIndex = index % targets.count
+
+            // Validate target index to ensure safety
+            guard targetIndex < targets.count else {
+                print("Error: Invalid target index \(targetIndex).")
+                continue
+            }
+
+            // Fetch the target for the current iteration
+            let target = targets[targetIndex]
+
+            // Calculate the time offset for the current day
+            let dayTimeOffset = TimeInterval(dayOffset * 24 * 60 * 60)
+
+            // Calculate the start time for the current target
+            let startTime = baseDate
+                .addingTimeInterval(dayTimeOffset)
+                .addingTimeInterval(TimeInterval(target.offset * 60))
+
+            // Calculate the end time for the current target
+            let endTime: Date = {
+                if targetIndex + 1 < targets.count {
+                    // End time is the start time of the next target within the same day
+                    return baseDate
+                        .addingTimeInterval(dayTimeOffset)
+                        .addingTimeInterval(TimeInterval(targets[targetIndex + 1].offset * 60))
+                } else {
+                    // End time is the end of the day (midnight of the next day)
+                    return baseDate.addingTimeInterval(dayTimeOffset + 24 * 60 * 60)
+                }
+            }()
+
+            // Convert glucose value based on user unit preference (mg/dL or mmol/L)
+            let targetValue = units == .mgdL ? target.low : target.low.asMmolL
+
+            // Append the processed target profile to the list
+            targetProfiles.append(
+                TargetProfile(
+                    value: targetValue,
+                    startTime: startTime.timeIntervalSinceReferenceDate,
+                    endTime: endTime.timeIntervalSinceReferenceDate
+                )
+            )
+        }
+
+        return targetProfiles
+    }
+}
+
+struct TargetProfile: Hashable {
+    let value: Decimal
+    let startTime: TimeInterval
+    let endTime: TimeInterval
+}
+
+private extension Date {
+    var startOfDay: Date {
+        Calendar.current.startOfDay(for: self)
+    }
+}

+ 18 - 0
FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/StartEndMarkerSetup.swift

@@ -0,0 +1,18 @@
+import Foundation
+
+extension Home.StateModel {
+    // 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 * minCount * 60
+        ))
+
+        endMarker = forecastDisplayType == .lines ? threeHourSinceNow : dynamicFutureDateForCone <= threeHourSinceNow ?
+            dynamicFutureDateForCone.addingTimeInterval(TimeInterval(minutes: 30)) : threeHourSinceNow
+    }
+}

+ 5 - 0
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -19,6 +19,8 @@ extension Home {
         @ObservationIgnored @Injected() var overrideStorage: OverrideStorage!
         private let timer = DispatchTimer(timeInterval: 5)
         private(set) var filteredHours = 24
+        var startMarker = Date(timeIntervalSinceNow: TimeInterval(hours: -24))
+        var endMarker = Date(timeIntervalSinceNow: TimeInterval(hours: 3))
         var manualGlucose: [BloodGlucose] = []
         var uploadStats = false
         var recentGlucose: BloodGlucose?
@@ -26,6 +28,7 @@ extension Home {
         var basalProfile: [BasalProfileEntry] = []
         var bgTargets = BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
             ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])
+        var targetProfiles: [TargetProfile] = []
         var timerDate = Date()
         var closedLoop = false
         var pumpSuspended = false
@@ -476,8 +479,10 @@ extension Home {
 
         private func setupGlucoseTargets() async {
             let bgTargets = await provider.getBGTargets()
+            let targetProfiles = processFetchedTargets(bgTargets, startMarker: startMarker)
             await MainActor.run {
                 self.bgTargets = bgTargets
+                self.targetProfiles = targetProfiles
             }
         }
 

+ 6 - 6
FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/BasalChart.swift

@@ -34,7 +34,7 @@ extension MainChartView {
             }
             .frame(minHeight: geo.size.height * 0.05)
             .frame(width: fullWidth(viewWidth: screenSize.width))
-            .chartXScale(domain: startMarker ... endMarker)
+            .chartXScale(domain: state.startMarker ... state.endMarker)
             .chartXAxis { basalChartXAxis }
             .chartXAxis(.hidden)
             .chartYAxis(.hidden)
@@ -96,7 +96,7 @@ extension MainChartView {
                 series: .value("profile", "profile")
             ).lineStyle(.init(lineWidth: 2, dash: [2, 4])).foregroundStyle(Color.insulin)
             LineMark(
-                x: .value("End Date", profile.endDate ?? endMarker),
+                x: .value("End Date", profile.endDate ?? state.endMarker),
                 y: .value("Amount", profile.amount),
                 series: .value("profile", "profile")
             ).lineStyle(.init(lineWidth: 2.5, dash: [2, 4])).foregroundStyle(Color.insulin)
@@ -204,7 +204,7 @@ extension MainChartView {
 
             async let getRegularBasalPoints = findRegularBasalPoints(
                 timeBegin: dayAgoTime,
-                timeEnd: endMarker.timeIntervalSince1970
+                timeEnd: state.endMarker.timeIntervalSince1970
             )
 
             var regularPoints = await getRegularBasalPoints
@@ -224,8 +224,8 @@ extension MainChartView {
                     BasalProfile(
                         amount: single.amount,
                         isOverwritten: single.isOverwritten,
-                        startDate: startMarker,
-                        endDate: endMarker
+                        startDate: state.startMarker,
+                        endDate: state.endMarker
                     )
                 )
             }
@@ -248,7 +248,7 @@ extension MainChartView {
                             amount: lastItem.amount,
                             isOverwritten: lastItem.isOverwritten,
                             startDate: lastItem.startDate,
-                            endDate: endMarker
+                            endDate: state.endMarker
                         )
                     )
                 }

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

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

+ 2 - 2
FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/DummyCharts.swift

@@ -43,7 +43,7 @@ extension MainChartView {
         )
         .frame(width: screenSize.width - 10)
         .chartXAxis { mainChartXAxis }
-        .chartXScale(domain: startMarker ... endMarker)
+        .chartXScale(domain: state.startMarker ... state.endMarker)
         .chartXAxis(.hidden)
         .chartYAxis { mainChartYAxis }
         .chartYScale(
@@ -69,7 +69,7 @@ extension MainChartView {
             .id("DummyCobChart")
             .frame(minHeight: geo.size.height * 0.12)
             .frame(width: screenSize.width - 10)
-            .chartXScale(domain: startMarker ... endMarker)
+            .chartXScale(domain: state.startMarker ... state.endMarker)
             .chartXAxis { basalChartXAxis }
             .chartXAxis(.hidden)
             .chartYAxis { cobIobChartYAxis }

+ 4 - 104
FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/GlucoseTargetsView.swift

@@ -3,30 +3,20 @@ import Foundation
 import SwiftUI
 
 struct GlucoseTargetsView: ChartContent {
-    let startMarker: Date
-    let units: GlucoseUnits
-    let bgTargets: BGTargets
+    let targetProfiles: [TargetProfile]
 
     var body: some ChartContent {
-        drawGlucoseTargets()
+        drawGlucoseTargets(for: targetProfiles)
     }
 
     /**
      Draws glucose target ranges on the chart
 
      - Returns: A ChartContent containing line marks representing target glucose ranges
-
-     The function:
-     - Creates target profiles for two consecutive days
-     - Converts values between mg/dL and mmol/L based on user settings
-     - Draws green lines to visualize the target ranges
      */
-    private func drawGlucoseTargets() -> some ChartContent {
-        // Array to store target profiles for visualization
-        let targetProfiles: [TargetProfile] = processFetchedTargets(bgTargets)
-
+    private func drawGlucoseTargets(for targetProfiles: [TargetProfile]) -> some ChartContent {
         // Draw target lines for each profile
-        return ForEach(targetProfiles, id: \.self) { profile in
+        ForEach(targetProfiles, id: \.self) { profile in
             LineMark(
                 x: .value("Time", Date(timeIntervalSinceReferenceDate: profile.startTime)),
                 y: .value("Target", profile.value)
@@ -42,94 +32,4 @@ struct GlucoseTargetsView: ChartContent {
             .foregroundStyle(Color.green.gradient)
         }
     }
-
-    /**
-     Processes raw glucose target data into a list of target profiles for visualization.
-
-     - Parameter rawTargets: The raw glucose target data containing offset and glucose values.
-     - Returns: An array of `TargetProfile` objects, each representing a glucose target range starting from the day of the startMarker and ending two days later.
-
-     The function:
-     - Converts glucose targets into profiles covering three consecutive days (day of startMarker, day after startMarker and day after that).
-     - Calculates start and end times for each target based on the offsets provided.
-     - Handles conversions between mg/dL and mmol/L as per user settings.
-     - Ensures targets span across midnight to avoid data cutoff.
-     */
-    private func processFetchedTargets(_ rawTargets: BGTargets) -> [TargetProfile] {
-        var targetProfiles: [TargetProfile] = []
-
-        // Ensure there are targets to process
-        guard !rawTargets.targets.isEmpty else {
-            print("Warning: No targets to process in rawTargets.")
-            return []
-        }
-
-        let targets = rawTargets.targets
-
-        // Base date is the start of the day for the startMarker
-        let baseDate = Calendar.current.startOfDay(for: startMarker)
-
-        // Process each target three times
-        for index in 0 ..< (targets.count * 3) {
-            // Calculate the day offset (0 for today, 1 for tomorrow, 2 for day after)
-            let dayOffset = index / targets.count
-            let targetIndex = index % targets.count
-
-            // Validate target index to ensure safety
-            guard targetIndex < targets.count else {
-                print("Error: Invalid target index \(targetIndex).")
-                continue
-            }
-
-            // Fetch the target for the current iteration
-            let target = targets[targetIndex]
-
-            // Calculate the time offset for the current day
-            let dayTimeOffset = TimeInterval(dayOffset * 24 * 60 * 60)
-
-            // Calculate the start time for the current target
-            let startTime = baseDate
-                .addingTimeInterval(dayTimeOffset)
-                .addingTimeInterval(TimeInterval(target.offset * 60))
-
-            // Calculate the end time for the current target
-            let endTime: Date = {
-                if targetIndex + 1 < targets.count {
-                    // End time is the start time of the next target within the same day
-                    return baseDate
-                        .addingTimeInterval(dayTimeOffset)
-                        .addingTimeInterval(TimeInterval(targets[targetIndex + 1].offset * 60))
-                } else {
-                    // End time is the end of the day (midnight of the next day)
-                    return baseDate.addingTimeInterval(dayTimeOffset + 24 * 60 * 60)
-                }
-            }()
-
-            // Convert glucose value based on user unit preference (mg/dL or mmol/L)
-            let targetValue = units == .mgdL ? target.low : target.low.asMmolL
-
-            // Append the processed target profile to the list
-            targetProfiles.append(
-                TargetProfile(
-                    value: targetValue,
-                    startTime: startTime.timeIntervalSinceReferenceDate,
-                    endTime: endTime.timeIntervalSinceReferenceDate
-                )
-            )
-        }
-
-        return targetProfiles
-    }
-}
-
-struct TargetProfile: Hashable {
-    let value: Decimal
-    let startTime: TimeInterval
-    let endTime: TimeInterval
-}
-
-private extension Date {
-    var startOfDay: Date {
-        Calendar.current.startOfDay(for: self)
-    }
 }

+ 7 - 13
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -22,10 +22,6 @@ struct MainChartView: View {
 
     @State var basalProfiles: [BasalProfile] = []
     @State var preparedTempBasals: [(start: Date, end: Date, rate: Double)] = []
-    @State var startMarker =
-        Date(timeIntervalSinceNow: TimeInterval(hours: -24))
-    @State var endMarker = Date(timeIntervalSinceNow: TimeInterval(hours: 3))
-
     @State var selection: Date? = nil
 
     @State var mainChartHasInitialized = false
@@ -87,7 +83,7 @@ struct MainChartView: View {
                         }
                         .onChange(of: state.glucoseFromPersistence.last?.glucose) {
                             scroller.scrollTo("MainChart", anchor: .trailing)
-                            updateStartEndMarkers()
+                            state.updateStartEndMarkers()
                         }
                         .onChange(of: state.enactedAndNonEnactedDeterminations.first?.deliverAt) {
                             scroller.scrollTo("MainChart", anchor: .trailing)
@@ -99,7 +95,7 @@ struct MainChartView: View {
                         .onAppear {
                             if !mainChartHasInitialized {
                                 scroller.scrollTo("MainChart", anchor: .trailing)
-                                updateStartEndMarkers()
+                                state.updateStartEndMarkers()
                                 calculateTempBasalsInBackground()
                                 mainChartHasInitialized = true
                             }
@@ -121,6 +117,10 @@ extension MainChartView {
                 drawEndRuleMark()
                 drawCurrentTimeMarker()
 
+                GlucoseTargetsView(
+                    targetProfiles: state.targetProfiles
+                )
+
                 OverrideView(
                     state: state,
                     overrides: state.overrides,
@@ -136,12 +136,6 @@ extension MainChartView {
                     viewContext: context
                 )
 
-                GlucoseTargetsView(
-                    startMarker: startMarker,
-                    units: state.units,
-                    bgTargets: state.bgTargets
-                )
-
                 GlucoseChartView(
                     glucoseData: state.glucoseFromPersistence,
                     units: state.units,
@@ -197,7 +191,7 @@ extension MainChartView {
                 minHeight: geo.size.height * (0.28 - safeAreaSize)
             )
             .frame(width: fullWidth(viewWidth: screenSize.width))
-            .chartXScale(domain: startMarker ... endMarker)
+            .chartXScale(domain: state.startMarker ... state.endMarker)
             .chartXAxis { mainChartXAxis }
             .chartYAxis { mainChartYAxis }
             .chartYAxis(.hidden)