ソースを参照

Refactor forecast display setting; add condition for carbs required

Deniz Cengiz 1 年間 前
コミット
6254b43383

+ 8 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -457,6 +457,8 @@
 		DD57C4D32C4C7103001A5B28 /* StatsData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD57C4B12C4C7103001A5B28 /* StatsData+CoreDataProperties.swift */; };
 		DD68889D2C386E17006E3C44 /* NightscoutExercise.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD68889C2C386E17006E3C44 /* NightscoutExercise.swift */; };
 		DD6B7CB22C7B6F0800B75029 /* Rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B7CB12C7B6F0800B75029 /* Rounding.swift */; };
+		DD6B7CB42C7B71F700B75029 /* ForecastDisplayType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B7CB32C7B71F700B75029 /* ForecastDisplayType.swift */; };
+		DD6B7CB62C7B748B00B75029 /* TotalInsulinDisplayType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B7CB52C7B748B00B75029 /* TotalInsulinDisplayType.swift */; };
 		DD88C8E22C50420800F2D558 /* DefinitionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD88C8E12C50420800F2D558 /* DefinitionRow.swift */; };
 		DDD163122C4C689900CD525A /* OverrideStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163112C4C689900CD525A /* OverrideStateModel.swift */; };
 		DDD163142C4C68D300CD525A /* OverrideProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163132C4C68D300CD525A /* OverrideProvider.swift */; };
@@ -1098,6 +1100,8 @@
 		DD57C4B12C4C7103001A5B28 /* StatsData+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatsData+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
 		DD68889C2C386E17006E3C44 /* NightscoutExercise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutExercise.swift; sourceTree = "<group>"; };
 		DD6B7CB12C7B6F0800B75029 /* Rounding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Rounding.swift; sourceTree = "<group>"; };
+		DD6B7CB32C7B71F700B75029 /* ForecastDisplayType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastDisplayType.swift; sourceTree = "<group>"; };
+		DD6B7CB52C7B748B00B75029 /* TotalInsulinDisplayType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalInsulinDisplayType.swift; sourceTree = "<group>"; };
 		DD88C8E12C50420800F2D558 /* DefinitionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefinitionRow.swift; sourceTree = "<group>"; };
 		DDD163112C4C689900CD525A /* OverrideStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideStateModel.swift; sourceTree = "<group>"; };
 		DDD163132C4C68D300CD525A /* OverrideProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideProvider.swift; sourceTree = "<group>"; };
@@ -1888,6 +1892,8 @@
 				583684072BD195A700070A60 /* Determination.swift */,
 				BDC2EA462C3045AD00E5BBD0 /* Override.swift */,
 				DD21FCB42C6952AD00AF2C25 /* DecimalPickerSettings.swift */,
+				DD6B7CB32C7B71F700B75029 /* ForecastDisplayType.swift */,
+				DD6B7CB52C7B748B00B75029 /* TotalInsulinDisplayType.swift */,
 			);
 			path = Models;
 			sourceTree = "<group>";
@@ -3170,6 +3176,7 @@
 				3811DE4125C9D4A100A708ED /* SettingsRootView.swift in Sources */,
 				38192E04261B82FA0094D973 /* ReachabilityManager.swift in Sources */,
 				38E44539274E411700EC9A94 /* Disk+UIImage.swift in Sources */,
+				DD6B7CB62C7B748B00B75029 /* TotalInsulinDisplayType.swift in Sources */,
 				388E595C25AD948C0019842D /* FreeAPSApp.swift in Sources */,
 				38FEF3FC2737E53800574A46 /* MainStateModel.swift in Sources */,
 				DD1745352C55AE7E00211FAC /* TargetBehavoirRootView.swift in Sources */,
@@ -3439,6 +3446,7 @@
 				38E4453D274E411700EC9A94 /* Disk+Errors.swift in Sources */,
 				38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */,
 				CE7CA3542A064973004BE681 /* TempPresetsIntentRequest.swift in Sources */,
+				DD6B7CB42C7B71F700B75029 /* ForecastDisplayType.swift in Sources */,
 				F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */,
 				DD17453A2C55BFA600211FAC /* AlgorithmAdvancedSettingsDataFlow.swift in Sources */,
 				5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */,

+ 1 - 1
FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json

@@ -41,7 +41,7 @@
   "yGridLines" : true,
   "oneDimensionalGraph" : false,
   "rulerMarks" : true,
-  "displayForecastsAsLines": false,
+  "forecastDisplayType": "cone",
   "maxCarbs": 250,
   "maxFat": 250,
   "maxProtein": 250,

+ 16 - 0
FreeAPS/Sources/Models/ForecastDisplayType.swift

@@ -0,0 +1,16 @@
+import Foundation
+
+enum ForecastDisplayType: String, JSON, CaseIterable, Identifiable, Codable, Hashable {
+    var id: String { rawValue }
+    case cone
+    case lines
+    var displayName: String {
+        switch self {
+        case .cone:
+            return NSLocalizedString("Cone", comment: "")
+
+        case .lines:
+            return NSLocalizedString("Lines", comment: "")
+        }
+    }
+}

+ 3 - 3
FreeAPS/Sources/Models/FreeAPSSettings.swift

@@ -57,7 +57,7 @@ struct FreeAPSSettings: JSON, Equatable {
     var yGridLines: Bool = true
     var oneDimensionalGraph: Bool = false
     var rulerMarks: Bool = true
-    var displayForecastsAsLines: Bool = false
+    var forecastDisplayType: ForecastDisplayType = .cone
     var maxCarbs: Decimal = 250
     var maxFat: Decimal = 250
     var maxProtein: Decimal = 250
@@ -266,8 +266,8 @@ extension FreeAPSSettings: Decodable {
             settings.rulerMarks = rulerMarks
         }
 
-        if let displayForecastsAsLines = try? container.decode(Bool.self, forKey: .displayForecastsAsLines) {
-            settings.displayForecastsAsLines = displayForecastsAsLines
+        if let forecastDisplayType = try? container.decode(ForecastDisplayType.self, forKey: .forecastDisplayType) {
+            settings.forecastDisplayType = forecastDisplayType
         }
 
         if let overrideHbA1cUnit = try? container.decode(Bool.self, forKey: .overrideHbA1cUnit) {

+ 22 - 0
FreeAPS/Sources/Models/TotalInsulinDisplayType.swift

@@ -0,0 +1,22 @@
+//
+//  TotalInsulinDisplayType.swift
+//  FreeAPS
+//
+//  Created by Cengiz Deniz on 25.08.24.
+//
+import Foundation
+
+enum TotalInsulinDisplayType: String, JSON, CaseIterable, Identifiable, Codable, Hashable {
+    var id: String { rawValue }
+    case totalDailyDose
+    case totalInsulinInScope
+
+    var displayName: String {
+        switch self {
+        case .totalDailyDose:
+            return NSLocalizedString("Total Daily Dose", comment: "")
+        case .totalInsulinInScope:
+            return NSLocalizedString("Total Insulin in Scope", comment: "")
+        }
+    }
+}

+ 2 - 2
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -108,7 +108,7 @@ extension Bolus {
         @Published var minForecast: [Int] = []
         @Published var maxForecast: [Int] = []
         @Published var minCount: Int = 12 // count of Forecasts drawn in 5 min distances, i.e. 12 means a min of 1 hour
-        @Published var displayForecastsAsLines: Bool = false
+        @Published var forecastDisplayType: ForecastDisplayType = .cone
         @Published var smooth: Bool = false
 
         let now = Date.now
@@ -148,7 +148,7 @@ extension Bolus {
             sweetMealFactor = settings.settings.sweetMealFactor
             displayPresets = settings.settings.displayPresets
 
-            displayForecastsAsLines = settings.settings.displayForecastsAsLines
+            forecastDisplayType = settings.settings.forecastDisplayType
 
             lowGlucose = units == .mgdL ? settingsManager.settings.low : settingsManager.settings.low.asMmolL
             highGlucose = units == .mgdL ? settingsManager.settings.high : settingsManager.settings.high.asMmolL

+ 2 - 2
FreeAPS/Sources/Modules/Bolus/View/ForeCastChart.swift

@@ -12,7 +12,7 @@ struct ForeCastChart: View {
 
     private var endMarker: Date {
         state
-            .displayForecastsAsLines ? Date(timeIntervalSinceNow: TimeInterval(hours: 3)) :
+            .forecastDisplayType == .lines ? Date(timeIntervalSinceNow: TimeInterval(hours: 3)) :
             Date(timeIntervalSinceNow: TimeInterval(
                 Int(1.5) * 5 * state
                     .minCount * 60
@@ -70,7 +70,7 @@ struct ForeCastChart: View {
             drawGlucose()
             drawCurrentTimeMarker()
 
-            if state.displayForecastsAsLines {
+            if state.forecastDisplayType == .lines {
                 drawForecastLines()
             } else {
                 drawForecastsCone()

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

@@ -86,7 +86,7 @@ extension Home {
         @Published var minForecast: [Int] = []
         @Published var maxForecast: [Int] = []
         @Published var minCount: Int = 12 // count of Forecasts drawn in 5 min distances, i.e. 12 means a min of 1 hour
-        @Published var displayForecastsAsLines: Bool = false
+        @Published var forecastDisplayType: ForecastDisplayType = .cone
 
         let context = CoreDataStack.shared.newTaskContext()
         let viewContext = CoreDataStack.shared.persistentContainer.viewContext
@@ -137,7 +137,7 @@ extension Home {
             cgmAvailable = fetchGlucoseManager.cgmGlucoseSourceType != CGMType.none
             showCarbsRequiredBadge = settingsManager.settings.showCarbsRequiredBadge
 
-            displayForecastsAsLines = settingsManager.settings.displayForecastsAsLines
+            forecastDisplayType = settingsManager.settings.forecastDisplayType
 
             broadcaster.register(GlucoseObserver.self, observer: self)
             broadcaster.register(DeterminationObserver.self, observer: self)
@@ -454,7 +454,7 @@ extension Home.StateModel:
         thresholdLines = settingsManager.settings.rulerMarks
         totalInsulinDisplayType = settingsManager.settings.totalInsulinDisplayType
         showCarbsRequiredBadge = settingsManager.settings.showCarbsRequiredBadge
-        displayForecastsAsLines = settingsManager.settings.displayForecastsAsLines
+        forecastDisplayType = settingsManager.settings.forecastDisplayType
         cgmAvailable = (fetchGlucoseManager.cgmGlucoseSourceType != CGMType.none)
         displayPumpStatusHighlightMessage()
         setupBatteryArray()

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

@@ -218,8 +218,8 @@ extension Backport {
     }
 
     @ViewBuilder func chartForegroundStyleScale(state: any StateModel) -> some View {
-        if (state as? Bolus.StateModel)?.displayForecastsAsLines == true ||
-            (state as? Home.StateModel)?.displayForecastsAsLines == true
+        if (state as? Bolus.StateModel)?.forecastDisplayType == ForecastDisplayType.lines ||
+            (state as? Home.StateModel)?.forecastDisplayType == ForecastDisplayType.lines
         {
             let modifiedContent = content
                 .chartForegroundStyleScale([
@@ -306,7 +306,7 @@ extension MainChartView {
                 drawManualGlucose()
                 drawCarbs()
 
-                if state.displayForecastsAsLines {
+                if state.forecastDisplayType == .lines {
                     drawForecastsLines()
                 } else {
                     drawForecastsCone()
@@ -1165,7 +1165,7 @@ extension MainChartView {
         ))
 
         endMarker = state
-            .displayForecastsAsLines ? threeHourSinceNow : dynamicFutureDateForCone <= threeHourSinceNow ?
+            .forecastDisplayType == .lines ? threeHourSinceNow : dynamicFutureDateForCone <= threeHourSinceNow ?
             dynamicFutureDateForCone.addingTimeInterval(TimeInterval(minutes: 30)) : threeHourSinceNow
     }
 

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

@@ -793,7 +793,7 @@ extension Home {
                     .font(.subheadline)
                     .foregroundColor(.secondary)
 
-                    if state.settingsManager.settings.displayForecastsAsLines {
+                    if state.forecastDisplayType == .lines {
                         List {
                             DefinitionRow(
                                 term: "IOB (Insulin on Board)",
@@ -849,7 +849,7 @@ extension Home {
             ZStack(alignment: .bottom) {
                 TabView(selection: $selectedTab) {
                     let carbsRequiredBadge: String? = {
-                        guard let carbsRequired = state.enactedAndNonEnactedDeterminations.first?.carbsRequired else {
+                        guard let carbsRequired = state.enactedAndNonEnactedDeterminations.first?.carbsRequired, state.showCarbsRequiredBadge else {
                             return nil
                         }
                         let carbsRequiredDecimal = Decimal(carbsRequired)

+ 3 - 15
FreeAPS/Sources/Modules/UserInterfaceSettings/UserInterfaceSettingsStateModel.swift

@@ -10,6 +10,7 @@ extension UserInterfaceSettings {
         @Published var yGridLines: Bool = false
         @Published var oneDimensionalGraph = false
         @Published var rulerMarks: Bool = true
+        @Published var forecastDisplayType: ForecastDisplayType = .cone
         @Published var totalInsulinDisplayType: TotalInsulinDisplayType = .totalDailyDose
         @Published var showCarbsRequiredBadge: Bool = true
         @Published var carbsRequiredThreshold: Decimal = 0
@@ -26,6 +27,8 @@ extension UserInterfaceSettings {
             subscribeSetting(\.rulerMarks, on: $rulerMarks) { rulerMarks = $0 }
             subscribeSetting(\.oneDimensionalGraph, on: $oneDimensionalGraph) { oneDimensionalGraph = $0 }
 
+            subscribeSetting(\.forecastDisplayType, on: $forecastDisplayType) { forecastDisplayType = $0 }
+
             subscribeSetting(\.totalInsulinDisplayType, on: $totalInsulinDisplayType) { totalInsulinDisplayType = $0 }
 
             subscribeSetting(\.low, on: $low) { low = $0 }
@@ -47,18 +50,3 @@ extension UserInterfaceSettings.StateModel: SettingsObserver {
         units = settingsManager.settings.units
     }
 }
-
-enum TotalInsulinDisplayType: String, JSON, CaseIterable, Identifiable, Codable, Hashable {
-    var id: String { rawValue }
-    case totalDailyDose
-    case totalInsulinInScope
-
-    var displayName: String {
-        switch self {
-        case .totalDailyDose:
-            return NSLocalizedString("Total Daily Dose", comment: "")
-        case .totalInsulinInScope:
-            return NSLocalizedString("Total Insulin in Scope", comment: "")
-        }
-    }
-}

+ 92 - 55
FreeAPS/Sources/Modules/UserInterfaceSettings/View/UserInterfaceSettingsRootView.swift

@@ -103,89 +103,126 @@ extension UserInterfaceSettings {
                     verboseHint: "Display Low and High Thresholds… bla bla bla"
                 )
 
-                Section {
-                    VStack {
+                if state.rulerMarks {
+                    Section {
                         VStack {
-                            HStack {
-                                Text("Low Threshold")
+                            VStack {
+                                HStack {
+                                    Text("Low Threshold")
 
-                                Spacer()
+                                    Spacer()
 
-                                Group {
-                                    Text(state.units == .mgdL ? state.low.description : state.low.asMmolL.description)
-                                        .foregroundColor(!displayPickerLowThreshold ? .primary : .accentColor)
+                                    Group {
+                                        Text(state.units == .mgdL ? state.low.description : state.low.asMmolL.description)
+                                            .foregroundColor(!displayPickerLowThreshold ? .primary : .accentColor)
 
-                                    Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
+                                        Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
+                                    }
+                                }
+                                .onTapGesture {
+                                    displayPickerLowThreshold.toggle()
                                 }
                             }
-                            .onTapGesture {
-                                displayPickerLowThreshold.toggle()
-                            }
-                        }
-                        .padding(.top)
+                            .padding(.top)
 
-                        if displayPickerLowThreshold {
-                            let setting = PickerSettingsProvider.shared.settings.low
+                            if displayPickerLowThreshold {
+                                let setting = PickerSettingsProvider.shared.settings.low
 
-                            Picker(selection: $state.low, label: Text("")) {
-                                ForEach(
-                                    PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
-                                    id: \.self
-                                ) { value in
-                                    let displayValue = state.units == .mgdL ? value : value.asMmolL
-                                    Text("\(displayValue.description)").tag(value)
+                                Picker(selection: $state.low, label: Text("")) {
+                                    ForEach(
+                                        PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
+                                        id: \.self
+                                    ) { value in
+                                        let displayValue = state.units == .mgdL ? value : value.asMmolL
+                                        Text("\(displayValue.description)").tag(value)
+                                    }
                                 }
+                                .pickerStyle(WheelPickerStyle())
+                                .frame(maxWidth: .infinity)
                             }
-                            .pickerStyle(WheelPickerStyle())
-                            .frame(maxWidth: .infinity)
-                        }
 
-                        VStack {
-                            HStack {
-                                Text("High Threshold")
+                            VStack {
+                                HStack {
+                                    Text("High Threshold")
 
-                                Spacer()
+                                    Spacer()
 
-                                Group {
-                                    Text(state.units == .mgdL ? state.high.description : state.high.asMmolL.description)
-                                        .foregroundColor(!displayPickerHighThreshold ? .primary : .accentColor)
+                                    Group {
+                                        Text(state.units == .mgdL ? state.high.description : state.high.asMmolL.description)
+                                            .foregroundColor(!displayPickerHighThreshold ? .primary : .accentColor)
 
-                                    Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
+                                        Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
+                                    }
+                                }
+                                .onTapGesture {
+                                    displayPickerHighThreshold.toggle()
                                 }
                             }
-                            .onTapGesture {
-                                displayPickerHighThreshold.toggle()
-                            }
-                        }
-                        .padding(.top)
+                            .padding(.top)
 
-                        if displayPickerHighThreshold {
-                            let setting = PickerSettingsProvider.shared.settings.high
-                            Picker(selection: $state.high, label: Text("")) {
-                                ForEach(
-                                    PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
-                                    id: \.self
-                                ) { value in
-                                    let displayValue = state.units == .mgdL ? value : value.asMmolL
-                                    Text("\(displayValue.description)").tag(value)
+                            if displayPickerHighThreshold {
+                                let setting = PickerSettingsProvider.shared.settings.high
+                                Picker(selection: $state.high, label: Text("")) {
+                                    ForEach(
+                                        PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
+                                        id: \.self
+                                    ) { value in
+                                        let displayValue = state.units == .mgdL ? value : value.asMmolL
+                                        Text("\(displayValue.description)").tag(value)
+                                    }
                                 }
+                                .pickerStyle(WheelPickerStyle())
+                                .frame(maxWidth: .infinity)
                             }
-                            .pickerStyle(WheelPickerStyle())
-                            .frame(maxWidth: .infinity)
-                        }
+
+                            HStack(alignment: .top) {
+                                Text(
+                                    "Sets thresholds for low and high glucose in home view main chart and statistics view."
+                                )
+                                .lineLimit(nil)
+                                .font(.footnote)
+                                .foregroundColor(.secondary)
+
+                                Spacer()
+                                Button(
+                                    action: {
+                                        hintLabel = "Low and High Thresholds"
+                                        selectedVerboseHint = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
+                                        shouldDisplayHint.toggle()
+                                    },
+                                    label: {
+                                        HStack {
+                                            Image(systemName: "questionmark.circle")
+                                        }
+                                    }
+                                ).buttonStyle(BorderlessButtonStyle())
+                            }.padding(.top)
+                        }.padding(.bottom)
+                    }.listRowBackground(Color.chart)
+                }
+
+                Section {
+                    VStack {
+                        Picker(
+                            selection: $state.forecastDisplayType,
+                            label: Text("Forecast Display Type")
+                        ) {
+                            ForEach(ForecastDisplayType.allCases) { selection in
+                                Text(selection.displayName).tag(selection)
+                            }
+                        }.padding(.top)
 
                         HStack(alignment: .top) {
                             Text(
-                                "Sets thresholds for low and high glucose in home view main chart and statistics view."
+                                "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
                             )
-                            .lineLimit(nil)
                             .font(.footnote)
                             .foregroundColor(.secondary)
-
+                            .lineLimit(nil)
                             Spacer()
                             Button(
                                 action: {
-                                    hintLabel = "Low and High Thresholds"
+                                    hintLabel = "Forecast Display Type"
                                     selectedVerboseHint = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
                                     shouldDisplayHint.toggle()
                                 },

+ 1 - 1
FreeAPS/Sources/Services/UserNotifications/UserNotificationsManager.swift

@@ -129,7 +129,7 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
     }
 
     private func notifyCarbsRequired(_ carbs: Int) {
-        guard Decimal(carbs) >= settingsManager.settings.carbsRequiredThreshold else { return }
+        guard Decimal(carbs) >= settingsManager.settings.carbsRequiredThreshold, settingsManager.settings.showCarbsRequiredBadge else { return }
 
         ensureCanSendNotification {
             var titles: [String] = []