Browse Source

Move dynamic ISF settings out from "OpenAPS" to "Extra Features" section

Jon Mårtensson 2 years ago
parent
commit
5ae04a94e0

+ 33 - 1
FreeAPS.xcodeproj/project.pbxproj

@@ -22,6 +22,10 @@
 		1927C8E62744606D00347C69 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1927C8E82744606D00347C69 /* InfoPlist.strings */; };
 		1935364028496F7D001E0B16 /* Oref2_variables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1935363F28496F7D001E0B16 /* Oref2_variables.swift */; };
 		193F6CDD2A512C8F001240FD /* Loops.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193F6CDC2A512C8F001240FD /* Loops.swift */; };
+		195D80B42AF6973A00D25097 /* DynamicRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195D80B32AF6973A00D25097 /* DynamicRootView.swift */; };
+		195D80B72AF697B800D25097 /* DynamicDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195D80B62AF697B800D25097 /* DynamicDataFlow.swift */; };
+		195D80B92AF697F700D25097 /* DynamicProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195D80B82AF697F700D25097 /* DynamicProvider.swift */; };
+		195D80BB2AF6980B00D25097 /* DynamicStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195D80BA2AF6980B00D25097 /* DynamicStateModel.swift */; };
 		1967DFBE29D052C200759F30 /* Icons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1967DFBD29D052C200759F30 /* Icons.swift */; };
 		1967DFC029D053AC00759F30 /* IconSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1967DFBF29D053AC00759F30 /* IconSelection.swift */; };
 		1967DFC229D053D300759F30 /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1967DFC129D053D300759F30 /* IconImage.swift */; };
@@ -526,6 +530,10 @@
 		1927C8FE274489BA00347C69 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		1935363F28496F7D001E0B16 /* Oref2_variables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Oref2_variables.swift; sourceTree = "<group>"; };
 		193F6CDC2A512C8F001240FD /* Loops.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Loops.swift; sourceTree = "<group>"; };
+		195D80B32AF6973A00D25097 /* DynamicRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRootView.swift; sourceTree = "<group>"; };
+		195D80B62AF697B800D25097 /* DynamicDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicDataFlow.swift; sourceTree = "<group>"; };
+		195D80B82AF697F700D25097 /* DynamicProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicProvider.swift; sourceTree = "<group>"; };
+		195D80BA2AF6980B00D25097 /* DynamicStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicStateModel.swift; sourceTree = "<group>"; };
 		1967DFBD29D052C200759F30 /* Icons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icons.swift; sourceTree = "<group>"; };
 		1967DFBF29D053AC00759F30 /* IconSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconSelection.swift; sourceTree = "<group>"; };
 		1967DFC129D053D300759F30 /* IconImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImage.swift; sourceTree = "<group>"; };
@@ -1050,6 +1058,25 @@
 			name = "Recovered References";
 			sourceTree = "<group>";
 		};
+		195D80B22AF696EE00D25097 /* Dynamic */ = {
+			isa = PBXGroup;
+			children = (
+				195D80B62AF697B800D25097 /* DynamicDataFlow.swift */,
+				195D80B82AF697F700D25097 /* DynamicProvider.swift */,
+				195D80BA2AF6980B00D25097 /* DynamicStateModel.swift */,
+				195D80B52AF6974200D25097 /* View */,
+			);
+			path = Dynamic;
+			sourceTree = "<group>";
+		};
+		195D80B52AF6974200D25097 /* View */ = {
+			isa = PBXGroup;
+			children = (
+				195D80B32AF6973A00D25097 /* DynamicRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
 		198377CF266BFEDE004DE65E /* Localizations */ = {
 			isa = PBXGroup;
 			children = (
@@ -1168,10 +1195,11 @@
 		3811DE0325C9D31700A708ED /* Modules */ = {
 			isa = PBXGroup;
 			children = (
+				195D80B22AF696EE00D25097 /* Dynamic */,
 				BD7DA9A32AE06DBA00601B20 /* BolusCalculatorConfig */,
 				190EBCC229FF134900BA767D /* StatConfig */,
-				CE94597C29E9E1CD0047C9C6 /* WatchConfig */,
 				19F95FF129F10F9C00314DDC /* Stat */,
+				CE94597C29E9E1CD0047C9C6 /* WatchConfig */,
 				19E1F7E629D0828B005C8D20 /* IconConfig */,
 				19D466A129AA2B0A004D5F33 /* FPUConfig */,
 				F90692CD274B99850037068D /* HealthKit */,
@@ -2576,6 +2604,7 @@
 				38C4D33725E9A1A300D30B77 /* DispatchQueue+Extensions.swift in Sources */,
 				F90692CF274B999A0037068D /* HealthKitDataFlow.swift in Sources */,
 				CE7CA3552A064973004BE681 /* ListStateIntent.swift in Sources */,
+				195D80B72AF697B800D25097 /* DynamicDataFlow.swift in Sources */,
 				3862CC2E2743F9F700BF832C /* CalendarManager.swift in Sources */,
 				CEA4F62329BE10F70011ADF7 /* SavitzkyGolayFilter.swift in Sources */,
 				38B4F3C325E2A20B00E76A18 /* PumpSetupView.swift in Sources */,
@@ -2758,6 +2787,7 @@
 				19F95FF729F10FEE00314DDC /* StatStateModel.swift in Sources */,
 				385CEAC125F2EA52002D6D5B /* Announcement.swift in Sources */,
 				8B759CFCF47B392BB365C251 /* BasalProfileEditorDataFlow.swift in Sources */,
+				195D80B42AF6973A00D25097 /* DynamicRootView.swift in Sources */,
 				389442CB25F65F7100FA1F27 /* NightscoutTreatment.swift in Sources */,
 				CE7CA3512A064973004BE681 /* ApplyTempPresetIntent.swift in Sources */,
 				FA630397F76B582C8D8681A7 /* BasalProfileEditorProvider.swift in Sources */,
@@ -2770,6 +2800,7 @@
 				38887CCE25F5725200944304 /* IOBEntry.swift in Sources */,
 				38E98A2425F52C9300C0CED0 /* Logger.swift in Sources */,
 				CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */,
+				195D80BB2AF6980B00D25097 /* DynamicStateModel.swift in Sources */,
 				E00EEC0327368630002FF094 /* ServiceAssembly.swift in Sources */,
 				38192E07261BA9960094D973 /* FetchTreatmentsManager.swift in Sources */,
 				19012CDC291D2CB900FB8210 /* LoopStats.swift in Sources */,
@@ -2853,6 +2884,7 @@
 				1D845DF2E3324130E1D95E67 /* DataTableProvider.swift in Sources */,
 				19F95FFA29F1102A00314DDC /* StatRootView.swift in Sources */,
 				0D9A5E34A899219C5C4CDFAF /* DataTableStateModel.swift in Sources */,
+				195D80B92AF697F700D25097 /* DynamicProvider.swift in Sources */,
 				D6D02515BBFBE64FEBE89856 /* DataTableRootView.swift in Sources */,
 				38569349270B5DFB0002C50D /* AppGroupSource.swift in Sources */,
 				F5CA3DB1F9DC8B05792BBFAA /* CGMDataFlow.swift in Sources */,

+ 5 - 0
FreeAPS/Sources/Modules/Dynamic/DynamicDataFlow.swift

@@ -0,0 +1,5 @@
+enum Dynamic {
+    enum Config {}
+}
+
+protocol DynamicProvider: Provider {}

+ 3 - 0
FreeAPS/Sources/Modules/Dynamic/DynamicProvider.swift

@@ -0,0 +1,3 @@
+extension Dynamic {
+    final class Provider: BaseProvider, DynamicProvider {}
+}

+ 69 - 0
FreeAPS/Sources/Modules/Dynamic/DynamicStateModel.swift

@@ -0,0 +1,69 @@
+import SwiftUI
+
+extension Dynamic {
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var settings: SettingsManager!
+        @Injected() var storage: FileStorage!
+
+        @Published var useNewFormula: Bool = false
+        @Published var enableDynamicCR: Bool = false
+        @Published var sigmoid: Bool = false
+        @Published var adjustmentFactor: Decimal = 0.5
+        @Published var weightPercentage: Decimal = 0.65
+        @Published var tddAdjBasal: Bool = false
+        @Published var threshold_setting: Decimal = 65
+        @Published var unit: GlucoseUnits = .mmolL
+
+        var preferences: Preferences {
+            settingsManager.preferences
+        }
+
+        override func subscribe() {
+            unit = settingsManager.settings.units
+            useNewFormula = settings.preferences.useNewFormula
+            enableDynamicCR = settings.preferences.enableDynamicCR
+            sigmoid = settings.preferences.sigmoid
+            adjustmentFactor = settings.preferences.adjustmentFactor
+            weightPercentage = settings.preferences.weightPercentage
+            tddAdjBasal = settings.preferences.tddAdjBasal
+
+            if unit == .mmolL {
+                threshold_setting = settings.preferences.threshold_setting.asMmolL
+            } else {
+                threshold_setting = settings.preferences.threshold_setting
+            }
+        }
+
+        var unChanged: Bool {
+            preferences.enableDynamicCR == enableDynamicCR &&
+                preferences.adjustmentFactor == adjustmentFactor &&
+                preferences.sigmoid == sigmoid &&
+                preferences.tddAdjBasal == tddAdjBasal &&
+                preferences.threshold_setting == convertBack(threshold_setting) &&
+                preferences.useNewFormula == useNewFormula &&
+                preferences.weightPercentage == weightPercentage
+        }
+
+        func convertBack(_ glucose: Decimal) -> Decimal {
+            if unit == .mmolL {
+                return glucose.asMgdL
+            }
+            return glucose
+        }
+
+        func saveIfChanged() {
+            if !unChanged {
+                var newSettings = storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self) ?? Preferences()
+                newSettings.enableDynamicCR = enableDynamicCR
+                newSettings.adjustmentFactor = adjustmentFactor
+                newSettings.sigmoid = sigmoid
+                newSettings.tddAdjBasal = tddAdjBasal
+                newSettings.threshold_setting = convertBack(threshold_setting)
+                newSettings.useNewFormula = useNewFormula
+                newSettings.weightPercentage = weightPercentage
+                newSettings.timestamp = Date()
+                storage.save(newSettings, as: OpenAPS.Settings.preferences)
+            }
+        }
+    }
+}

+ 89 - 0
FreeAPS/Sources/Modules/Dynamic/View/DynamicRootView.swift

@@ -0,0 +1,89 @@
+import SwiftUI
+import Swinject
+
+extension Dynamic {
+    struct RootView: BaseView {
+        let resolver: Resolver
+        @StateObject var state = StateModel()
+
+        private var conversionFormatter: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.numberStyle = .decimal
+            formatter.maximumFractionDigits = 1
+
+            return formatter
+        }
+
+        private var formatter: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.numberStyle = .decimal
+            return formatter
+        }
+
+        private var glucoseFormatter: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.numberStyle = .decimal
+            if state.unit == .mmolL {
+                formatter.maximumFractionDigits = 1
+            } else { formatter.maximumFractionDigits = 0 }
+            formatter.roundingMode = .halfUp
+            return formatter
+        }
+
+        var body: some View {
+            Form {
+                Section {
+                    HStack {
+                        Toggle("Activate Dynamic Sensitivity (ISF)", isOn: $state.useNewFormula)
+                    }
+                    if state.useNewFormula {
+                        HStack {
+                            Toggle("Activate Dynamic Carb Ratio (CR)", isOn: $state.enableDynamicCR)
+                        }
+                    }
+                } header: { Text("Enable") }
+
+                if state.useNewFormula {
+                    Section {
+                        HStack {
+                            Toggle("Use Sigmoid Formula", isOn: $state.sigmoid)
+                        }
+                    } header: { Text("Formula") }
+
+                    Section {
+                        HStack {
+                            Text("Adjustment Factor")
+                            Spacer()
+                            DecimalTextField("0", value: $state.adjustmentFactor, formatter: formatter)
+                        }
+
+                        HStack {
+                            Text("Weighted Average of TDD. Weight of past 24 hours:")
+                            Spacer()
+                            DecimalTextField("0", value: $state.weightPercentage, formatter: formatter)
+                        }
+
+                        HStack {
+                            Toggle("Adjust basal", isOn: $state.tddAdjBasal)
+                        }
+                    } header: { Text("Settings") }
+
+                    Section {
+                        HStack {
+                            Text("Threshold Setting")
+                            Spacer()
+                            DecimalTextField("0", value: $state.threshold_setting, formatter: glucoseFormatter)
+                            Text(state.unit.rawValue)
+                        }
+                    } header: { Text("Safety") }
+                }
+            }
+            .onAppear(perform: configureView)
+            .navigationBarTitle("Dynamic ISF")
+            .navigationBarTitleDisplayMode(.automatic)
+            .onDisappear {
+                state.saveIfChanged()
+            }
+        }
+    }
+}

+ 2 - 0
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -32,6 +32,7 @@ extension NightscoutConfig {
         @Published var dia: Decimal = 6
         @Published var maxBasal: Decimal = 2
         @Published var maxBolus: Decimal = 10
+        @Published var allowAnnouncements: Bool = false
 
         override func subscribe() {
             url = keychain.getValue(String.self, forKey: Config.urlKey) ?? ""
@@ -41,6 +42,7 @@ extension NightscoutConfig {
             maxBasal = settingsManager.pumpSettings.maxBasal
             maxBolus = settingsManager.pumpSettings.maxBolus
 
+            subscribeSetting(\.allowAnnouncements, on: $allowAnnouncements) { allowAnnouncements = $0 }
             subscribeSetting(\.isUploadEnabled, on: $isUploadEnabled) { isUploadEnabled = $0 }
             subscribeSetting(\.useLocalGlucoseSource, on: $useLocalSource) { useLocalSource = $0 }
             subscribeSetting(\.localGlucosePort, on: $localPort.map(Int.init)) { localPort = Decimal($0) }

+ 4 - 0
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift

@@ -119,6 +119,10 @@ extension NightscoutConfig {
                     Button("Backfill glucose") { state.backfillGlucose() }
                         .disabled(state.url.isEmpty || state.connecting || state.backfilling)
                 }
+
+                Section {
+                    Toggle("Remote control", isOn: $state.allowAnnouncements)
+                } header: { Text("Allow Remote control of iAPS") }
             }
             .onAppear(perform: configureView)
             .navigationBarTitle("Nightscout Config")

File diff suppressed because it is too large
+ 0 - 75
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift


+ 0 - 2
FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift

@@ -27,8 +27,6 @@ extension PreferencesEditor {
                         Text("mg/dL").tag(0)
                         Text("mmol/L").tag(1)
                     }
-
-                    Toggle("Remote control", isOn: $state.allowAnnouncements)
                 }
 
                 ForEach(state.sections.indexed(), id: \.1.id) { sectionIndex, section in

+ 1 - 0
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -50,6 +50,7 @@ extension Settings {
                     Text("App Icons").navigationLink(to: .iconConfig, from: self)
                     Text("Bolus Calculator").navigationLink(to: .bolusCalculatorConfig, from: self)
                     Text("Fat And Protein Conversion").navigationLink(to: .fpuConfig, from: self)
+                    Text("Dynamic ISF").navigationLink(to: .dynamicISF, from: self)
                 } header: { Text("Extra Features") }
 
                 Section {

+ 3 - 0
FreeAPS/Sources/Router/Screen.swift

@@ -33,6 +33,7 @@ enum Screen: Identifiable, Hashable {
     case watch
     case statisticsConfig
     case bolusCalculatorConfig
+    case dynamicISF
 
     var id: Int { String(reflecting: self).hashValue }
 }
@@ -102,6 +103,8 @@ extension Screen {
             StatConfig.RootView(resolver: resolver)
         case .bolusCalculatorConfig:
             BolusCalculatorConfig.RootView(resolver: resolver)
+        case .dynamicISF:
+            Dynamic.RootView(resolver: resolver)
         }
     }