Просмотр исходного кода

Change all decimal settings to picker

Deniz Cengiz 1 год назад
Родитель
Сommit
c294f5347e

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -419,6 +419,7 @@
 		DD1745522C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745512C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift */; };
 		DD1745552C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745542C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift */; };
 		DD1DB7CC2BECCA1F0048B367 /* BuildDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */; };
+		DD21FCB52C6952AD00AF2C25 /* DecimalPickerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD21FCB42C6952AD00AF2C25 /* DecimalPickerSettings.swift */; };
 		DD399FB31EACB9343C944C4C /* PreferencesEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3E609094E064C99A4752C /* PreferencesEditorStateModel.swift */; };
 		DD57C4B22C4C7103001A5B28 /* LoopStatRecord+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD57C4902C4C7103001A5B28 /* LoopStatRecord+CoreDataClass.swift */; };
 		DD57C4B32C4C7103001A5B28 /* LoopStatRecord+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD57C4912C4C7103001A5B28 /* LoopStatRecord+CoreDataProperties.swift */; };
@@ -1060,6 +1061,7 @@
 		DD1745512C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsLimitsSettingsStateModel.swift; sourceTree = "<group>"; };
 		DD1745542C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsLimitsSettingsRootView.swift; sourceTree = "<group>"; };
 		DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildDetails.swift; sourceTree = "<group>"; };
+		DD21FCB42C6952AD00AF2C25 /* DecimalPickerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecimalPickerSettings.swift; sourceTree = "<group>"; };
 		DD57C4902C4C7103001A5B28 /* LoopStatRecord+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoopStatRecord+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; };
 		DD57C4912C4C7103001A5B28 /* LoopStatRecord+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoopStatRecord+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
 		DD57C4922C4C7103001A5B28 /* MealPresetStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MealPresetStored+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; };
@@ -1884,6 +1886,7 @@
 				BDF530D72B40F8AC002CAF43 /* LockScreenView.swift */,
 				583684072BD195A700070A60 /* Determination.swift */,
 				BDC2EA462C3045AD00E5BBD0 /* Override.swift */,
+				DD21FCB42C6952AD00AF2C25 /* DecimalPickerSettings.swift */,
 			);
 			path = Models;
 			sourceTree = "<group>";
@@ -3285,6 +3288,7 @@
 				3811DE2225C9D48300A708ED /* MainProvider.swift in Sources */,
 				3811DE0C25C9D32F00A708ED /* BaseProvider.swift in Sources */,
 				CE95BF5A2BA62E4A00DC3DE3 /* PluginSource.swift in Sources */,
+				DD21FCB52C6952AD00AF2C25 /* DecimalPickerSettings.swift in Sources */,
 				3811DE5C25C9D4D500A708ED /* Formatters.swift in Sources */,
 				3871F39F25ED895A0013ECB5 /* Decimal+Extensions.swift in Sources */,
 				CEE9A6592BBB418300EB5194 /* CalibrationsDataFlow.swift in Sources */,

+ 142 - 0
FreeAPS/Sources/Models/DecimalPickerSettings.swift

@@ -0,0 +1,142 @@
+import SwiftUI
+
+class PickerSettingsProvider: ObservableObject {
+    static let shared = PickerSettingsProvider()
+
+    var settings = DecimalPickerSettings()
+
+    private init() {} // Private init to enforce singleton pattern
+
+    // Helper function to generate values for the picker
+    func generatePickerValues(from setting: PickerSetting) -> [Decimal] {
+        var values: [Decimal] = []
+        var currentValue = setting.min
+
+        while currentValue <= setting.max {
+            values.append(currentValue)
+            currentValue += setting.step
+        }
+
+        return values
+    }
+}
+
+struct DecimalPickerSettings {
+    var lowGlucose = PickerSetting(value: 72, step: 1, min: 40, max: 400, type: PickerSetting.PickerSettingType.glucose)
+    var highGlucose = PickerSetting(value: 270, step: 1, min: 100, max: 500, type: PickerSetting.PickerSettingType.glucose)
+    var carbsRequiredThreshold = PickerSetting(value: 10, step: 1, min: 0, max: 100, type: PickerSetting.PickerSettingType.gramms)
+    var individualAdjustmentFactor = PickerSetting(
+        value: 0.5,
+        step: 0.1,
+        min: 0.1,
+        max: 2,
+        type: PickerSetting.PickerSettingType.factor
+    )
+    var high = PickerSetting(value: 180, step: 1, min: 70, max: 400, type: PickerSetting.PickerSettingType.glucose)
+    var low = PickerSetting(value: 70, step: 1, min: 40, max: 100, type: PickerSetting.PickerSettingType.glucose)
+    var maxCarbs = PickerSetting(value: 250, step: 5, min: 0, max: 500, type: PickerSetting.PickerSettingType.gramms)
+    var maxFat = PickerSetting(value: 250, step: 5, min: 0, max: 500, type: PickerSetting.PickerSettingType.gramms)
+    var maxProtein = PickerSetting(value: 250, step: 5, min: 0, max: 500, type: PickerSetting.PickerSettingType.gramms)
+    var overrideFactor = PickerSetting(value: 0.8, step: 0.1, min: 0.5, max: 1.5, type: PickerSetting.PickerSettingType.factor)
+    var fattyMealFactor = PickerSetting(value: 0.7, step: 0.1, min: 0.5, max: 2, type: PickerSetting.PickerSettingType.factor)
+    var sweetMealFactor = PickerSetting(value: 2, step: 0.1, min: 1, max: 5, type: PickerSetting.PickerSettingType.factor)
+    var maxIOB = PickerSetting(value: 0, step: 0.1, min: 0, max: 20, type: PickerSetting.PickerSettingType.insulinUnit)
+    var maxDailySafetyMultiplier = PickerSetting(
+        value: 3,
+        step: 0.1,
+        min: 1,
+        max: 5,
+        type: PickerSetting.PickerSettingType.factor
+    )
+    var currentBasalSafetyMultiplier = PickerSetting(
+        value: 4,
+        step: 0.1,
+        min: 1,
+        max: 5,
+        type: PickerSetting.PickerSettingType.factor
+    )
+    var autosensMax = PickerSetting(value: 1.2, step: 0.1, min: 0.5, max: 2, type: PickerSetting.PickerSettingType.factor)
+    var autosensMin = PickerSetting(value: 0.7, step: 0.1, min: 0.5, max: 1, type: PickerSetting.PickerSettingType.factor)
+    var smbDeliveryRatio = PickerSetting(value: 0.5, step: 0.1, min: 0.1, max: 1, type: PickerSetting.PickerSettingType.factor)
+    var halfBasalExerciseTarget = PickerSetting(
+        value: 160,
+        step: 5,
+        min: 100,
+        max: 200,
+        type: PickerSetting.PickerSettingType.glucose
+    )
+    var maxCOB = PickerSetting(value: 120, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gramms)
+    var min5mCarbimpact = PickerSetting(value: 8, step: 1, min: 0, max: 20, type: PickerSetting.PickerSettingType.gramms)
+    var autotuneISFAdjustmentFraction = PickerSetting(
+        value: 1.0,
+        step: 0.1,
+        min: 0.5,
+        max: 2,
+        type: PickerSetting.PickerSettingType.factor
+    )
+    var remainingCarbsFraction = PickerSetting(
+        value: 1.0,
+        step: 0.1,
+        min: 0.5,
+        max: 2,
+        type: PickerSetting.PickerSettingType.factor
+    )
+    var remainingCarbsCap = PickerSetting(value: 90, step: 5, min: 0, max: 200, type: PickerSetting.PickerSettingType.gramms)
+    var maxSMBBasalMinutes = PickerSetting(value: 30, step: 1, min: 0, max: 60, type: PickerSetting.PickerSettingType.factor)
+    var maxUAMSMBBasalMinutes = PickerSetting(value: 30, step: 1, min: 0, max: 60, type: PickerSetting.PickerSettingType.factor)
+    var smbInterval = PickerSetting(value: 3, step: 0.1, min: 0.5, max: 10, type: PickerSetting.PickerSettingType.factor)
+    var bolusIncrement = PickerSetting(
+        value: 0.1,
+        step: 0.1,
+        min: 0.05,
+        max: 1,
+        type: PickerSetting.PickerSettingType.insulinUnit
+    )
+    var insulinPeakTime = PickerSetting(value: 75, step: 1, min: 30, max: 120, type: PickerSetting.PickerSettingType.factor)
+    var carbsReqThreshold = PickerSetting(value: 1.0, step: 0.1, min: 0, max: 10, type: PickerSetting.PickerSettingType.gramms)
+    var noisyCGMTargetMultiplier = PickerSetting(
+        value: 1.3,
+        step: 0.1,
+        min: 1,
+        max: 2,
+        type: PickerSetting.PickerSettingType.factor
+    )
+    var maxDeltaBGthreshold = PickerSetting(value: 0.2, step: 0.1, min: 0.1, max: 2, type: PickerSetting.PickerSettingType.factor)
+    var adjustmentFactor = PickerSetting(value: 0.8, step: 0.1, min: 0.5, max: 1.5, type: PickerSetting.PickerSettingType.factor)
+    var adjustmentFactorSigmoid = PickerSetting(
+        value: 0.5,
+        step: 0.1,
+        min: 0.5,
+        max: 2,
+        type: PickerSetting.PickerSettingType.factor
+    )
+    var weightPercentage = PickerSetting(value: 0.65, step: 0.1, min: 0.1, max: 1, type: PickerSetting.PickerSettingType.factor)
+    var enableSMB_high_bg_target = PickerSetting(
+        value: 110,
+        step: 1,
+        min: 70,
+        max: 200,
+        type: PickerSetting.PickerSettingType.glucose
+    )
+    var threshold_setting = PickerSetting(value: 65, step: 1, min: 50, max: 100, type: PickerSetting.PickerSettingType.glucose)
+    var updateInterval = PickerSetting(value: 20, step: 1, min: 1, max: 60, type: PickerSetting.PickerSettingType.factor)
+    var delay = PickerSetting(value: 20, step: 1, min: 1, max: 60, type: PickerSetting.PickerSettingType.factor)
+    var minuteInterval = PickerSetting(value: 20, step: 1, min: 1, max: 60, type: PickerSetting.PickerSettingType.factor)
+    var timeCap = PickerSetting(value: 20, step: 1, min: 1, max: 60, type: PickerSetting.PickerSettingType.factor)
+    var hours = PickerSetting(value: 6, step: 1, min: 2, max: 24, type: PickerSetting.PickerSettingType.factor)
+}
+
+struct PickerSetting {
+    var value: Decimal
+    var step: Decimal
+    var min: Decimal
+    var max: Decimal
+    var type: PickerSettingType
+
+    enum PickerSettingType {
+        case glucose
+        case factor
+        case gramms
+        case insulinUnit
+    }
+}

+ 8 - 8
FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift

@@ -57,7 +57,7 @@ extension AlgorithmAdvancedSettings {
                             hintLabel = NSLocalizedString("Max Daily Safety Multiplier", comment: "Max Daily Safety Multiplier")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("maxDailySafetyMultiplier"),
                     label: NSLocalizedString("Max Daily Safety Multiplier", comment: "Max Daily Safety Multiplier"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
@@ -80,7 +80,7 @@ extension AlgorithmAdvancedSettings {
                             )
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("currentBasalSafetyMultiplier"),
                     label: NSLocalizedString("Current Basal Safety Multiplier", comment: "Current Basal Safety Multiplier"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
@@ -100,7 +100,7 @@ extension AlgorithmAdvancedSettings {
                             hintLabel = NSLocalizedString("Use Custom Peak Time", comment: "Use Custom Peak Time")
                         }
                     ),
-                    type: .conditionalDecimal,
+                    type: .conditionalDecimal("insulinPeakTime"),
                     label: NSLocalizedString("Use Custom Peak Time", comment: "Use Custom Peak Time"),
                     conditionalLabel: NSLocalizedString("Insulin Peak Time", comment: "Insulin Peak Time"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
@@ -186,7 +186,7 @@ extension AlgorithmAdvancedSettings {
                             )
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("autotuneISFAdjustmentFraction"),
                     label: NSLocalizedString("Autotune ISF Adjustment Fraction", comment: "Autotune ISF Adjustment Fraction"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
@@ -206,7 +206,7 @@ extension AlgorithmAdvancedSettings {
                             hintLabel = NSLocalizedString("Min 5m Carbimpact", comment: "Min 5m Carbimpact")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("min5mCarbimpact"),
                     label: NSLocalizedString("Min 5m Carbimpact", comment: "Min 5m Carbimpact"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
@@ -226,7 +226,7 @@ extension AlgorithmAdvancedSettings {
                             hintLabel = NSLocalizedString("Remaining Carbs Fraction", comment: "Remaining Carbs Fraction")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("remainingCarbsFraction"),
                     label: NSLocalizedString("Remaining Carbs Fraction", comment: "Remaining Carbs Fraction"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
@@ -246,7 +246,7 @@ extension AlgorithmAdvancedSettings {
                             hintLabel = NSLocalizedString("Remaining Carbs Cap", comment: "Remaining Carbs Cap")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("remainingCarbsCap"),
                     label: NSLocalizedString("Remaining Carbs Cap", comment: "Remaining Carbs Cap"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
@@ -266,7 +266,7 @@ extension AlgorithmAdvancedSettings {
                             hintLabel = NSLocalizedString("Noisy CGM Target Multiplier", comment: "Noisy CGM Target Multiplier")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("noisyCGMTargetMultiplier"),
                     label: NSLocalizedString("Noisy CGM Target Multiplier", comment: "Noisy CGM Target Multiplier"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(

+ 3 - 3
FreeAPS/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift

@@ -45,7 +45,7 @@ extension AutosensSettings {
                             hintLabel = NSLocalizedString("Autosens Max", comment: "Autosens Max")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("autosensMax"),
                     label: NSLocalizedString("Autosens Max", comment: "Autosens Max"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
@@ -63,10 +63,10 @@ extension AutosensSettings {
                         get: { selectedVerboseHint },
                         set: {
                             selectedVerboseHint = $0
-                            hintLabel = NSLocalizedString("Autosens Max", comment: "Autosens Max")
+                            hintLabel = NSLocalizedString("Autosens Min", comment: "Autosens Min")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("autosensMin"),
                     label: NSLocalizedString("Autosens Min", comment: "Autosens Min"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(

+ 3 - 3
FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift

@@ -76,7 +76,7 @@ extension BolusCalculatorConfig {
                             hintLabel = "Recommended Bolus Percentage"
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("overrideFactor"),
                     label: "Recommended Bolus Percentage",
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: "Recommended Bolus Percentage… bla bla bla",
@@ -94,7 +94,7 @@ extension BolusCalculatorConfig {
                             hintLabel = "Fatty Meal Factor"
                         }
                     ),
-                    type: .conditionalDecimal,
+                    type: .conditionalDecimal("fattyMealFactor"),
                     label: "Enable Fatty Meal Factor",
                     conditionalLabel: "Fatty Meal Factor",
                     miniHint: "Lower your bolus recommendation by factor x for fatty meals.",
@@ -112,7 +112,7 @@ extension BolusCalculatorConfig {
                             hintLabel = "Super Bolus & Sweet Meal Factor"
                         }
                     ),
-                    type: .conditionalDecimal,
+                    type: .conditionalDecimal("sweetMealFactor"),
                     label: "Enable Super Bolus",
                     conditionalLabel: "Super Bolus Factor",
                     miniHint: "Add x times current scheduled basal rate to your bolus recommendation.",

+ 4 - 4
FreeAPS/Sources/Modules/DynamicSettings/View/DynamicSettingsRootView.swift

@@ -121,7 +121,7 @@ extension DynamicSettings {
                                     hintLabel = "Adjustment Factor"
                                 }
                             ),
-                            type: .decimal,
+                            type: .decimal("adjustmentFactor"),
                             label: "Adjustment Factor",
                             miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                             verboseHint: "Adjustment Factor for logarithmic dynamic sensitvity... bla bla bla"
@@ -138,7 +138,7 @@ extension DynamicSettings {
                                     hintLabel = "Sigmoid Adjustment Factor"
                                 }
                             ),
-                            type: .decimal,
+                            type: .decimal("adjustmentFactorSigmoid"),
                             label: "Sigmoid Adjustment Factor",
                             miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                             verboseHint: "Sigmoid Adjustment Factor… should be 0.5… bla bla ba"
@@ -156,7 +156,7 @@ extension DynamicSettings {
                                 hintLabel = "Weighted Average of TDD"
                             }
                         ),
-                        type: .decimal,
+                        type: .decimal("weightPercentage"),
                         label: "Weighted Average of TDD",
                         miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                         verboseHint: "Weight of past 24 hours"
@@ -190,7 +190,7 @@ extension DynamicSettings {
                                 hintLabel = "Threshold Setting"
                             }
                         ),
-                        type: .decimal,
+                        type: .decimal("threshold_setting"),
                         label: "Threshold Setting",
                         miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                         verboseHint: "BG threshold"

+ 2 - 2
FreeAPS/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift

@@ -55,7 +55,7 @@ extension UnitsLimitsSettings {
                             hintLabel = NSLocalizedString("Max IOB", comment: "Max IOB")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("maxIOB"),
                     label: NSLocalizedString("Max IOB", comment: "Max IOB"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
@@ -75,7 +75,7 @@ extension UnitsLimitsSettings {
                             hintLabel = NSLocalizedString("Max COB", comment: "Max COB")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("maxCOB"),
                     label: NSLocalizedString("Max COB", comment: "Max COB"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(

+ 104 - 17
FreeAPS/Sources/Modules/MealSettings/View/MealSettingsRootView.swift

@@ -13,6 +13,9 @@ extension MealSettings {
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
+        @State private var displayPickerMaxCarbs: Bool = false
+        @State private var displayPickerMaxFat: Bool = false
+        @State private var displayPickerMaxProtein: Bool = false
 
         @Environment(\.colorScheme) var colorScheme
         var color: LinearGradient {
@@ -58,20 +61,104 @@ extension MealSettings {
                     header: Text("Limits per Entry"),
                     content: {
                         VStack {
-                            HStack {
-                                Text("Max Carbs")
-                                TextFieldWithToolBar(text: $state.maxCarbs, placeholder: "g", numberFormatter: formatter)
-                            }.padding(state.useFPUconversion ? .top : .vertical)
+                            VStack {
+                                HStack {
+                                    Text("Max Carbs")
+
+                                    Spacer()
+
+                                    Group {
+                                        Text(state.maxCarbs.description)
+                                            .foregroundColor(!displayPickerMaxCarbs ? .primary : .accentColor)
+
+                                        Text(" g").foregroundColor(.secondary)
+                                    }
+                                }
+                                .onTapGesture {
+                                    displayPickerMaxCarbs.toggle()
+                                }
+                            }.padding(.top)
+
+                            if displayPickerMaxCarbs {
+                                let setting = PickerSettingsProvider.shared.settings.maxCarbs
+                                Picker(selection: $state.maxCarbs, label: Text("")) {
+                                    ForEach(
+                                        PickerSettingsProvider.shared.generatePickerValues(from: setting),
+                                        id: \.self
+                                    ) { value in
+                                        Text("\(value.description)").tag(value)
+                                    }
+                                }
+                                .pickerStyle(WheelPickerStyle())
+                                .frame(maxWidth: .infinity)
+                            }
 
                             if state.useFPUconversion {
-                                HStack {
-                                    Text("Max Fat")
-                                    TextFieldWithToolBar(text: $state.maxFat, placeholder: "g", numberFormatter: formatter)
+                                VStack {
+                                    HStack {
+                                        Text("Max Fat")
+
+                                        Spacer()
+
+                                        Group {
+                                            Text(state.maxFat.description)
+                                                .foregroundColor(!displayPickerMaxFat ? .primary : .accentColor)
+
+                                            Text(" g").foregroundColor(.secondary)
+                                        }
+                                    }
+                                    .onTapGesture {
+                                        displayPickerMaxFat.toggle()
+                                    }
+                                }
+                                .padding(.top)
+
+                                if displayPickerMaxFat {
+                                    let setting = PickerSettingsProvider.shared.settings.maxFat
+                                    Picker(selection: $state.maxCarbs, label: Text("")) {
+                                        ForEach(
+                                            PickerSettingsProvider.shared.generatePickerValues(from: setting),
+                                            id: \.self
+                                        ) { value in
+                                            Text("\(value.description)").tag(value)
+                                        }
+                                    }
+                                    .pickerStyle(WheelPickerStyle())
+                                    .frame(maxWidth: .infinity)
+                                }
+
+                                VStack {
+                                    HStack {
+                                        Text("Max Protein")
+
+                                        Spacer()
+
+                                        Group {
+                                            Text(state.maxProtein.description)
+                                                .foregroundColor(!displayPickerMaxProtein ? .primary : .accentColor)
+
+                                            Text(" g").foregroundColor(.secondary)
+                                        }
+                                    }
+                                    .onTapGesture {
+                                        displayPickerMaxProtein.toggle()
+                                    }
+                                }
+                                .padding(.top)
+
+                                if displayPickerMaxProtein {
+                                    let setting = PickerSettingsProvider.shared.settings.maxProtein
+                                    Picker(selection: $state.maxProtein, label: Text("")) {
+                                        ForEach(
+                                            PickerSettingsProvider.shared.generatePickerValues(from: setting),
+                                            id: \.self
+                                        ) { value in
+                                            Text("\(value.description)").tag(value)
+                                        }
+                                    }
+                                    .pickerStyle(WheelPickerStyle())
+                                    .frame(maxWidth: .infinity)
                                 }
-                                HStack {
-                                    Text("Max Protein")
-                                    TextFieldWithToolBar(text: $state.maxProtein, placeholder: "g", numberFormatter: formatter)
-                                }.padding(.bottom)
                             }
 
                             HStack(alignment: .top) {
@@ -95,7 +182,7 @@ extension MealSettings {
                                         }
                                     }
                                 ).buttonStyle(BorderlessButtonStyle())
-                            }
+                            }.padding(.top)
                         }.padding(.bottom)
                     }
                 ).listRowBackground(Color.chart)
@@ -130,7 +217,7 @@ extension MealSettings {
                                 hintLabel = "Fat and Protein Delay"
                             }
                         ),
-                        type: .decimal,
+                        type: .decimal("delay"),
                         label: "Fat and Protein Delay",
                         miniHint: "Delay is time from now until the first future carb entry.",
                         verboseHint: "X-Axis Interval Step… bla bla bla"
@@ -147,7 +234,7 @@ extension MealSettings {
                                 hintLabel = "Maximum Duration (hours)"
                             }
                         ),
-                        type: .decimal,
+                        type: .decimal("timeCap"),
                         label: "Maximum Duration (hours)",
                         miniHint: "Carb spread over a maximum number of hours (5-12).",
                         verboseHint: "This spreads the carb equivilants over a maximum duration setting that can be configured from 5-12 hours."
@@ -164,14 +251,14 @@ extension MealSettings {
                                 hintLabel = "Spread Interval (minutes)"
                             }
                         ),
-                        type: .decimal,
+                        type: .decimal("minuteInterval"),
                         label: "Spread Interval (minutes)",
                         miniHint: "Interval in minutes is how many minutes are between entries.",
                         verboseHint: "Interval in minutes is how many minutes are between entries. The shorter the interval, the smoother the result. 10, 15, 20, 30, or 60 are reasonable choices."
                     )
 
                     SettingInputSection(
-                        decimalValue: $state.minuteInterval,
+                        decimalValue: $state.individualAdjustmentFactor,
                         booleanValue: $booleanPlaceholder,
                         shouldDisplayHint: $shouldDisplayHint,
                         selectedVerboseHint: Binding(
@@ -181,7 +268,7 @@ extension MealSettings {
                                 hintLabel = "Fat and Protein Factor"
                             }
                         ),
-                        type: .decimal,
+                        type: .decimal("individualAdjustmentFactor"),
                         label: "Fat and Protein Factor",
                         miniHint: "Influences how many carb equivalents are recorded for fat and protein.",
                         verboseHint: "The Fat and Protein Factor influences how much effect the fat and protein has on the entries. 1.0 is full effect (original Warsaw Method) and 0.5 is half effect. Note that you may find that your normal carb ratio needs to increase to a larger number if you begin adding fat and protein entries. For this reason, it is best to start with a factor of about 0.5 to ease into it."

+ 1 - 1
FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift

@@ -91,7 +91,7 @@ extension PumpSettingsEditor {
                             hintLabel = "Duration of Insulin Action"
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("dia"),
                     label: "Duration of Insulin Action",
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: "Duration of Insulin Action… bla bla bla"

+ 6 - 6
FreeAPS/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift

@@ -153,7 +153,7 @@ extension SMBSettings {
                                 hintLabel = NSLocalizedString("Enable SMB With High BG", comment: "Enable SMB With High BG")
                             }
                         ),
-                        type: .conditionalDecimal,
+                        type: .conditionalDecimal("enableSMB_high_bg_target"),
                         label: NSLocalizedString("Enable SMB With High BG", comment: "Enable SMB With High BG"),
                         conditionalLabel: "High BG Target",
                         miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
@@ -195,7 +195,7 @@ extension SMBSettings {
                             hintLabel = NSLocalizedString("Max SMB Basal Minutes", comment: "Max SMB Basal Minutes")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("maxSMBBasalMinutes"),
                     label: NSLocalizedString("Max SMB Basal Minutes", comment: "Max SMB Basal Minutes"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
@@ -215,7 +215,7 @@ extension SMBSettings {
                             hintLabel = NSLocalizedString("Max UAM SMB Basal Minutes", comment: "Max UAM SMB Basal Minutes")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("maxUAMSMBBasalMinutes"),
                     label: NSLocalizedString("Max UAM SMB Basal Minutes", comment: "Max UAM SMB Basal Minutes"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
@@ -235,7 +235,7 @@ extension SMBSettings {
                             hintLabel = NSLocalizedString("Max Delta-BG Threshold SMB", comment: "Max Delta-BG Threshold")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("maxDeltaBGthreshold"),
                     label: NSLocalizedString("Max Delta-BG Threshold SMB", comment: "Max Delta-BG Threshold"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
@@ -255,7 +255,7 @@ extension SMBSettings {
                             hintLabel = NSLocalizedString("SMB DeliveryRatio", comment: "SMB DeliveryRatio")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("smbDeliveryRatio"),
                     label: NSLocalizedString("SMB DeliveryRatio", comment: "SMB DeliveryRatio"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
@@ -275,7 +275,7 @@ extension SMBSettings {
                             hintLabel = NSLocalizedString("SMB Interval", comment: "SMB Interval")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("smbInterval"),
                     label: NSLocalizedString("SMB Interval", comment: "SMB Interval"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(

+ 1 - 1
FreeAPS/Sources/Modules/TargetBehavoir/View/TargetBehavoirRootView.swift

@@ -138,7 +138,7 @@ extension TargetBehavoir {
                             hintLabel = NSLocalizedString("Half Basal Exercise Target", comment: "Half Basal Exercise Target")
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("halfBasalExerciseTarget"),
                     label: NSLocalizedString("Half Basal Exercise Target", comment: "Half Basal Exercise Target"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(

+ 70 - 14
FreeAPS/Sources/Modules/UserInterfaceSettings/View/UserInterfaceSettingsRootView.swift

@@ -12,6 +12,8 @@ extension UserInterfaceSettings {
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
+        @State private var displayPickerLowThreshold: Bool = false
+        @State private var displayPickerHighThreshold: Bool = false
 
         @Environment(\.colorScheme) var colorScheme
         var color: LinearGradient {
@@ -102,17 +104,71 @@ extension UserInterfaceSettings {
 
                 Section {
                     VStack {
-                        HStack {
-                            Text("Low Threshold")
-                            Spacer()
-                            TextFieldWithToolBar(text: $state.low, placeholder: "0", numberFormatter: glucoseFormatter)
-                            Text(state.units.rawValue).foregroundColor(.secondary)
-                        }.padding(.top)
-                        HStack {
-                            Text("High Threshold")
-                            Spacer()
-                            TextFieldWithToolBar(text: $state.high, placeholder: "0", numberFormatter: glucoseFormatter)
-                            Text(state.units.rawValue).foregroundColor(.secondary)
+                        VStack {
+                            HStack {
+                                Text("Low Threshold")
+
+                                Spacer()
+
+                                Group {
+                                    Text(state.low.description)
+                                        .foregroundColor(!displayPickerLowThreshold ? .primary : .accentColor)
+
+                                    Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
+                                }
+                            }
+                            .onTapGesture {
+                                displayPickerLowThreshold.toggle()
+                            }
+                        }
+                        .padding(.top)
+
+                        if displayPickerLowThreshold {
+                            let setting = PickerSettingsProvider.shared.settings.low
+
+                            Picker(selection: $state.low, label: Text("")) {
+                                ForEach(
+                                    PickerSettingsProvider.shared.generatePickerValues(from: setting),
+                                    id: \.self
+                                ) { value in
+                                    Text("\(value.description)").tag(value)
+                                }
+                            }
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
+                        }
+
+                        VStack {
+                            HStack {
+                                Text("High Threshold")
+
+                                Spacer()
+
+                                Group {
+                                    Text(state.high.description)
+                                        .foregroundColor(!displayPickerHighThreshold ? .primary : .accentColor)
+
+                                    Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
+                                }
+                            }
+                            .onTapGesture {
+                                displayPickerHighThreshold.toggle()
+                            }
+                        }
+                        .padding(.top)
+
+                        if displayPickerHighThreshold {
+                            let setting = PickerSettingsProvider.shared.settings.high
+                            Picker(selection: $state.high, label: Text("")) {
+                                ForEach(
+                                    PickerSettingsProvider.shared.generatePickerValues(from: setting),
+                                    id: \.self
+                                ) { value in
+                                    Text("\(value.description)").tag(value)
+                                }
+                            }
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
                         }
 
                         HStack(alignment: .top) {
@@ -136,7 +192,7 @@ extension UserInterfaceSettings {
                                     }
                                 }
                             ).buttonStyle(BorderlessButtonStyle())
-                        }
+                        }.padding(.top)
                     }.padding(.bottom)
                 }.listRowBackground(Color.chart)
 
@@ -151,7 +207,7 @@ extension UserInterfaceSettings {
                             hintLabel = "X-Axis Interval Step"
                         }
                     ),
-                    type: .decimal,
+                    type: .decimal("hours"),
                     label: "X-Axis Interval Step",
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: "X-Axis Interval Step… bla bla bla"
@@ -240,7 +296,7 @@ extension UserInterfaceSettings {
                             hintLabel = "Show Carbs Required Badge"
                         }
                     ),
-                    type: .conditionalDecimal,
+                    type: .conditionalDecimal("carbsRequiredThreshold"),
                     label: "Show Carbs Required Badge",
                     conditionalLabel: "Carbs Required Threshold",
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",

+ 195 - 35
FreeAPS/Sources/Views/SettingInputSection.swift

@@ -1,10 +1,23 @@
 import SwiftUI
 
 struct SettingInputSection: View {
-    enum InputType {
-        case decimal
+    enum SettingInputSectionType: Equatable {
+        case decimal(String)
         case boolean
-        case conditionalDecimal
+        case conditionalDecimal(String)
+
+        static func == (lhs: SettingInputSectionType, rhs: SettingInputSectionType) -> Bool {
+            switch (lhs, rhs) {
+            case (.boolean, .boolean):
+                return true
+            case let (.decimal(lhsValue), .decimal(rhsValue)):
+                return lhsValue == rhsValue
+            case let (.conditionalDecimal(lhsValue), .conditionalDecimal(rhsValue)):
+                return lhsValue == rhsValue
+            default:
+                return false
+            }
+        }
     }
 
     @Binding var decimalValue: Decimal
@@ -12,44 +25,74 @@ struct SettingInputSection: View {
     @Binding var shouldDisplayHint: Bool
     @Binding var selectedVerboseHint: String?
 
-    var type: InputType
+    var type: SettingInputSectionType
     var label: String
     var conditionalLabel: String?
     var miniHint: String
     var verboseHint: String
     var headerText: String?
     var footerText: String?
-    private var formatter: NumberFormatter {
-        let formatter = NumberFormatter()
-        formatter.numberStyle = .decimal
-        return formatter
+
+    // Access the shared PickerSettingsProvider instance
+    @ObservedObject private var pickerSettingsProvider = PickerSettingsProvider.shared
+    @State private var displayPicker: Bool = false
+    @State private var units: GlucoseUnits = .mgdL
+
+    @Injected() var settingsManager: SettingsManager!
+
+    mutating func subscribe() {
+        units = settingsManager.settings.units
     }
 
     var body: some View {
         Section(
             content: {
                 VStack {
-                    if type == .decimal {
-                        HStack {
-                            Text(label)
+                    switch type {
+                    case let .decimal(key):
+                        if let setting = getPickerSetting(for: key) {
+                            VStack {
+                                HStack {
+                                    Text(label)
 
-                            TextFieldWithToolBar(
-                                text: Binding(
-                                    get: { decimalValue },
-                                    set: { decimalValue = $0 }
-                                ),
-                                placeholder: decimalValue.description,
-                                numberFormatter: formatter
-                            )
+                                    Spacer()
 
-                        }.padding(.top)
-                    } else if type == .boolean {
+                                    Group {
+                                        Text(decimalValue.description)
+                                            .foregroundColor(!displayPicker ? .primary : .accentColor)
+
+                                        if setting.type == PickerSetting.PickerSettingType.glucose {
+                                            Text(units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
+                                        } else if setting.type == PickerSetting.PickerSettingType.insulinUnit {
+                                            Text(" U").foregroundColor(.secondary)
+                                        } else if setting.type == PickerSetting.PickerSettingType.gramms {
+                                            Text(" g").foregroundColor(.secondary)
+                                        }
+                                    }.onTapGesture {
+                                        displayPicker.toggle()
+                                    }
+                                }.padding(.top)
+
+                                if displayPicker {
+                                    Picker(selection: $decimalValue, label: Text("")) {
+                                        ForEach(pickerSettingsProvider.generatePickerValues(from: setting), id: \.self) { value in
+                                            Text("\(value.description)").tag(value)
+                                        }
+                                    }
+                                    .pickerStyle(WheelPickerStyle())
+                                    .frame(maxWidth: .infinity)
+                                }
+                            }
+                        }
+
+                    case .boolean:
                         HStack {
                             Toggle(isOn: $booleanValue) {
                                 Text(label)
                             }
                         }.padding(.top)
-                    } else if type == .conditionalDecimal, let secondLabel = conditionalLabel {
+
+                    case let .conditionalDecimal(key):
                         HStack {
                             Toggle(isOn: $booleanValue) {
                                 Text(label)
@@ -57,18 +100,43 @@ struct SettingInputSection: View {
                         }.padding(.vertical)
 
                         if $booleanValue.wrappedValue {
-                            HStack {
-                                Text(secondLabel)
-
-                                TextFieldWithToolBar(
-                                    text: Binding(
-                                        get: { decimalValue },
-                                        set: { decimalValue = $0 }
-                                    ),
-                                    placeholder: decimalValue.description,
-                                    numberFormatter: formatter
-                                )
-                            }.padding(.bottom)
+                            if let setting = getPickerSetting(for: key) {
+                                VStack {
+                                    HStack {
+                                        Text(conditionalLabel ?? label)
+
+                                        Spacer()
+
+                                        Group {
+                                            Text(decimalValue.description)
+                                                .foregroundColor(!displayPicker ? .primary : .accentColor)
+
+                                            if setting.type == PickerSetting.PickerSettingType.glucose {
+                                                Text(units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
+                                            } else if setting.type == PickerSetting.PickerSettingType.insulinUnit {
+                                                Text(" U").foregroundColor(.secondary)
+                                            } else if setting.type == PickerSetting.PickerSettingType.gramms {
+                                                Text(" g").foregroundColor(.secondary)
+                                            }
+                                        }.onTapGesture {
+                                            displayPicker.toggle()
+                                        }
+                                    }.padding(.top)
+
+                                    if displayPicker {
+                                        Picker(selection: $decimalValue, label: Text("")) {
+                                            ForEach(
+                                                pickerSettingsProvider.generatePickerValues(from: setting),
+                                                id: \.self
+                                            ) { value in
+                                                Text("\(value.description)").tag(value)
+                                            }
+                                        }
+                                        .pickerStyle(WheelPickerStyle())
+                                        .frame(maxWidth: .infinity)
+                                    }
+                                }
+                            }
                         }
                     }
 
@@ -89,7 +157,7 @@ struct SettingInputSection: View {
                                 }
                             }
                         ).buttonStyle(BorderlessButtonStyle())
-                    }.padding(type == .boolean ? .vertical : .bottom)
+                    }.padding(.vertical)
                 }
             },
             header: {
@@ -104,4 +172,96 @@ struct SettingInputSection: View {
             }
         ).listRowBackground(Color.chart)
     }
+
+    // Helper function to retrieve PickerSetting based on key
+    private func getPickerSetting(for key: String) -> PickerSetting? {
+        switch key {
+        case "lowGlucose":
+            return pickerSettingsProvider.settings.lowGlucose
+        case "highGlucose":
+            return pickerSettingsProvider.settings.highGlucose
+        case "carbsRequiredThreshold":
+            return pickerSettingsProvider.settings.carbsRequiredThreshold
+        case "individualAdjustmentFactor":
+            return pickerSettingsProvider.settings.individualAdjustmentFactor
+        case "delay":
+            return pickerSettingsProvider.settings.delay
+        case "timeCap":
+            return pickerSettingsProvider.settings.timeCap
+        case "minuteInterval":
+            return pickerSettingsProvider.settings.minuteInterval
+        case "high":
+            return pickerSettingsProvider.settings.high
+        case "low":
+            return pickerSettingsProvider.settings.low
+        case "hours":
+            return pickerSettingsProvider.settings.hours
+        case "maxCarbs":
+            return pickerSettingsProvider.settings.maxCarbs
+        case "maxFat":
+            return pickerSettingsProvider.settings.maxFat
+        case "maxProtein":
+            return pickerSettingsProvider.settings.maxProtein
+        case "overrideFactor":
+            return pickerSettingsProvider.settings.overrideFactor
+        case "fattyMealFactor":
+            return pickerSettingsProvider.settings.fattyMealFactor
+        case "sweetMealFactor":
+            return pickerSettingsProvider.settings.sweetMealFactor
+        case "maxIOB":
+            return pickerSettingsProvider.settings.maxIOB
+        case "maxDailySafetyMultiplier":
+            return pickerSettingsProvider.settings.maxDailySafetyMultiplier
+        case "currentBasalSafetyMultiplier":
+            return pickerSettingsProvider.settings.currentBasalSafetyMultiplier
+        case "autosensMax":
+            return pickerSettingsProvider.settings.autosensMax
+        case "autosensMin":
+            return pickerSettingsProvider.settings.autosensMin
+        case "smbDeliveryRatio":
+            return pickerSettingsProvider.settings.smbDeliveryRatio
+        case "halfBasalExerciseTarget":
+            return pickerSettingsProvider.settings.halfBasalExerciseTarget
+        case "maxCOB":
+            return pickerSettingsProvider.settings.maxCOB
+        case "min5mCarbimpact":
+            return pickerSettingsProvider.settings.min5mCarbimpact
+        case "autotuneISFAdjustmentFraction":
+            return pickerSettingsProvider.settings.autotuneISFAdjustmentFraction
+        case "remainingCarbsFraction":
+            return pickerSettingsProvider.settings.remainingCarbsFraction
+        case "remainingCarbsCap":
+            return pickerSettingsProvider.settings.remainingCarbsCap
+        case "maxSMBBasalMinutes":
+            return pickerSettingsProvider.settings.maxSMBBasalMinutes
+        case "maxUAMSMBBasalMinutes":
+            return pickerSettingsProvider.settings.maxUAMSMBBasalMinutes
+        case "smbInterval":
+            return pickerSettingsProvider.settings.smbInterval
+        case "bolusIncrement":
+            return pickerSettingsProvider.settings.bolusIncrement
+        case "insulinPeakTime":
+            return pickerSettingsProvider.settings.insulinPeakTime
+        case "carbsReqThreshold":
+            return pickerSettingsProvider.settings.carbsReqThreshold
+        case "noisyCGMTargetMultiplier":
+            return pickerSettingsProvider.settings.noisyCGMTargetMultiplier
+        case "maxDeltaBGthreshold":
+            return pickerSettingsProvider.settings.maxDeltaBGthreshold
+        case "adjustmentFactor":
+            return pickerSettingsProvider.settings.adjustmentFactor
+        case "adjustmentFactorSigmoid":
+            return pickerSettingsProvider.settings.adjustmentFactorSigmoid
+        case "weightPercentage":
+            return pickerSettingsProvider.settings.weightPercentage
+        case "enableSMB_high_bg_target":
+            return pickerSettingsProvider.settings.enableSMB_high_bg_target
+        case "threshold_setting":
+            return pickerSettingsProvider.settings.threshold_setting
+        case "updateInterval":
+            return pickerSettingsProvider.settings.updateInterval
+        default:
+            return nil
+        }
+    }
 }