Przeglądaj źródła

Preferences editor

Ivan Valkou 5 lat temu
rodzic
commit
370bde438d

+ 41 - 137
FreeAPS/Sources/Models/Preferences.swift

@@ -1,133 +1,45 @@
 import Foundation
 
 struct Preferences: JSON {
-    var maxIOB: Decimal
-    var maxDailySafetyMultiplier: Decimal
-    var currentBasalSafetyMultiplier: Decimal
-    var autosensMax: Decimal
-    var autosensMin: Decimal
-    var rewindResetsAutosens: Bool
-    var highTemptargetRaisesSensitivity: Bool
-    var lowTemptargetLowersSensitivity: Bool
-    var sensitivityRaisesTarget: Bool
-    var resistanceLowersTarget: Bool
-    var advTargetAdjustments: Bool
-    var exerciseMode: Bool
-    var halfBasalExerciseTarget: Decimal
-    var maxCOB: Decimal
-    var wideBGTargetRange: Bool
-    var skipNeutralTemps: Bool
-    var unsuspendIfNoTemp: Bool
-    var bolusSnoozeDIADivisor: Decimal
-    var min5mCarbimpact: Decimal
-    var autotuneISFAdjustmentFraction: Decimal
-    var remainingCarbsFraction: Decimal
-    var remainingCarbsCap: Decimal
-    var enableUAM: Bool
-    var a52RiskEnable: Bool
-    var enableSMBWithCOB: Bool
-    var enableSMBWithTemptarget: Bool
-    var enableSMBAlways: Bool
-    var enableSMBAfterCarbs: Bool
-    var allowSMBWithHighTemptarget: Bool
-    var maxSMBBasalMinutes: Decimal
-    var maxUAMSMBBasalMinutes: Decimal
-    var smbInterval: Decimal
-    var bolusIncrement: Decimal
-    var curve: InsulinCurve
-    var useCustomPeakTime: Bool
-    var insulinPeakTime: Decimal
-    var carbsReqThreshold: Decimal
-    var offlineHotspot: Bool // unused, for compatibility
-    var noisyCGMTargetMultiplier: Decimal
-    var suspendZerosIOB: Bool
-    var enableEnliteBgproxy: Bool // unused, for compatibility
-
-    init(
-        maxIOB: Decimal = 0,
-        maxDailySafetyMultiplier: Decimal = 3,
-        currentBasalSafetyMultiplier: Decimal = 4,
-        autosensMax: Decimal = 1.2,
-        autosensMin: Decimal = 0.7,
-        rewindResetsAutosens: Bool = true,
-        highTemptargetRaisesSensitivity: Bool = false,
-        lowTemptargetLowersSensitivity: Bool = false,
-        sensitivityRaisesTarget: Bool = true,
-        resistanceLowersTarget: Bool = false,
-        advTargetAdjustments: Bool = false,
-        exerciseMode: Bool = false,
-        halfBasalExerciseTarget: Decimal = 160,
-        maxCOB: Decimal = 120,
-        wideBGTargetRange: Bool = false,
-        skipNeutralTemps: Bool = false,
-        unsuspendIfNoTemp: Bool = false,
-        bolusSnoozeDIADivisor: Decimal = 2,
-        min5mCarbimpact: Decimal = 8,
-        autotuneISFAdjustmentFraction: Decimal = 1.0,
-        remainingCarbsFraction: Decimal = 1.0,
-        remainingCarbsCap: Decimal = 90,
-        enableUAM: Bool = false,
-        a52RiskEnable: Bool = false,
-        enableSMBWithCOB: Bool = false,
-        enableSMBWithTemptarget: Bool = false,
-        enableSMBAlways: Bool = false,
-        enableSMBAfterCarbs: Bool = false,
-        allowSMBWithHighTemptarget: Bool = false,
-        maxSMBBasalMinutes: Decimal = 30,
-        maxUAMSMBBasalMinutes: Decimal = 30,
-        smbInterval: Decimal = 3,
-        bolusIncrement: Decimal = 0.1,
-        curve: InsulinCurve = .rapidActing,
-        useCustomPeakTime: Bool = false,
-        insulinPeakTime: Decimal = 75,
-        carbsReqThreshold: Decimal = 1,
-        offlineHotspot: Bool = false, // unused, for compatibility
-        noisyCGMTargetMultiplier: Decimal = 1.3,
-        suspendZerosIOB: Bool = true,
-        enableEnliteBgproxy: Bool = false // unused, for compatibility
-    ) {
-        self.maxIOB = maxIOB
-        self.maxDailySafetyMultiplier = maxDailySafetyMultiplier
-        self.currentBasalSafetyMultiplier = currentBasalSafetyMultiplier
-        self.autosensMax = autosensMax
-        self.autosensMin = autosensMin
-        self.rewindResetsAutosens = rewindResetsAutosens
-        self.highTemptargetRaisesSensitivity = highTemptargetRaisesSensitivity
-        self.lowTemptargetLowersSensitivity = lowTemptargetLowersSensitivity
-        self.sensitivityRaisesTarget = sensitivityRaisesTarget
-        self.resistanceLowersTarget = resistanceLowersTarget
-        self.advTargetAdjustments = advTargetAdjustments
-        self.exerciseMode = exerciseMode
-        self.halfBasalExerciseTarget = halfBasalExerciseTarget
-        self.maxCOB = maxCOB
-        self.wideBGTargetRange = wideBGTargetRange
-        self.skipNeutralTemps = skipNeutralTemps
-        self.unsuspendIfNoTemp = unsuspendIfNoTemp
-        self.bolusSnoozeDIADivisor = bolusSnoozeDIADivisor
-        self.min5mCarbimpact = min5mCarbimpact
-        self.autotuneISFAdjustmentFraction = autotuneISFAdjustmentFraction
-        self.remainingCarbsFraction = remainingCarbsFraction
-        self.remainingCarbsCap = remainingCarbsCap
-        self.enableUAM = enableUAM
-        self.a52RiskEnable = a52RiskEnable
-        self.enableSMBWithCOB = enableSMBWithCOB
-        self.enableSMBWithTemptarget = enableSMBWithTemptarget
-        self.enableSMBAlways = enableSMBAlways
-        self.enableSMBAfterCarbs = enableSMBAfterCarbs
-        self.allowSMBWithHighTemptarget = allowSMBWithHighTemptarget
-        self.maxSMBBasalMinutes = maxSMBBasalMinutes
-        self.maxUAMSMBBasalMinutes = maxUAMSMBBasalMinutes
-        self.smbInterval = smbInterval
-        self.bolusIncrement = bolusIncrement
-        self.curve = curve
-        self.useCustomPeakTime = useCustomPeakTime
-        self.insulinPeakTime = insulinPeakTime
-        self.carbsReqThreshold = carbsReqThreshold
-        self.offlineHotspot = offlineHotspot
-        self.noisyCGMTargetMultiplier = noisyCGMTargetMultiplier
-        self.suspendZerosIOB = suspendZerosIOB
-        self.enableEnliteBgproxy = enableEnliteBgproxy
-    }
+    var maxIOB: Decimal = 0
+    var maxDailySafetyMultiplier: Decimal = 3
+    var currentBasalSafetyMultiplier: Decimal = 4
+    var autosensMax: Decimal = 1.2
+    var autosensMin: Decimal = 0.7
+    var rewindResetsAutosens: Bool = true
+    var highTemptargetRaisesSensitivity: Bool = false
+    var lowTemptargetLowersSensitivity: Bool = false
+    var sensitivityRaisesTarget: Bool = true
+    var resistanceLowersTarget: Bool = false
+    var advTargetAdjustments: Bool = false
+    var exerciseMode: Bool = false
+    var halfBasalExerciseTarget: Decimal = 160
+    var maxCOB: Decimal = 120
+    var wideBGTargetRange: Bool = false
+    var skipNeutralTemps: Bool = false
+    var unsuspendIfNoTemp: Bool = true
+    var bolusSnoozeDIADivisor: Decimal = 2
+    var min5mCarbimpact: Decimal = 8
+    var autotuneISFAdjustmentFraction: Decimal = 1.0
+    var remainingCarbsFraction: Decimal = 1.0
+    var remainingCarbsCap: Decimal = 90
+    var enableUAM: Bool = false
+    var a52RiskEnable: Bool = false
+    var enableSMBWithCOB: Bool = false
+    var enableSMBWithTemptarget: Bool = false
+    var enableSMBAlways: Bool = false
+    var enableSMBAfterCarbs: Bool = false
+    var allowSMBWithHighTemptarget: Bool = false
+    var maxSMBBasalMinutes: Decimal = 30
+    var maxUAMSMBBasalMinutes: Decimal = 30
+    var smbInterval: Decimal = 3
+    var bolusIncrement: Decimal = 0.1
+    var curve: InsulinCurve = .rapidActing
+    var useCustomPeakTime: Bool = false
+    var insulinPeakTime: Decimal = 75
+    var carbsReqThreshold: Decimal = 1.0
+    var noisyCGMTargetMultiplier: Decimal = 1.3
+    var suspendZerosIOB: Bool = true
 }
 
 extension Preferences {
@@ -169,23 +81,15 @@ extension Preferences {
         case useCustomPeakTime
         case insulinPeakTime
         case carbsReqThreshold
-        case offlineHotspot = "offline_hotspot"
         case noisyCGMTargetMultiplier
         case suspendZerosIOB = "suspend_zeros_iob"
-        case enableEnliteBgproxy
     }
 }
 
-enum InsulinCurve: String, Codable {
+enum InsulinCurve: String, JSON, Identifiable, CaseIterable {
     case rapidActing = "rapid-acting"
     case ultraRapid = "ultra-rapid"
     case bilinear
-}
 
-extension Preferences {
-    var prettyPrinted: String {
-        let encoder = JSONEncoder()
-        encoder.outputFormatting = .prettyPrinted
-        return String(data: try! encoder.encode(self), encoding: .utf8)!
-    }
+    var id: InsulinCurve { self }
 }

+ 10 - 2
FreeAPS/Sources/Modules/ISFEditor/ISFEditorViewModel.swift

@@ -24,8 +24,8 @@ extension ISFEditor {
         private(set) var units: GlucoseUnits = .mmolL
 
         override func subscribe() {
-            units = settingsManager.settings.units
             let profile = provider.profile
+            units = profile.units
             items = profile.sensitivities.map { value in
                 let timeIndex = timeValues.firstIndex(of: Double(value.offset * 60)) ?? 0
                 let rateIndex = rateValues.firstIndex(of: Double(value.sensitivity)) ?? 0
@@ -56,7 +56,11 @@ extension ISFEditor {
                 let rate = Decimal(self.rateValues[item.rateIndex])
                 return InsulinSensitivityEntry(sensitivity: rate, offset: minutes, start: fotmatter.string(from: date))
             }
-            let profile = InsulinSensitivities(units: units, userPrefferedUnits: units, sensitivities: sensitivities)
+            let profile = InsulinSensitivities(
+                units: units,
+                userPrefferedUnits: settingsManager.settings.units,
+                sensitivities: sensitivities
+            )
             provider.saveProfile(profile)
         }
 
@@ -66,6 +70,10 @@ extension ISFEditor {
                 let sorted = uniq.sorted { $0.timeIndex < $1.timeIndex }
                 sorted.first?.timeIndex = 0
                 self.items = sorted
+
+                if self.items.isEmpty {
+                    self.units = self.settingsManager.settings.units
+                }
             }
         }
     }

+ 31 - 1
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorDataFlow.swift

@@ -1,5 +1,35 @@
+import Foundation
+
 enum PreferencesEditor {
     enum Config {}
+
+    class Field<T>: Identifiable {
+        var displayName: String
+        var keypath: WritableKeyPath<Preferences, T>
+        var value: T {
+            didSet {
+                settable?.onSet(keypath, value: value)
+            }
+        }
+
+        weak var settable: PreferencesSettable?
+
+        init(displayName: String, keypath: WritableKeyPath<Preferences, T>, value: T, settable: PreferencesSettable? = nil) {
+            self.displayName = displayName
+            self.keypath = keypath
+            self.value = value
+            self.settable = settable
+        }
+
+        let id = UUID()
+    }
+}
+
+protocol PreferencesEditorProvider: Provider {
+    var preferences: Preferences { get }
+    func savePreferences(_ preferences: Preferences)
 }
 
-protocol PreferencesEditorProvider: Provider {}
+protocol PreferencesSettable: AnyObject {
+    func onSet<T>(_ keypath: WritableKeyPath<Preferences, T>, value: T)
+}

+ 16 - 1
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorProvider.swift

@@ -1,3 +1,18 @@
+import Foundation
+
 extension PreferencesEditor {
-    final class Provider: BaseProvider, PreferencesEditorProvider {}
+    final class Provider: BaseProvider, PreferencesEditorProvider {
+        private let processQueue = DispatchQueue(label: "PreferencesEditorProvider.processQueue")
+        var preferences: Preferences {
+            (try? storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self))
+                ?? Preferences(from: OpenAPS.defaults(for: OpenAPS.Settings.preferences))
+                ?? Preferences()
+        }
+
+        func savePreferences(_ preferences: Preferences) {
+            processQueue.async {
+                try? self.storage.save(preferences, as: OpenAPS.Settings.preferences)
+            }
+        }
+    }
 }

+ 264 - 2
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorViewModel.swift

@@ -1,7 +1,269 @@
+import Foundation
 import SwiftUI
 
 extension PreferencesEditor {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: PreferencesEditorProvider {
-        override func subscribe() {}
+    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject,
+        PreferencesSettable where Provider: PreferencesEditorProvider
+    {
+        @Injected() var settingsManager: SettingsManager!
+        private(set) var preferences = Preferences()
+        @Published var unitsIndex = 1
+        @Published var decimalFields: [Field<Decimal>] = []
+        @Published var boolFields: [Field<Bool>] = []
+        @Published var insulinCirveField = Field<InsulinCurve>(
+            displayName: "Insulin curve",
+            keypath: \.curve,
+            value: .rapidActing
+        )
+
+        override func subscribe() {
+            preferences = provider.preferences
+            unitsIndex = settingsManager.settings.units == .mgdL ? 0 : 1
+            insulinCirveField.value = preferences.curve
+            insulinCirveField.settable = self
+
+            $unitsIndex
+                .sink { [weak self] index in
+                    self?.settingsManager.settings.units = index == 0 ? .mgdL : .mmolL
+                }
+                .store(in: &lifetime)
+
+            boolFields = [
+                Field(
+                    displayName: "Rewind Resets Autosens",
+                    keypath: \.rewindResetsAutosens,
+                    value: preferences.rewindResetsAutosens,
+                    settable: self
+                ),
+                Field(
+                    displayName: "High Temptarget Raises Sensitivity",
+                    keypath: \.highTemptargetRaisesSensitivity,
+                    value: preferences.highTemptargetRaisesSensitivity,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Low Temptarget Lowers Sensitivity",
+                    keypath: \.lowTemptargetLowersSensitivity,
+                    value: preferences.lowTemptargetLowersSensitivity,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Sensitivity Raises Target",
+                    keypath: \.sensitivityRaisesTarget,
+                    value: preferences.sensitivityRaisesTarget,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Resistance Lowers Target",
+                    keypath: \.resistanceLowersTarget,
+                    value: preferences.resistanceLowersTarget,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Exercise Mode",
+                    keypath: \.exerciseMode,
+                    value: preferences.exerciseMode,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Wide BG Target Range",
+                    keypath: \.wideBGTargetRange,
+                    value: preferences.wideBGTargetRange,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Skip Neutral Temps",
+                    keypath: \.skipNeutralTemps,
+                    value: preferences.skipNeutralTemps,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Unsuspend If No Temp",
+                    keypath: \.unsuspendIfNoTemp,
+                    value: preferences.unsuspendIfNoTemp,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Enable UAM",
+                    keypath: \.enableUAM,
+                    value: preferences.enableUAM,
+                    settable: self
+                ),
+                Field(
+                    displayName: "A52 Risk Enable",
+                    keypath: \.a52RiskEnable,
+                    value: preferences.a52RiskEnable,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Enable SMB With COB",
+                    keypath: \.enableSMBWithCOB,
+                    value: preferences.enableSMBWithCOB,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Enable SMB With Temptarget",
+                    keypath: \.enableSMBWithTemptarget,
+                    value: preferences.enableSMBWithTemptarget,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Enable SMB Always",
+                    keypath: \.enableSMBAlways,
+                    value: preferences.enableSMBAlways,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Enable SMB After Carbs",
+                    keypath: \.enableSMBAfterCarbs,
+                    value: preferences.enableSMBAfterCarbs,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Allow SMB With High Temptarget",
+                    keypath: \.allowSMBWithHighTemptarget,
+                    value: preferences.allowSMBWithHighTemptarget,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Use Custom Peak Time",
+                    keypath: \.useCustomPeakTime,
+                    value: preferences.useCustomPeakTime,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Suspend Zeros IOB",
+                    keypath: \.suspendZerosIOB,
+                    value: preferences.suspendZerosIOB,
+                    settable: self
+                )
+            ]
+
+            decimalFields = [
+                Field(
+                    displayName: "Max IOB",
+                    keypath: \.maxIOB,
+                    value: preferences.maxIOB,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Max Daily Safety Multiplier",
+                    keypath: \.maxDailySafetyMultiplier,
+                    value: preferences.maxDailySafetyMultiplier,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Current Basal Safety Multiplier",
+                    keypath: \.currentBasalSafetyMultiplier,
+                    value: preferences.currentBasalSafetyMultiplier,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Autosens Max",
+                    keypath: \.autosensMax,
+                    value: preferences.autosensMax,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Autosens Min",
+                    keypath: \.autosensMin,
+                    value: preferences.autosensMin,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Half Basal Exercise Target",
+                    keypath: \.halfBasalExerciseTarget,
+                    value: preferences.halfBasalExerciseTarget,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Max COB",
+                    keypath: \.maxCOB,
+                    value: preferences.maxCOB,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Bolus Snooze DIA Divisor",
+                    keypath: \.bolusSnoozeDIADivisor,
+                    value: preferences.bolusSnoozeDIADivisor,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Min 5m Carbimpact",
+                    keypath: \.min5mCarbimpact,
+                    value: preferences.min5mCarbimpact,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Autotune ISF Adjustment Fraction",
+                    keypath: \.autotuneISFAdjustmentFraction,
+                    value: preferences.autotuneISFAdjustmentFraction,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Remaining Carbs Fraction",
+                    keypath: \.remainingCarbsFraction,
+                    value: preferences.remainingCarbsFraction,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Remaining Carbs Cap",
+                    keypath: \.remainingCarbsCap,
+                    value: preferences.remainingCarbsCap,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Max SMB Basal Minutes",
+                    keypath: \.maxSMBBasalMinutes,
+                    value: preferences.maxSMBBasalMinutes,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Max UAM SMB Basal Minutes",
+                    keypath: \.maxUAMSMBBasalMinutes,
+                    value: preferences.maxUAMSMBBasalMinutes,
+                    settable: self
+                ),
+                Field(
+                    displayName: "SMB Interval",
+                    keypath: \.smbInterval,
+                    value: preferences.smbInterval,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Bolus Increment",
+                    keypath: \.bolusIncrement,
+                    value: preferences.bolusIncrement,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Insulin Peak Time",
+                    keypath: \.insulinPeakTime,
+                    value: preferences.insulinPeakTime,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Carbs Req Threshold",
+                    keypath: \.carbsReqThreshold,
+                    value: preferences.carbsReqThreshold,
+                    settable: self
+                ),
+                Field(
+                    displayName: "Noisy CGM Target Multiplier",
+                    keypath: \.noisyCGMTargetMultiplier,
+                    value: preferences.noisyCGMTargetMultiplier,
+                    settable: self
+                )
+            ]
+        }
+
+        func onSet<T>(_ keypath: WritableKeyPath<Preferences, T>, value: T) {
+            preferences[keyPath: keypath] = value
+            save()
+        }
+
+        func save() {
+            provider.savePreferences(preferences)
+        }
     }
 }

+ 36 - 4
FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift

@@ -4,11 +4,43 @@ extension PreferencesEditor {
     struct RootView: BaseView {
         @EnvironmentObject var viewModel: ViewModel<Provider>
 
+        private var formatter: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.numberStyle = .decimal
+            return formatter
+        }
+
         var body: some View {
-            Text("PreferencesEditor screen")
-                .navigationTitle("PreferencesEditor")
-                .navigationBarTitleDisplayMode(.automatic)
-                .navigationBarItems(leading: Button("Close", action: viewModel.hideModal))
+            Form {
+                Section(header: Text("FreeAPS X")) {
+                    Picker("Glucose units", selection: $viewModel.unitsIndex) {
+                        Text("mg/dL").tag(0)
+                        Text("mmol/L").tag(1)
+                    }
+                }
+
+                Section(header: Text("OpenAPS")) {
+                    Picker(selection: $viewModel.insulinCirveField.value, label: Text(viewModel.insulinCirveField.displayName)) {
+                        ForEach(InsulinCurve.allCases) { v in
+                            Text(v.rawValue).tag(v)
+                        }
+                    }
+
+                    ForEach(viewModel.boolFields.indexed(), id: \.1.id) { index, field in
+                        Toggle(field.displayName, isOn: self.$viewModel.boolFields[index].value)
+                    }
+
+                    ForEach(viewModel.decimalFields.indexed(), id: \.1.id) { index, field in
+                        HStack {
+                            Text(field.displayName)
+                            DecimalTextField("0", value: self.$viewModel.decimalFields[index].value, formatter: formatter)
+                        }
+                    }
+                }
+            }
+            .navigationTitle("Preferences")
+            .navigationBarTitleDisplayMode(.automatic)
+            .navigationBarItems(leading: Button("Close", action: viewModel.hideModal))
         }
     }
 }

+ 9 - 9
FreeAPS/Sources/Modules/PumpSettingsEditor/PumpSettingsEditorViewModel.swift

@@ -2,25 +2,25 @@ import SwiftUI
 
 extension PumpSettingsEditor {
     class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: PumpSettingsEditorProvider {
-        @Published var maxBasal = 0.0
-        @Published var maxBolus = 0.0
-        @Published var dia = 0.0
+        @Published var maxBasal: Decimal = 0.0
+        @Published var maxBolus: Decimal = 0.0
+        @Published var dia: Decimal = 0.0
 
         @Published var syncInProgress = false
 
         override func subscribe() {
             let settings = provider.settings()
-            maxBasal = Double(settings.maxBasal)
-            maxBolus = Double(settings.maxBolus)
-            dia = Double(settings.insulinActionCurve)
+            maxBasal = settings.maxBasal
+            maxBolus = settings.maxBolus
+            dia = settings.insulinActionCurve
         }
 
         func save() {
             syncInProgress = true
             let settings = PumpSettings(
-                insulinActionCurve: Decimal(dia),
-                maxBolus: Decimal(maxBolus),
-                maxBasal: Decimal(maxBasal)
+                insulinActionCurve: dia,
+                maxBolus: maxBolus,
+                maxBasal: maxBasal
             )
             provider.save(settings: settings)
                 .receive(on: DispatchQueue.main)

+ 6 - 2
FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorViewModel.swift

@@ -24,8 +24,8 @@ extension TargetsEditor {
         private(set) var units: GlucoseUnits = .mmolL
 
         override func subscribe() {
-            units = settingsManager.settings.units
             let profile = provider.profile
+            units = profile.units
             items = profile.targets.map { value in
                 let timeIndex = timeValues.firstIndex(of: Double(value.offset * 60)) ?? 0
                 let lowIndex = rateValues.firstIndex(of: Double(value.low)) ?? 0
@@ -60,7 +60,7 @@ extension TargetsEditor {
                 let high = Decimal(self.rateValues[item.highIndex])
                 return BGTargetEntry(low: low, high: high, start: fotmatter.string(from: date), offset: minutes)
             }
-            let profile = BGTargets(units: units, userPrefferedUnits: units, targets: targets)
+            let profile = BGTargets(units: units, userPrefferedUnits: settingsManager.settings.units, targets: targets)
             provider.saveProfile(profile)
         }
 
@@ -74,6 +74,10 @@ extension TargetsEditor {
                     }
                 sorted.first?.timeIndex = 0
                 self.items = sorted
+
+                if self.items.isEmpty {
+                    self.units = self.settingsManager.settings.units
+                }
             }
         }
     }

+ 3 - 3
FreeAPS/Sources/Views/DecimalTextField.swift

@@ -2,12 +2,12 @@ import SwiftUI
 
 struct DecimalTextField: UIViewRepresentable {
     private var placeholder: String
-    @Binding var value: Double
+    @Binding var value: Decimal
     private var formatter: NumberFormatter
 
     init(
         _ placeholder: String,
-        value: Binding<Double>,
+        value: Binding<Decimal>,
         formatter: NumberFormatter
     ) {
         self.placeholder = placeholder
@@ -93,7 +93,7 @@ struct DecimalTextField: UIViewRepresentable {
 
                 // Set Value
                 let double = number.doubleValue
-                parent.value = double
+                parent.value = Decimal(double)
             }
 
             return isNumber || withDecimal