Sfoglia il codice sorgente

Merge pull request #178 from dnzxy/extend-stuff

Extend Various Things
polscm32 1 anno fa
parent
commit
0f70d3e91d
23 ha cambiato i file con 277 aggiunte e 150 eliminazioni
  1. 4 4
      FreeAPS.xcodeproj/project.pbxproj
  2. 0 57
      FreeAPS/Sources/Helpers/SettingsRowView.swift
  3. 4 0
      FreeAPS/Sources/Models/BGTargets.swift
  4. 1 1
      FreeAPS/Sources/Modules/Adjustments/AdjustmentsProvider.swift
  5. 1 1
      FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel.swift
  6. 1 1
      FreeAPS/Sources/Modules/Calibrations/View/CalibrationsChart.swift
  7. 1 1
      FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift
  8. 3 3
      FreeAPS/Sources/Modules/Home/HomeDataFlow.swift
  9. 5 5
      FreeAPS/Sources/Modules/Home/HomeProvider.swift
  10. 34 11
      FreeAPS/Sources/Modules/Home/HomeStateModel.swift
  11. 2 2
      FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/GlucoseChartView.swift
  12. 140 0
      FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/GlucoseTargetsView.swift
  13. 18 4
      FreeAPS/Sources/Modules/Home/View/Chart/ChartLegendView.swift
  14. 6 0
      FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift
  15. 1 1
      FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift
  16. 11 8
      FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift
  17. 22 22
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  18. 1 1
      FreeAPS/Sources/Modules/LiveActivitySettings/View/LiveActivityWidgetConfiguration.swift
  19. 17 23
      FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift
  20. 1 1
      FreeAPS/Sources/Modules/Treatments/TreatmentsDataFlow.swift
  21. 1 1
      FreeAPS/Sources/Modules/Treatments/TreatmentsProvider.swift
  22. 1 1
      FreeAPS/Sources/Modules/Treatments/TreatmentsStateModel.swift
  23. 2 2
      FreeAPS/Sources/Modules/Treatments/View/MealPreset/MealPresetView.swift

+ 4 - 4
FreeAPS.xcodeproj/project.pbxproj

@@ -260,7 +260,6 @@
 		58645BA52CA2D347008AFCE7 /* ForecastSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58645BA42CA2D347008AFCE7 /* ForecastSetup.swift */; };
 		58645BA72CA2D390008AFCE7 /* ChartAxisSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58645BA62CA2D390008AFCE7 /* ChartAxisSetup.swift */; };
 		5864E8592C42CFAE00294306 /* DeterminationStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5864E8582C42CFAE00294306 /* DeterminationStorage.swift */; };
-		587DA1F62B77F3DD00B28F8A /* SettingsRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587DA1F52B77F3DD00B28F8A /* SettingsRowView.swift */; };
 		5887527C2BD986E1008B081D /* OpenAPSBattery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5887527B2BD986E1008B081D /* OpenAPSBattery.swift */; };
 		58A3D53A2C96D4DE003F90FC /* AddTempTargetForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3D5392C96D4DE003F90FC /* AddTempTargetForm.swift */; };
 		58A3D5442C96DE11003F90FC /* TempTargetStored+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3D5432C96DE11003F90FC /* TempTargetStored+Helper.swift */; };
@@ -452,6 +451,7 @@
 		DD1DB7CC2BECCA1F0048B367 /* BuildDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */; };
 		DD1E53592D273F26008F32A4 /* LoopStatusHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1E53582D273F20008F32A4 /* LoopStatusHelpView.swift */; };
 		DD21FCB52C6952AD00AF2C25 /* DecimalPickerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD21FCB42C6952AD00AF2C25 /* DecimalPickerSettings.swift */; };
+		DD2CC85C2D25DA1000445446 /* GlucoseTargetsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2CC85B2D25D9CE00445446 /* GlucoseTargetsView.swift */; };
 		DD32CF982CC82463003686D6 /* TrioRemoteControl+Bolus.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CF972CC82460003686D6 /* TrioRemoteControl+Bolus.swift */; };
 		DD32CF9A2CC8247B003686D6 /* TrioRemoteControl+Meal.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CF992CC8246F003686D6 /* TrioRemoteControl+Meal.swift */; };
 		DD32CF9C2CC82499003686D6 /* TrioRemoteControl+TempTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CF9B2CC82495003686D6 /* TrioRemoteControl+TempTarget.swift */; };
@@ -961,7 +961,6 @@
 		58645BA42CA2D347008AFCE7 /* ForecastSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastSetup.swift; sourceTree = "<group>"; };
 		58645BA62CA2D390008AFCE7 /* ChartAxisSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartAxisSetup.swift; sourceTree = "<group>"; };
 		5864E8582C42CFAE00294306 /* DeterminationStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeterminationStorage.swift; sourceTree = "<group>"; };
-		587DA1F52B77F3DD00B28F8A /* SettingsRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRowView.swift; sourceTree = "<group>"; };
 		5887527B2BD986E1008B081D /* OpenAPSBattery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAPSBattery.swift; sourceTree = "<group>"; };
 		58A3D5392C96D4DE003F90FC /* AddTempTargetForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTempTargetForm.swift; sourceTree = "<group>"; };
 		58A3D5432C96DE11003F90FC /* TempTargetStored+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetStored+Helper.swift"; sourceTree = "<group>"; };
@@ -1155,6 +1154,7 @@
 		DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildDetails.swift; sourceTree = "<group>"; };
 		DD1E53582D273F20008F32A4 /* LoopStatusHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStatusHelpView.swift; sourceTree = "<group>"; };
 		DD21FCB42C6952AD00AF2C25 /* DecimalPickerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecimalPickerSettings.swift; sourceTree = "<group>"; };
+		DD2CC85B2D25D9CE00445446 /* GlucoseTargetsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseTargetsView.swift; sourceTree = "<group>"; };
 		DD32CF972CC82460003686D6 /* TrioRemoteControl+Bolus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+Bolus.swift"; sourceTree = "<group>"; };
 		DD32CF992CC8246F003686D6 /* TrioRemoteControl+Meal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+Meal.swift"; sourceTree = "<group>"; };
 		DD32CF9B2CC82495003686D6 /* TrioRemoteControl+TempTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+TempTarget.swift"; sourceTree = "<group>"; };
@@ -2073,7 +2073,6 @@
 				FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */,
 				FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */,
 				CEA4F62229BE10F70011ADF7 /* SavitzkyGolayFilter.swift */,
-				587DA1F52B77F3DD00B28F8A /* SettingsRowView.swift */,
 				BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */,
 				BD1661302B82ADAB00256551 /* CustomProgressView.swift */,
 				581516A32BCED84A00BF67D7 /* DebuggingIdentifiers.swift */,
@@ -2504,6 +2503,7 @@
 		BDDAF9F12D0055CC00B34E7A /* ChartElements */ = {
 			isa = PBXGroup;
 			children = (
+				DD2CC85B2D25D9CE00445446 /* GlucoseTargetsView.swift */,
 				BDDAF9EE2D00553E00B34E7A /* SelectionPopoverView.swift */,
 				582DF9742C8CDB92001F516D /* GlucoseChartView.swift */,
 				582DF9762C8CDBE7001F516D /* InsulinView.swift */,
@@ -3633,13 +3633,13 @@
 				BDFD165A2AE40438007F0DDA /* TreatmentsRootView.swift in Sources */,
 				38E98A2525F52C9300C0CED0 /* IssueReporter.swift in Sources */,
 				DD1745522C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift in Sources */,
+				DD2CC85C2D25DA1000445446 /* GlucoseTargetsView.swift in Sources */,
 				190EBCC429FF136900BA767D /* UserInterfaceSettingsDataFlow.swift in Sources */,
 				5A2325582BFCC168003518CA /* NightscoutConnectView.swift in Sources */,
 				3811DEB025C9D88300A708ED /* BaseKeychain.swift in Sources */,
 				110AEDE42C5193D200615CC9 /* BolusIntentRequest.swift in Sources */,
 				3811DE4325C9D4A100A708ED /* SettingsProvider.swift in Sources */,
 				45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */,
-				587DA1F62B77F3DD00B28F8A /* SettingsRowView.swift in Sources */,
 				CE7CA34E2A064973004BE681 /* AppShortcuts.swift in Sources */,
 				38C4D33A25E9A1ED00D30B77 /* NSObject+AssociatedValues.swift in Sources */,
 				110AEDEB2C51A0AE00615CC9 /* ShortcutsConfigView.swift in Sources */,

+ 0 - 57
FreeAPS/Sources/Helpers/SettingsRowView.swift

@@ -1,57 +0,0 @@
-import SwiftUI
-
-struct SettingsRowView: View {
-    let imageName: String
-    let title: String
-    let tint: Color
-    let spacing: CGFloat?
-    let font: CGFloat?
-
-    init(imageName: String, title: String, tint: Color, spacing: CGFloat? = 12, font: CGFloat? = 35) {
-        self.imageName = imageName
-        self.title = title
-        self.tint = tint
-        self.spacing = spacing
-        self.font = font
-    }
-
-    var body: some View {
-        HStack(spacing: spacing ?? 12, content: {
-            Image(systemName: imageName)
-                .imageScale(.small)
-                .font(.system(size: font ?? 35))
-                .foregroundColor(tint)
-
-            Text(title)
-                .font(.subheadline)
-                .foregroundStyle(.primary)
-        })
-    }
-}
-
-struct SettingsRowViewCustomImage: View {
-    let imageName: String
-    let title: String
-    let frame: CGFloat?
-    let spacing: CGFloat?
-
-    init(imageName: String, title: String, frame: CGFloat? = 35, spacing: CGFloat? = 12) {
-        self.imageName = imageName
-        self.title = title
-        self.frame = frame
-        self.spacing = spacing
-    }
-
-    var body: some View {
-        HStack(spacing: spacing ?? 12, content: {
-            Image(imageName)
-                .resizable()
-                .aspectRatio(contentMode: .fit)
-                .frame(width: frame ?? 35, height: frame ?? 35)
-
-            Text(title)
-                .font(.subheadline)
-                .foregroundStyle(.primary)
-        })
-    }
-}

+ 4 - 0
FreeAPS/Sources/Models/BGTargets.swift

@@ -6,6 +6,10 @@ struct BGTargets: JSON {
     var targets: [BGTargetEntry]
 }
 
+protocol BGTargetsObserver {
+    func bgTargetsDidChange(_ bgTargets: BGTargets)
+}
+
 extension BGTargets {
     private enum CodingKeys: String, CodingKey {
         case units

+ 1 - 1
FreeAPS/Sources/Modules/Adjustments/AdjustmentsProvider.swift

@@ -1,6 +1,6 @@
 extension Adjustments {
     final class Provider: BaseProvider, AdjustmentsProvider {
-        func getBGTarget() async -> BGTargets {
+        func getBGTargets() async -> BGTargets {
             await storage.retrieveAsync(OpenAPS.Settings.bgTargets, as: BGTargets.self)
                 ?? BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
                 ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])

+ 1 - 1
FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel.swift

@@ -105,7 +105,7 @@ extension Adjustments {
             dateFormatter.dateFormat = "HH:mm:ss"
             dateFormatter.timeZone = TimeZone.current
 
-            let bgTargets = await provider.getBGTarget()
+            let bgTargets = await provider.getBGTargets()
             let entries: [(start: String, value: Decimal)] = bgTargets.targets.map { ($0.start, $0.low) }
 
             for (index, entry) in entries.enumerated() {

+ 1 - 1
FreeAPS/Sources/Modules/Calibrations/View/CalibrationsChart.swift

@@ -45,7 +45,7 @@ struct CalibrationsChart: View {
                             )
                         Text(dateFormatter.string(from: value.date))
                             .foregroundColor(.white)
-                            .font(.system(size: 10))
+                            .font(.caption2)
                             .position(
                                 x: value.x / maxValue * geo.size.width,
                                 y: geo.size.width - (value.y / maxValue * geo.size.width) + 10

+ 1 - 1
FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -141,7 +141,7 @@ extension DataTable {
                     HStack {
                         Text("Add Glucose")
                         Image(systemName: "plus")
-                            .font(.system(size: 20))
+                            .font(.title2)
                     }
                 }
             )

+ 3 - 3
FreeAPS/Sources/Modules/Home/HomeDataFlow.swift

@@ -7,10 +7,10 @@ enum Home {
 
 protocol HomeProvider: Provider {
     func heartbeatNow()
-    func pumpSettings() -> PumpSettings
+    func pumpSettings() async -> PumpSettings
     func getBasalProfile() async -> [BasalProfileEntry]
     func tempTargets(hours: Int) -> [TempTarget]
-    func pumpReservoir() -> Decimal?
+    func pumpReservoir() async -> Decimal?
     func tempTarget() -> TempTarget?
-    func getBGTarget() async -> BGTargets
+    func getBGTargets() async -> BGTargets
 }

+ 5 - 5
FreeAPS/Sources/Modules/Home/HomeProvider.swift

@@ -26,14 +26,14 @@ extension Home {
             tempTargetsStorage.current()
         }
 
-        func pumpSettings() -> PumpSettings {
-            storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self)
+        func pumpSettings() async -> PumpSettings {
+            await storage.retrieveAsync(OpenAPS.Settings.settings, as: PumpSettings.self)
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
                 ?? PumpSettings(insulinActionCurve: 10, maxBolus: 10, maxBasal: 2)
         }
 
-        func pumpReservoir() -> Decimal? {
-            storage.retrieve(OpenAPS.Monitor.reservoir, as: Decimal.self)
+        func pumpReservoir() async -> Decimal? {
+            await storage.retrieveAsync(OpenAPS.Monitor.reservoir, as: Decimal.self)
         }
 
         func getBasalProfile() async -> [BasalProfileEntry] {
@@ -42,7 +42,7 @@ extension Home {
                 ?? []
         }
 
-        func getBGTarget() async -> BGTargets {
+        func getBGTargets() async -> BGTargets {
             await storage.retrieveAsync(OpenAPS.Settings.bgTargets, as: BGTargets.self)
                 ?? BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
                 ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])

+ 34 - 11
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -24,6 +24,8 @@ extension Home {
         var recentGlucose: BloodGlucose?
         var maxBasal: Decimal = 2
         var basalProfile: [BasalProfileEntry] = []
+        var bgTargets = BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
+            ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])
         var tempTargets: [TempTarget] = []
         var timerDate = Date()
         var closedLoop = false
@@ -160,12 +162,15 @@ extension Home {
                         self.setupBatteryArray()
                     }
                     group.addTask {
-                        self.setupPumpSettings()
+                        await self.setupPumpSettings()
                     }
                     group.addTask {
                         await self.setupBasalProfile()
                     }
                     group.addTask {
+                        await self.setupGlucoseTargets()
+                    }
+                    group.addTask {
                         self.setupReservoir()
                     }
                     group.addTask {
@@ -268,6 +273,7 @@ extension Home {
             broadcaster.register(PreferencesObserver.self, observer: self)
             broadcaster.register(PumpSettingsObserver.self, observer: self)
             broadcaster.register(BasalProfileObserver.self, observer: self)
+            broadcaster.register(BGTargetsObserver.self, observer: self)
             broadcaster.register(PumpReservoirObserver.self, observer: self)
             broadcaster.register(PumpDeactivatedObserver.self, observer: self)
 
@@ -457,10 +463,10 @@ extension Home {
             return roundedAmount.formatted()
         }
 
-        private func setupPumpSettings() {
-            DispatchQueue.main.async { [weak self] in
-                guard let self = self else { return }
-                self.maxBasal = self.provider.pumpSettings().maxBasal
+        private func setupPumpSettings() async {
+            let maxBasal = await provider.pumpSettings().maxBasal
+            await MainActor.run {
+                self.maxBasal = maxBasal
             }
         }
 
@@ -471,10 +477,19 @@ extension Home {
             }
         }
 
+        private func setupGlucoseTargets() async {
+            let bgTargets = await provider.getBGTargets()
+            await MainActor.run {
+                self.bgTargets = bgTargets
+            }
+        }
+
         private func setupReservoir() {
-            DispatchQueue.main.async { [weak self] in
-                guard let self = self else { return }
-                self.reservoir = self.provider.pumpReservoir()
+            Task {
+                let reservoir = await provider.pumpReservoir()
+                await MainActor.run {
+                    self.reservoir = reservoir
+                }
             }
         }
 
@@ -492,7 +507,6 @@ extension Home {
             dateFormatter.dateFormat = "HH:mm"
             dateFormatter.timeZone = TimeZone.current
 
-            let bgTargets = await provider.getBGTarget()
             let entries: [(start: String, value: Decimal)] = bgTargets.targets.map { ($0.start, $0.low) }
 
             for (index, entry) in entries.enumerated() {
@@ -546,6 +560,7 @@ extension Home.StateModel:
     PreferencesObserver,
     PumpSettingsObserver,
     BasalProfileObserver,
+    BGTargetsObserver,
     PumpReservoirObserver,
     PumpTimeZoneObserver,
     PumpDeactivatedObserver
@@ -597,8 +612,10 @@ extension Home.StateModel:
     }
 
     func pumpSettingsDidChange(_: PumpSettings) {
-        setupPumpSettings()
-        setupBatteryArray()
+        Task {
+            await setupPumpSettings()
+            setupBatteryArray()
+        }
     }
 
     func basalProfileDidChange(_: [BasalProfileEntry]) {
@@ -607,6 +624,12 @@ extension Home.StateModel:
         }
     }
 
+    func bgTargetsDidChange(_: BGTargets) {
+        Task {
+            await setupGlucoseTargets()
+        }
+    }
+
     func pumpReservoirDidChange(_: Decimal) {
         setupReservoir()
         displayPumpStatusHighlightMessage()

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

@@ -42,7 +42,7 @@ struct GlucoseChartView: ChartContent {
                 .symbol {
                     if item.isManual {
                         Image(systemName: "drop.fill")
-                            .font(.system(size: 10))
+                            .font(.caption2)
                             .symbolRenderingMode(.monochrome)
                             .bold()
                             .foregroundStyle(.red)
@@ -61,7 +61,7 @@ struct GlucoseChartView: ChartContent {
                 .symbol {
                     if item.isManual {
                         Image(systemName: "drop.fill")
-                            .font(.system(size: 10))
+                            .font(.caption2)
                             .symbolRenderingMode(.monochrome)
                             .bold()
                             .foregroundStyle(.red)

+ 140 - 0
FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/GlucoseTargetsView.swift

@@ -0,0 +1,140 @@
+import Charts
+import Foundation
+import SwiftUI
+
+struct GlucoseTargetsView: ChartContent {
+    let startMarker: Date
+    let units: GlucoseUnits
+    let bgTargets: BGTargets
+
+    var body: some ChartContent {
+        drawGlucoseTargets()
+    }
+
+    /**
+     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)
+
+        // Draw target lines for each profile
+        return ForEach(targetProfiles, id: \.self) { profile in
+            LineMark(
+                x: .value("Time", Date(timeIntervalSinceReferenceDate: profile.startTime)),
+                y: .value("Target", profile.value)
+            )
+            .lineStyle(.init(lineWidth: 1))
+            .foregroundStyle(Color.green.gradient)
+
+            LineMark(
+                x: .value("Time", Date(timeIntervalSinceReferenceDate: profile.endTime)),
+                y: .value("Target", profile.value)
+            )
+            .lineStyle(.init(lineWidth: 1))
+            .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 for today and tomorrow.
+
+     The function:
+     - Converts glucose targets into profiles covering two consecutive days (today and tomorrow).
+     - 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.
+
+     Example:
+     For a target at offset 0 (midnight) with low glucose value 70 mg/dL, the function generates two profiles:
+     - One for today from midnight to the next target offset or end of the day.
+     - Another for tomorrow covering the same time range.
+     */
+    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 twice: once for today and once for tomorrow
+        for index in 0 ..< (targets.count * 2) {
+            // Calculate the day offset (0 for today, 1 for tomorrow)
+            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 - 4
FreeAPS/Sources/Modules/Home/View/Chart/ChartLegendView.swift

@@ -66,15 +66,29 @@ struct ChartLegendView: View {
                         DefinitionRow(
                             term: "CGM Glucose Value",
                             definition: VStack(alignment: .leading, spacing: 10) {
-                                Text(
-                                    "Displays real-time glucose readings from your CGM. Depending on your user interface settings, this may be displayed in a static (red, green, orange) or dynamic (full color spectrum) coloring scheme."
-                                )
+                                if state.settingsManager.settings.smoothGlucose {
+                                    Text(
+                                        "Displays real-time glucose readings from your CGM that were smoothed using the Savatzky-Golay filter. The displayed glucose readings may not match the actual readings from your CGM."
+                                    )
+                                    Text(
+                                        "Depending on your user interface settings, this may be displayed in a static (red, green, orange) or dynamic (full color spectrum) coloring scheme."
+                                    )
+                                } else {
+                                    Text(
+                                        "Displays real-time glucose readings from your CGM. Depending on your user interface settings, this may be displayed in a static (red, green, orange) or dynamic (full color spectrum) coloring scheme."
+                                    )
+                                }
                                 Text(
                                     "To modify how glucose readings are displayed, go to Settings > Features > User Interface > Glucose Color Scheme."
                                 )
+                                if state.settingsManager.settings.smoothGlucose {
+                                    Text(
+                                        "To disable smoothing, go to Settings > Devices > Continuous Glucose Monitor > Smooth Glucose Value and toggle off the setting."
+                                    )
+                                }
                             },
                             color: Color.green,
-                            iconString: !state.settingsManager.settings.smoothGlucose ? "circle.fill" : "record.circle.fill"
+                            iconString: state.settingsManager.settings.smoothGlucose ? "record.circle.fill" : "circle.fill"
                         )
 
                         DefinitionRow(

+ 6 - 0
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -137,6 +137,12 @@ extension MainChartView {
                     viewContext: context
                 )
 
+                GlucoseTargetsView(
+                    startMarker: startMarker,
+                    units: state.units,
+                    bgTargets: state.bgTargets
+                )
+
                 GlucoseChartView(
                     glucoseData: state.glucoseFromPersistence,
                     units: state.units,

+ 1 - 1
FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift

@@ -42,7 +42,7 @@ struct LoopView: View {
             }
         }
         .strikethrough(!closedLoop || manualTempBasal, pattern: .solid, color: color)
-        .font(.system(size: 16, weight: .bold, design: .rounded))
+        .font(.callout).fontWeight(.bold).fontDesign(.rounded)
         .foregroundColor(color)
     }
 

+ 11 - 8
FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift

@@ -44,23 +44,23 @@ struct PumpView: View {
                 if let reservoir = reservoir {
                     HStack {
                         Image(systemName: "cross.vial.fill")
-                            .font(.system(size: 16))
+                            .font(.callout)
                             .foregroundColor(reservoirColor)
                         if reservoir == 0xDEAD_BEEF {
                             Text("50+ " + NSLocalizedString("U", comment: "Insulin unit"))
-                                .font(.system(size: 15, design: .rounded))
+                                .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                         } else {
                             Text(
                                 Formatter.integerFormatter
                                     .string(from: reservoir as NSNumber)! + NSLocalizedString(" U", comment: "Insulin unit")
                             )
-                            .font(.system(size: 16, design: .rounded))
+                            .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                         }
                     }
 
                     if let timeZone = timeZone, timeZone.secondsFromGMT() != TimeZone.current.secondsFromGMT() {
                         Image(systemName: "clock.badge.exclamationmark.fill")
-                            .font(.system(size: 16))
+                            .font(.callout)
                             .symbolRenderingMode(.palette)
                             .foregroundStyle(.red, Color(.warning))
                     }
@@ -69,20 +69,23 @@ struct PumpView: View {
                 if (battery.first?.display) != nil, let shouldBatteryDisplay = battery.first?.display, shouldBatteryDisplay {
                     HStack {
                         Image(systemName: "battery.100")
-                            .font(.system(size: 16))
+                            .font(.callout)
                             .foregroundColor(batteryColor)
-                        Text("\(Int(battery.first?.percent ?? 100)) %").font(.system(size: 16, design: .rounded))
+                        Text("\(Int(battery.first?.percent ?? 100)) %")
+                            .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
                 }
 
                 if let date = expiresAtDate {
                     HStack {
                         Image(systemName: "stopwatch.fill")
-                            .font(.system(size: 16))
+                            .font(.callout)
                             .foregroundColor(timerColor)
 
                         Text(remainingTimeString(time: date.timeIntervalSince(timerDate)))
-                            .font(.system(size: 16, design: .rounded))
+                            .font(!(date.timeIntervalSince(timerDate) > 0) ? .subheadline : .callout)
+                            .fontWeight(.bold)
+                            .fontDesign(.rounded)
                     }
                 }
             }

+ 22 - 22
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -337,7 +337,7 @@ extension Home {
                     let bg = eventualBG as Decimal
                     HStack {
                         Image(systemName: "arrow.right.circle")
-                            .font(.system(size: 16, weight: .bold))
+                            .font(.callout).fontWeight(.bold)
                         Text(
                             Formatter.decimalFormatterWithTwoFractionDigits.string(
                                 from: (
@@ -345,15 +345,14 @@ extension Home {
                                         .asMmolL : bg
                                 ) as NSNumber
                             )!
-                        )
-                        .font(.system(size: 16))
+                        ).font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
                 } else {
                     HStack {
                         Image(systemName: "arrow.right.circle")
-                            .font(.system(size: 16, weight: .bold))
+                            .font(.callout).fontWeight(.bold)
                         Text("--")
-                            .font(.system(size: 16))
+                            .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
                 }
             }
@@ -363,7 +362,7 @@ extension Home {
             HStack {
                 HStack {
                     Image(systemName: "syringe.fill")
-                        .font(.system(size: 16))
+                        .font(.callout)
                         .foregroundColor(Color.insulin)
                     Text(
                         (
@@ -372,14 +371,14 @@ extension Home {
                         ) +
                             NSLocalizedString(" U", comment: "Insulin unit")
                     )
-                    .font(.system(size: 16, weight: .bold, design: .rounded))
+                    .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                 }
 
                 Spacer()
 
                 HStack {
                     Image(systemName: "fork.knife")
-                        .font(.system(size: 16))
+                        .font(.callout)
                         .foregroundColor(.loopYellow)
                     Text(
                         (
@@ -389,7 +388,7 @@ extension Home {
                         ) +
                             NSLocalizedString(" g", comment: "gram of carbs")
                     )
-                    .font(.system(size: 16, weight: .bold, design: .rounded))
+                    .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                 }
 
                 Spacer()
@@ -397,19 +396,20 @@ extension Home {
                 HStack {
                     if state.pumpSuspended {
                         Text("Pump suspended")
-                            .font(.system(size: 12, weight: .bold, design: .rounded)).foregroundColor(.loopGray)
+                            .font(.callout).fontWeight(.bold).fontDesign(.rounded)
+                            .foregroundColor(.loopGray)
                     } else if let tempBasalString = tempBasalString {
                         Image(systemName: "drop.circle")
-                            .font(.system(size: 16))
+                            .font(.callout)
                             .foregroundColor(.insulinTintColor)
                         Text(tempBasalString)
-                            .font(.system(size: 16, weight: .bold, design: .rounded))
+                            .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     } else {
                         Image(systemName: "drop.circle")
-                            .font(.system(size: 16))
+                            .font(.callout)
                             .foregroundColor(.insulinTintColor)
                         Text("No Data")
-                            .font(.system(size: 16, weight: .bold, design: .rounded))
+                            .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
                 }
                 if state.totalInsulinDisplayType == .totalDailyDose {
@@ -423,7 +423,7 @@ extension Home {
                             ) +
                             NSLocalizedString(" U", comment: "Insulin unit")
                     )
-                    .font(.system(size: 16, weight: .bold, design: .rounded))
+                    .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                 } else {
                     Spacer()
                     HStack {
@@ -431,7 +431,7 @@ extension Home {
                             "TINS: \(state.roundedTotalBolus)" +
                                 NSLocalizedString(" U", comment: "Unit in number of units delivered (keep the space character!)")
                         )
-                        .font(.system(size: 16, weight: .bold, design: .rounded))
+                        .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                         .onChange(of: state.hours) {
                             state.roundedTotalBolus = state.calculateTINS()
                         }
@@ -448,7 +448,7 @@ extension Home {
         @ViewBuilder func adjustmentsOverrideView(_ overrideString: String) -> some View {
             Group {
                 Image(systemName: "clock.arrow.2.circlepath")
-                    .font(.system(size: 20))
+                    .font(.title2)
                     .foregroundStyle(Color.primary, Color.purple)
                 VStack(alignment: .leading) {
                     Text(latestOverride.first?.name ?? "Custom Override")
@@ -467,7 +467,7 @@ extension Home {
         @ViewBuilder func adjustmentsTempTargetView(_ tempTargetString: String) -> some View {
             Group {
                 Image(systemName: "target")
-                    .font(.system(size: 20))
+                    .font(.title2)
                     .foregroundStyle(Color.loopGreen)
                 VStack(alignment: .leading) {
                     Text(latestTempTarget.first?.name ?? "Temp Target")
@@ -483,7 +483,7 @@ extension Home {
 
         @ViewBuilder func adjustmentsCancelView(_ cancelAction: @escaping () -> Void) -> some View {
             Image(systemName: "xmark.app")
-                .font(.system(size: 24))
+                .font(.title)
                 .onTapGesture {
                     cancelAction()
                 }
@@ -491,7 +491,7 @@ extension Home {
 
         @ViewBuilder func adjustmentsCancelTempTargetView() -> some View {
             Image(systemName: "xmark.app")
-                .font(.system(size: 24))
+                .font(.title)
                 .confirmationDialog(
                     "Stop the Temp Target \"\(latestTempTarget.first?.name ?? "")\"?",
                     isPresented: $isConfirmStopTempTargetShown,
@@ -515,7 +515,7 @@ extension Home {
 
         @ViewBuilder func adjustmentsCancelOverrideView() -> some View {
             Image(systemName: "xmark.app")
-                .font(.system(size: 24))
+                .font(.title)
                 .confirmationDialog(
                     "Stop the Override \"\(latestOverride.first?.name ?? "")\"?",
                     isPresented: $isConfirmStopOverridePresented,
@@ -552,7 +552,7 @@ extension Home {
 
                 /// to ensure the same position....
                 Image(systemName: "xmark.app")
-                    .font(.system(size: 25))
+                    .font(.title)
                     // clear color for the icon
                     .foregroundStyle(Color.clear)
             }.onTapGesture {

+ 1 - 1
FreeAPS/Sources/Modules/LiveActivitySettings/View/LiveActivityWidgetConfiguration.swift

@@ -163,7 +163,7 @@ struct LiveActivityWidgetConfiguration: BaseView {
                         .foregroundColor(Color(UIColor.systemGray2))
                         .background(Color.white)
                         .clipShape(Circle())
-                        .font(.system(size: 20))
+                        .font(.title3)
                 }
                 .offset(x: 10, y: -10)
                 .confirmationDialog("Remove Widget", isPresented: $isRemovalConfirmationPresented, titleVisibility: .hidden) {

+ 17 - 23
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -192,34 +192,28 @@ extension Settings {
                     Section(
                         header: Text("Search Results"),
                         content: {
-                            ForEach(filteredItems) { filteredItem in
-                                VStack(alignment: .leading) {
-                                    Text(filteredItem.matchedContent).bold()
-                                    if let path = filteredItem.settingItem.path {
-                                        Text(path.map(\.stringValue).joined(separator: " > "))
-                                            .font(.caption)
-                                            .foregroundColor(.secondary)
-                                    }
-                                }.navigationLink(to: filteredItem.settingItem.view, from: self)
+                            if filteredItems.isNotEmpty {
+                                ForEach(filteredItems) { filteredItem in
+                                    VStack(alignment: .leading) {
+                                        Text(filteredItem.matchedContent).bold()
+                                        if let path = filteredItem.settingItem.path {
+                                            Text(path.map(\.stringValue).joined(separator: " > "))
+                                                .font(.caption)
+                                                .foregroundColor(.secondary)
+                                        }
+                                    }.navigationLink(to: filteredItem.settingItem.view, from: self)
+                                }
+                            } else {
+                                Text("No settings matching your search query")
+                                    +
+                                    Text(" »\(searchText)« ").bold()
+                                    +
+                                    Text("found.")
                             }
                         }
                     ).listRowBackground(Color.chart)
                 }
 
-//                Section {
-//                    Text("Targets")
-//                        .navigationLink(to: .configEditor(file: OpenAPS.Settings.bgTargets), from: self)
-//                    Text("Sensitivities")
-//                        .navigationLink(to: .configEditor(file: OpenAPS.Settings.insulinSensitivities), from: self)
-//                    Text("Profile")
-//                        .navigationLink(to: .configEditor(file: OpenAPS.Settings.profile), from: self)
-//                    Text("Preferences")
-//                        .navigationLink(
-//                            to: .configEditor(file: OpenAPS.Settings.preferences),
-//                            from: self
-//                        )
-//                }.listRowBackground(Color.chart)
-
                 // TODO: remove this more or less entirely; add build-time flag to enable Middleware; add settings export feature
 //                Section {
 //                    Toggle("Developer Options", isOn: $state.debugOptions)

+ 1 - 1
FreeAPS/Sources/Modules/Treatments/TreatmentsDataFlow.swift

@@ -6,6 +6,6 @@ protocol TreatmentsProvider: Provider {
     func getPumpSettings() async -> PumpSettings
     func getBasalProfile() async -> [BasalProfileEntry]
     func getCarbRatios() async -> CarbRatios
-    func getBGTarget() async -> BGTargets
+    func getBGTargets() async -> BGTargets
     func getISFValues() async -> InsulinSensitivities
 }

+ 1 - 1
FreeAPS/Sources/Modules/Treatments/TreatmentsProvider.swift

@@ -18,7 +18,7 @@ extension Treatments {
                 ?? CarbRatios(units: .grams, schedule: [])
         }
 
-        func getBGTarget() async -> BGTargets {
+        func getBGTargets() async -> BGTargets {
             await storage.retrieveAsync(OpenAPS.Settings.bgTargets, as: BGTargets.self)
                 ?? BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
                 ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])

+ 1 - 1
FreeAPS/Sources/Modules/Treatments/TreatmentsStateModel.swift

@@ -303,7 +303,7 @@ extension Treatments {
                 let carbRatios = await provider.getCarbRatios()
                 entries = carbRatios.schedule.map { ($0.start, $0.ratio) }
             case .bgTarget:
-                let bgTargets = await provider.getBGTarget()
+                let bgTargets = await provider.getBGTargets()
                 entries = bgTargets.targets.map { ($0.start, $0.low) }
             case .isf:
                 let isfValues = await provider.getISFValues()

+ 2 - 2
FreeAPS/Sources/Modules/Treatments/View/MealPreset/MealPresetView.swift

@@ -290,7 +290,7 @@ struct MealPresetView: View {
             if carbs == 0, fat == 0, protein == 0 { state.summation = [] }
         }
         label: { Image(systemName: "minus.circle.fill")
-            .font(.system(size: 20))
+            .font(.title3)
         }
         .disabled(
             state
@@ -315,7 +315,7 @@ struct MealPresetView: View {
             state.addPresetToNewMeal()
         }
         label: { Image(systemName: "plus.circle.fill")
-            .font(.system(size: 20))
+            .font(.title3)
         }
         .disabled(state.selection == nil)
         .buttonStyle(.borderless)