Jonas Björkert vor 1 Jahr
Ursprung
Commit
880b9b5abc
26 geänderte Dateien mit 555 neuen und 589 gelöschten Zeilen
  1. 16 8
      LoopFollow.xcodeproj/project.pbxproj
  2. 0 16
      LoopFollow/Application/SceneDelegate.swift
  3. 0 18
      LoopFollow/Controllers/AppStateController.swift
  4. 84 84
      LoopFollow/Controllers/Graphs.swift
  5. 1 1
      LoopFollow/Controllers/MainViewController+updateStats.swift
  6. 4 4
      LoopFollow/Controllers/Nightscout/BGData.swift
  7. 1 1
      LoopFollow/Controllers/Nightscout/DeviceStatusLoop.swift
  8. 2 2
      LoopFollow/Controllers/Nightscout/DeviceStatusOpenAPS.swift
  9. 2 2
      LoopFollow/Controllers/Nightscout/Profile.swift
  10. 1 1
      LoopFollow/Controllers/Nightscout/Treatments.swift
  11. 1 2
      LoopFollow/Controllers/Nightscout/Treatments/Carbs.swift
  12. 2 2
      LoopFollow/Controllers/Nightscout/Treatments/Overrides.swift
  13. 1 1
      LoopFollow/Controllers/Nightscout/Treatments/TemporaryTarget.swift
  14. 2 2
      LoopFollow/Controllers/Stats.swift
  15. 13 0
      LoopFollow/Helpers/BinaryFloatingPoint+localized.swift
  16. 43 0
      LoopFollow/Helpers/Views/SettingsStepperRow.swift
  17. 152 0
      LoopFollow/Settings/GraphSettingsView.swift
  18. 2 0
      LoopFollow/Storage/Observable.swift
  19. 180 0
      LoopFollow/Storage/Storage+Migrate.swift
  20. 20 1
      LoopFollow/Storage/Storage.swift
  21. 0 19
      LoopFollow/Storage/UserDefaults.swift
  22. 0 286
      LoopFollow/ViewControllers/GraphSettingsViewController.swift
  23. 12 128
      LoopFollow/ViewControllers/MainViewController.swift
  24. 0 2
      LoopFollow/ViewControllers/NightScoutViewController.swift
  25. 16 7
      LoopFollow/ViewControllers/SettingsViewController.swift
  26. 0 2
      LoopFollow/ViewControllers/WatchSettingsViewController.swift

+ 16 - 8
LoopFollow.xcodeproj/project.pbxproj

@@ -121,6 +121,10 @@
 		DD7FFAFD2A72953000C3A304 /* EKEventStore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7FFAFC2A72953000C3A304 /* EKEventStore+Extensions.swift */; };
 		DD8316182DE3633D004467AA /* GeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8316172DE3633D004467AA /* GeneralSettingsView.swift */; };
 		DD8316442DE47CA9004467AA /* BGPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8316432DE47CA9004467AA /* BGPicker.swift */; };
+		DD8316462DE49B09004467AA /* GraphSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8316452DE49B09004467AA /* GraphSettingsView.swift */; };
+		DD8316482DE49EE5004467AA /* Storage+Migrate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8316472DE49EE5004467AA /* Storage+Migrate.swift */; };
+		DD83164A2DE4C504004467AA /* SettingsStepperRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8316492DE4C504004467AA /* SettingsStepperRow.swift */; };
+		DD83164C2DE4DB3A004467AA /* BinaryFloatingPoint+localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD83164B2DE4DB3A004467AA /* BinaryFloatingPoint+localized.swift */; };
 		DD85E9952D739CFE001C8BB7 /* OmnipodDashHeartbeatBluetoothTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD85E9942D739CED001C8BB7 /* OmnipodDashHeartbeatBluetoothTransmitter.swift */; };
 		DD91E4DD2BDEC3F8002D9E97 /* GlucoseConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD91E4DC2BDEC3F8002D9E97 /* GlucoseConversion.swift */; };
 		DD98F54424BCEFEE0007425A /* ShareClientExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD98F54324BCEFEE0007425A /* ShareClientExtension.swift */; };
@@ -170,9 +174,7 @@
 		DDCC3A582DDC9655006F1C10 /* MissedBolusAlarmEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCC3A572DDC9655006F1C10 /* MissedBolusAlarmEditor.swift */; };
 		DDCC3A5A2DDC988F006F1C10 /* CarbSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCC3A592DDC988F006F1C10 /* CarbSample.swift */; };
 		DDCF979424C0D380002C9752 /* UIViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979324C0D380002C9752 /* UIViewExtension.swift */; };
-		DDCF979824C1489C002C9752 /* GraphSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979724C1489C002C9752 /* GraphSettingsViewController.swift */; };
 		DDCF979A24C14DB4002C9752 /* WatchSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979924C14DB4002C9752 /* WatchSettingsViewController.swift */; };
-		DDCF979E24C2382A002C9752 /* AppStateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979D24C2382A002C9752 /* AppStateController.swift */; };
 		DDCF9A802D85FD0B004DF4DD /* Alarm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF9A7F2D85FD09004DF4DD /* Alarm.swift */; };
 		DDCF9A822D85FD15004DF4DD /* AlarmType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF9A812D85FD14004DF4DD /* AlarmType.swift */; };
 		DDCF9A882D85FD33004DF4DD /* AlarmData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF9A872D85FD33004DF4DD /* AlarmData.swift */; };
@@ -481,6 +483,10 @@
 		DD7FFAFC2A72953000C3A304 /* EKEventStore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EKEventStore+Extensions.swift"; sourceTree = "<group>"; };
 		DD8316172DE3633D004467AA /* GeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsView.swift; sourceTree = "<group>"; };
 		DD8316432DE47CA9004467AA /* BGPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGPicker.swift; sourceTree = "<group>"; };
+		DD8316452DE49B09004467AA /* GraphSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphSettingsView.swift; sourceTree = "<group>"; };
+		DD8316472DE49EE5004467AA /* Storage+Migrate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Migrate.swift"; sourceTree = "<group>"; };
+		DD8316492DE4C504004467AA /* SettingsStepperRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsStepperRow.swift; sourceTree = "<group>"; };
+		DD83164B2DE4DB3A004467AA /* BinaryFloatingPoint+localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BinaryFloatingPoint+localized.swift"; sourceTree = "<group>"; };
 		DD85E9942D739CED001C8BB7 /* OmnipodDashHeartbeatBluetoothTransmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmnipodDashHeartbeatBluetoothTransmitter.swift; sourceTree = "<group>"; };
 		DD91E4DC2BDEC3F8002D9E97 /* GlucoseConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseConversion.swift; sourceTree = "<group>"; };
 		DD98F54324BCEFEE0007425A /* ShareClientExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareClientExtension.swift; sourceTree = "<group>"; };
@@ -533,9 +539,7 @@
 		DDCC3ABF2DDE10B0006F1C10 /* Testing.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Testing.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/Testing.framework; sourceTree = DEVELOPER_DIR; };
 		DDCC3AD62DDE1790006F1C10 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		DDCF979324C0D380002C9752 /* UIViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExtension.swift; sourceTree = "<group>"; };
-		DDCF979724C1489C002C9752 /* GraphSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphSettingsViewController.swift; sourceTree = "<group>"; };
 		DDCF979924C14DB4002C9752 /* WatchSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchSettingsViewController.swift; sourceTree = "<group>"; };
-		DDCF979D24C2382A002C9752 /* AppStateController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateController.swift; sourceTree = "<group>"; };
 		DDCF9A7F2D85FD09004DF4DD /* Alarm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alarm.swift; sourceTree = "<group>"; };
 		DDCF9A812D85FD14004DF4DD /* AlarmType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmType.swift; sourceTree = "<group>"; };
 		DDCF9A872D85FD33004DF4DD /* AlarmData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmData.swift; sourceTree = "<group>"; };
@@ -837,6 +841,7 @@
 		DD1A97122D429495000DDC11 /* Settings */ = {
 			isa = PBXGroup;
 			children = (
+				DD8316452DE49B09004467AA /* GraphSettingsView.swift */,
 				DD1A97152D4294B2000DDC11 /* AdvancedSettingsViewModel.swift */,
 				DD1A97132D4294A4000DDC11 /* AdvancedSettingsView.swift */,
 				DD8316172DE3633D004467AA /* GeneralSettingsView.swift */,
@@ -1085,6 +1090,7 @@
 				DDD10F002C510C6B00D76A8E /* ObservableUserDefaults.swift */,
 				DDD10F062C529DE800D76A8E /* Observable.swift */,
 				DD4878042C7B2C970048F05C /* Storage.swift */,
+				DD8316472DE49EE5004467AA /* Storage+Migrate.swift */,
 			);
 			path = Storage;
 			sourceTree = "<group>";
@@ -1162,6 +1168,7 @@
 		DDF6999C2C5AAA4C0058A8D9 /* Views */ = {
 			isa = PBXGroup;
 			children = (
+				DD8316492DE4C504004467AA /* SettingsStepperRow.swift */,
 				DD8316432DE47CA9004467AA /* BGPicker.swift */,
 				DDF6999D2C5AAA640058A8D9 /* ErrorMessageView.swift */,
 				DD16AF0E2C99592F00FB655A /* HKQuantityInputView.swift */,
@@ -1209,7 +1216,6 @@
 			isa = PBXGroup;
 			children = (
 				DD7E19802ACDA0EA00DBD158 /* Nightscout */,
-				DDCF979D24C2382A002C9752 /* AppStateController.swift */,
 				FC7CE589248ABEA3001F83B8 /* AlarmSound.swift */,
 				FC16A97924996673003D6245 /* NightScout.swift */,
 				FC16A97C24996747003D6245 /* SpeakBG.swift */,
@@ -1425,6 +1431,7 @@
 		FCC688542489367300A0279D /* Helpers */ = {
 			isa = PBXGroup;
 			children = (
+				DD83164B2DE4DB3A004467AA /* BinaryFloatingPoint+localized.swift */,
 				DD4AFB3A2DB55CB600BB593F /* TimeOfDay.swift */,
 				DD7B0D432D730A320063DCB6 /* CycleHelper.swift */,
 				DDF6999C2C5AAA4C0058A8D9 /* Views */,
@@ -1457,7 +1464,6 @@
 				FC97881B2485969B00A7906C /* MainViewController.swift */,
 				FC97881D2485969B00A7906C /* NightScoutViewController.swift */,
 				FCFEECA1248857A600402A7F /* SettingsViewController.swift */,
-				DDCF979724C1489C002C9752 /* GraphSettingsViewController.swift */,
 				DDCF979924C14DB4002C9752 /* WatchSettingsViewController.swift */,
 			);
 			path = ViewControllers;
@@ -1812,20 +1818,19 @@
 				DD7FFAFD2A72953000C3A304 /* EKEventStore+Extensions.swift in Sources */,
 				FCC6886724898F8000A0279D /* UserDefaultsValue.swift in Sources */,
 				DD7F4C092DD504A700D449E9 /* OverrideStartCondition.swift in Sources */,
-				DDCF979E24C2382A002C9752 /* AppStateController.swift in Sources */,
 				DDEF503F2D32754F00999A5D /* ProfileTask.swift in Sources */,
 				DD7F4C052DD4BBE200D449E9 /* NotLoopingCondition.swift in Sources */,
 				DD5334212C60EBEE00062F9D /* InsulinCartridgeChange.swift in Sources */,
 				DD7F4B9D2DD1EAE500D449E9 /* AlarmAudioSection.swift in Sources */,
 				FC97881E2485969B00A7906C /* NightScoutViewController.swift in Sources */,
 				DD608A0A2C23593900F91132 /* SMB.swift in Sources */,
-				DDCF979824C1489C002C9752 /* GraphSettingsViewController.swift in Sources */,
 				FC3AE7B5249E8E0E00AAE1E0 /* LoopFollow.xcdatamodeld in Sources */,
 				DD7F4C252DD7B20700D449E9 /* AlarmType+timeUnit.swift in Sources */,
 				DDC7E5152DBCFA7900EB1127 /* SnoozerViewController.swift in Sources */,
 				DD7F4C072DD5042F00D449E9 /* OverrideStartAlarmEditor.swift in Sources */,
 				DDCC3A4B2DDBB5E4006F1C10 /* BatteryCondition.swift in Sources */,
 				DDDF6F492D479AF000884336 /* NoRemoteView.swift in Sources */,
+				DD83164A2DE4C504004467AA /* SettingsStepperRow.swift in Sources */,
 				DD0650ED2DCE9371004D3B41 /* HighBgAlarmEditor.swift in Sources */,
 				DD7F4C172DD63FA700D449E9 /* RecBolusCondition.swift in Sources */,
 				FCC6886F2489A53800A0279D /* AppConstants.swift in Sources */,
@@ -1958,6 +1963,7 @@
 				DD98F54424BCEFEE0007425A /* ShareClientExtension.swift in Sources */,
 				DD9ED0CE2D35587A000D2A63 /* LogEntry.swift in Sources */,
 				DD0C0C602C415B9D00DBADDF /* ProfileManager.swift in Sources */,
+				DD83164C2DE4DB3A004467AA /* BinaryFloatingPoint+localized.swift in Sources */,
 				DD4AFB3D2DB55D2900BB593F /* AlarmConfiguration.swift in Sources */,
 				DDF699962C5582290058A8D9 /* TextFieldWithToolBar.swift in Sources */,
 				DD91E4DD2BDEC3F8002D9E97 /* GlucoseConversion.swift in Sources */,
@@ -1985,6 +1991,7 @@
 				DD7F4C152DD51FEB00D449E9 /* TempTargetEndAlarmEditor.swift in Sources */,
 				DD2C2E542D3C37DC006413A5 /* DexcomSettingsViewModel.swift in Sources */,
 				FCE537BC249A4D7D00F80BF8 /* carbBolusArrays.swift in Sources */,
+				DD8316462DE49B09004467AA /* GraphSettingsView.swift in Sources */,
 				DD5334232C60ED3600062F9D /* IAge.swift in Sources */,
 				FCD2A27D24C9D044009F7B7B /* Globals.swift in Sources */,
 				DD0C0C642C45A59400DBADDF /* HKUnit+Extensions.swift in Sources */,
@@ -2000,6 +2007,7 @@
 				DD16AF0D2C98485400FB655A /* SecureStorageValue.swift in Sources */,
 				DDC7E5CF2DC77C2000EB1127 /* SnoozerViewModel.swift in Sources */,
 				DD2C2E4F2D3B8AF1006413A5 /* NightscoutSettingsView.swift in Sources */,
+				DD8316482DE49EE5004467AA /* Storage+Migrate.swift in Sources */,
 				DD1A97162D4294B3000DDC11 /* AdvancedSettingsViewModel.swift in Sources */,
 				FCA2DDE62501095000254A8C /* Timers.swift in Sources */,
 				DD2C2E512D3B8B0C006413A5 /* NightscoutSettingsViewModel.swift in Sources */,

+ 0 - 16
LoopFollow/Application/SceneDelegate.swift

@@ -9,8 +9,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
     var window: UIWindow?
     let synthesizer = AVSpeechSynthesizer()
 
-    let appStateController = AppStateController()
-
     func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) {
         // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
         // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
@@ -23,20 +21,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
         else {
             return
         }
-
-        // set the main controllers' connection to the app sate
-        // other controllers that need to know app state are setup programatically
-        for i in 0 ..< viewControllers.count {
-            if let vc = viewControllers[i] as? MainViewController {
-                vc.appStateController = appStateController
-            }
-            if let vc = viewControllers[i] as? NightscoutViewController {
-                vc.appStateController = appStateController
-            }
-            if let vc = viewControllers[i] as? SettingsViewController {
-                vc.appStateController = appStateController
-            }
-        }
     }
 
     func sceneDidDisconnect(_: UIScene) {

+ 0 - 18
LoopFollow/Controllers/AppStateController.swift

@@ -1,18 +0,0 @@
-// LoopFollow
-// AppStateController.swift
-// Created by Jose Paredes on 2020-07-18.
-
-import Foundation
-
-// App Sate used used to changes to the app view controllers (Settings, for example)
-// Recommended way of utilizing is when viewVillAppear(..) is called,
-// look in the app state to see if further action must be t
-
-// Setup App States to comminicate between views
-
-class AppStateController {
-    // add app states & methods here
-
-    // Chart Settings State
-    var chartSettingsChanged: Bool = false
-}

+ 84 - 84
LoopFollow/Controllers/Graphs.swift

@@ -240,7 +240,7 @@ extension MainViewController {
     func createGraph() {
         // Create the BG Graph Data
         let bgChartEntry = [ChartDataEntry]()
-        let maxBG: Float = UserDefaultsRepository.minBGScale.value
+        let maxBG = Storage.shared.minBGScale.value
 
         // Setup BG line details
         let lineBG = LineChartDataSet(entries: bgChartEntry, label: "")
@@ -251,12 +251,12 @@ extension MainViewController {
         lineBG.highlightEnabled = true
         lineBG.drawValuesEnabled = false
 
-        if UserDefaultsRepository.showLines.value {
+        if Storage.shared.showLines.value {
             lineBG.lineWidth = 2
         } else {
             lineBG.lineWidth = 0
         }
-        if UserDefaultsRepository.showDots.value {
+        if Storage.shared.showDots.value {
             lineBG.drawCirclesEnabled = true
         } else {
             lineBG.drawCirclesEnabled = false
@@ -275,12 +275,12 @@ extension MainViewController {
         linePrediction.highlightEnabled = true
         linePrediction.drawValuesEnabled = false
 
-        if UserDefaultsRepository.showLines.value {
+        if Storage.shared.showLines.value {
             linePrediction.lineWidth = 2
         } else {
             linePrediction.lineWidth = 0
         }
-        if UserDefaultsRepository.showDots.value {
+        if Storage.shared.showDots.value {
             linePrediction.drawCirclesEnabled = true
         } else {
             linePrediction.drawCirclesEnabled = false
@@ -290,7 +290,7 @@ extension MainViewController {
 
         // create Basal graph data
         let chartEntry = [ChartDataEntry]()
-        let maxBasal = UserDefaultsRepository.minBasalScale.value
+        let maxBasal = Storage.shared.minBasalScale.value
         let lineBasal = LineChartDataSet(entries: chartEntry, label: "")
         lineBasal.setDrawHighlightIndicators(false)
         lineBasal.setColor(NSUIColor.systemBlue, alpha: 0.5)
@@ -322,7 +322,7 @@ extension MainViewController {
         lineBolus.drawCirclesEnabled = true
         lineBolus.drawFilledEnabled = false
 
-        if UserDefaultsRepository.showValues.value {
+        if Storage.shared.showValues.value {
             lineBolus.drawValuesEnabled = true
             lineBolus.highlightEnabled = false
         } else {
@@ -348,7 +348,7 @@ extension MainViewController {
         lineCarbs.drawCirclesEnabled = true
         lineCarbs.drawFilledEnabled = false
 
-        if UserDefaultsRepository.showValues.value {
+        if Storage.shared.showValues.value {
             lineCarbs.drawValuesEnabled = true
             lineCarbs.highlightEnabled = false
         } else {
@@ -469,12 +469,12 @@ extension MainViewController {
         COBlinePrediction.highlightEnabled = true
         COBlinePrediction.drawValuesEnabled = false
 
-        if UserDefaultsRepository.showLines.value {
+        if Storage.shared.showLines.value {
             COBlinePrediction.lineWidth = 2
         } else {
             COBlinePrediction.lineWidth = 0
         }
-        if UserDefaultsRepository.showDots.value {
+        if Storage.shared.showDots.value {
             COBlinePrediction.drawCirclesEnabled = true
         } else {
             COBlinePrediction.drawCirclesEnabled = false
@@ -493,12 +493,12 @@ extension MainViewController {
         IOBlinePrediction.highlightEnabled = true
         IOBlinePrediction.drawValuesEnabled = false
 
-        if UserDefaultsRepository.showLines.value {
+        if Storage.shared.showLines.value {
             IOBlinePrediction.lineWidth = 2
         } else {
             IOBlinePrediction.lineWidth = 0
         }
-        if UserDefaultsRepository.showDots.value {
+        if Storage.shared.showDots.value {
             IOBlinePrediction.drawCirclesEnabled = true
         } else {
             IOBlinePrediction.drawCirclesEnabled = false
@@ -517,12 +517,12 @@ extension MainViewController {
         UAMlinePrediction.highlightEnabled = true
         UAMlinePrediction.drawValuesEnabled = false
 
-        if UserDefaultsRepository.showLines.value {
+        if Storage.shared.showLines.value {
             UAMlinePrediction.lineWidth = 2
         } else {
             UAMlinePrediction.lineWidth = 0
         }
-        if UserDefaultsRepository.showDots.value {
+        if Storage.shared.showDots.value {
             UAMlinePrediction.drawCirclesEnabled = true
         } else {
             UAMlinePrediction.drawCirclesEnabled = false
@@ -541,12 +541,12 @@ extension MainViewController {
         ZTlinePrediction.highlightEnabled = true
         ZTlinePrediction.drawValuesEnabled = false
 
-        if UserDefaultsRepository.showLines.value {
+        if Storage.shared.showLines.value {
             ZTlinePrediction.lineWidth = 2
         } else {
             ZTlinePrediction.lineWidth = 0
         }
-        if UserDefaultsRepository.showDots.value {
+        if Storage.shared.showDots.value {
             ZTlinePrediction.drawCirclesEnabled = true
         } else {
             ZTlinePrediction.drawCirclesEnabled = false
@@ -570,7 +570,7 @@ extension MainViewController {
         lineSmb.drawCirclesEnabled = false
         lineSmb.drawFilledEnabled = false
 
-        if UserDefaultsRepository.showValues.value {
+        if Storage.shared.showValues.value {
             lineSmb.drawValuesEnabled = true
             lineSmb.highlightEnabled = false
         } else {
@@ -624,13 +624,13 @@ extension MainViewController {
 
         // Add lower red line based on low alert value
         let ll = ChartLimitLine()
-        ll.limit = Double(UserDefaultsRepository.lowLine.value)
+        ll.limit = Storage.shared.lowLine.value
         ll.lineColor = NSUIColor.systemRed.withAlphaComponent(0.5)
         BGChart.rightAxis.addLimitLine(ll)
 
         // Add upper yellow line based on low alert value
         let ul = ChartLimitLine()
-        ul.limit = Double(UserDefaultsRepository.highLine.value)
+        ul.limit = Storage.shared.highLine.value
         ul.lineColor = NSUIColor.systemYellow.withAlphaComponent(0.5)
         BGChart.rightAxis.addLimitLine(ul)
 
@@ -689,7 +689,7 @@ extension MainViewController {
         ul.lineWidth = 1
         BGChart.xAxis.addLimitLine(ul)
 
-        if UserDefaultsRepository.show30MinLine.value {
+        if Storage.shared.show30MinLine.value {
             let ul2 = ChartLimitLine()
             ul2.limit = Double(dateTimeUtils.getNowTimeIntervalUTC().advanced(by: -30 * 60))
             ul2.lineColor = NSUIColor.systemBlue.withAlphaComponent(0.5)
@@ -697,7 +697,7 @@ extension MainViewController {
             BGChart.xAxis.addLimitLine(ul2)
         }
 
-        if UserDefaultsRepository.showDIALines.value {
+        if Storage.shared.showDIALines.value {
             for i in 1 ..< 7 {
                 let ul = ChartLimitLine()
                 ul.limit = Double(dateTimeUtils.getNowTimeIntervalUTC() - Double(i * 60 * 60))
@@ -710,7 +710,7 @@ extension MainViewController {
             }
         }
 
-        if UserDefaultsRepository.show90MinLine.value {
+        if Storage.shared.show90MinLine.value {
             let ul3 = ChartLimitLine()
             ul3.limit = Double(dateTimeUtils.getNowTimeIntervalUTC().advanced(by: -90 * 60))
             ul3.lineColor = NSUIColor.systemOrange.withAlphaComponent(0.5)
@@ -721,9 +721,9 @@ extension MainViewController {
 
     func createMidnightLines() {
         // Draw a line at midnight: useful when showing multiple days of data
-        if UserDefaultsRepository.showMidnightLines.value {
+        if Storage.shared.showMidnightLines.value {
             var midnightTimeInterval = dateTimeUtils.getTimeIntervalMidnightToday()
-            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            let graphHours = 24 * Storage.shared.downloadDays.value
             let graphStart = dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours)
             while midnightTimeInterval > graphStart {
                 // Large chart
@@ -752,14 +752,14 @@ extension MainViewController {
         let dataIndexPrediction = 1
         let lineBG = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
         let linePrediction = BGChart.lineData!.dataSets[dataIndexPrediction] as! LineChartDataSet
-        if UserDefaultsRepository.showLines.value {
+        if Storage.shared.showLines.value {
             lineBG.lineWidth = 2
             linePrediction.lineWidth = 2
         } else {
             lineBG.lineWidth = 0
             linePrediction.lineWidth = 0
         }
-        if UserDefaultsRepository.showDots.value {
+        if Storage.shared.showDots.value {
             lineBG.drawCirclesEnabled = true
             linePrediction.drawCirclesEnabled = true
         } else {
@@ -774,13 +774,13 @@ extension MainViewController {
 
         // Add lower red line based on low alert value
         let ll = ChartLimitLine()
-        ll.limit = Double(UserDefaultsRepository.lowLine.value)
+        ll.limit = Storage.shared.lowLine.value
         ll.lineColor = NSUIColor.systemRed.withAlphaComponent(0.5)
         BGChart.rightAxis.addLimitLine(ll)
 
         // Add upper yellow line based on low alert value
         let ul = ChartLimitLine()
-        ul.limit = Double(UserDefaultsRepository.highLine.value)
+        ul.limit = Storage.shared.highLine.value
         ul.lineColor = NSUIColor.systemYellow.withAlphaComponent(0.5)
         BGChart.rightAxis.addLimitLine(ul)
 
@@ -802,22 +802,22 @@ extension MainViewController {
         let smallChart = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
         mainChart.removeAll(keepingCapacity: false)
         smallChart.removeAll(keepingCapacity: false)
-        let maxBGOffset: Float = 50
+        let maxBGOffset: Double = 50
 
         var colors = [NSUIColor]()
 
-        topBG = UserDefaultsRepository.minBGScale.value
+        topBG = Storage.shared.minBGScale.value
         for i in 0 ..< entries.count {
-            if Float(entries[i].sgv) > topBG - maxBGOffset {
-                topBG = Float(entries[i].sgv) + maxBGOffset
+            if Double(entries[i].sgv) > topBG - maxBGOffset {
+                topBG = Double(entries[i].sgv) + maxBGOffset
             }
             let value = ChartDataEntry(x: Double(entries[i].date), y: Double(entries[i].sgv), data: formatPillText(line1: Localizer.toDisplayUnits(String(entries[i].sgv)), time: entries[i].date))
             mainChart.append(value)
             smallChart.append(value)
 
-            if Double(entries[i].sgv) >= Double(UserDefaultsRepository.highLine.value) {
+            if Double(entries[i].sgv) >= Storage.shared.highLine.value {
                 colors.append(NSUIColor.systemYellow)
-            } else if Double(entries[i].sgv) <= Double(UserDefaultsRepository.lowLine.value) {
+            } else if Double(entries[i].sgv) <= Storage.shared.lowLine.value {
                 colors.append(NSUIColor.systemRed)
             } else {
                 colors.append(NSUIColor.systemGreen)
@@ -877,17 +877,17 @@ extension MainViewController {
         smallChart.clear()
 
         var colors = [NSUIColor]()
-        let maxBGOffset: Float = 20
+        let maxBGOffset: Double = 20
 
-        topPredictionBG = UserDefaultsRepository.minBGScale.value
+        topPredictionBG = Storage.shared.minBGScale.value
         for i in 0 ..< predictionData.count {
             var predictionVal = Double(predictionData[i].sgv)
-            if Float(predictionVal) > topPredictionBG - maxBGOffset {
-                topPredictionBG = Float(predictionVal) + maxBGOffset
+            if Double(predictionVal) > topPredictionBG - maxBGOffset {
+                topPredictionBG = predictionVal + maxBGOffset
             }
 
             if i == 0 {
-                if UserDefaultsRepository.showDots.value {
+                if Storage.shared.showDots.value {
                     colors.append((color ?? NSUIColor.systemPurple).withAlphaComponent(0.0))
                 } else {
                     colors.append((color ?? NSUIColor.systemPurple).withAlphaComponent(1.0))
@@ -930,12 +930,12 @@ extension MainViewController {
         var dataIndex = 2
         BGChart.lineData?.dataSets[dataIndex].clear()
         BGChartFull.lineData?.dataSets[dataIndex].clear()
-        var maxBasal = UserDefaultsRepository.minBasalScale.value
+        var maxBasal = Storage.shared.minBasalScale.value
         var maxBasalSmall = 0.0
         for i in 0 ..< basalData.count {
             let value = ChartDataEntry(x: Double(basalData[i].date), y: Double(basalData[i].basalRate), data: formatPillText(line1: String(basalData[i].basalRate), time: basalData[i].date))
             BGChart.data?.dataSets[dataIndex].addEntry(value)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 BGChartFull.data?.dataSets[dataIndex].addEntry(value)
             }
             if basalData[i].basalRate > maxBasal {
@@ -953,7 +953,7 @@ extension MainViewController {
         BGChart.data?.notifyDataChanged()
         BGChart.notifyDataSetChanged()
 
-        if UserDefaultsRepository.smallGraphTreatments.value {
+        if Storage.shared.smallGraphTreatments.value {
             BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
             BGChartFull.data?.notifyDataChanged()
             BGChartFull.notifyDataSetChanged()
@@ -967,7 +967,7 @@ extension MainViewController {
         for i in 0 ..< basalScheduleData.count {
             let value = ChartDataEntry(x: Double(basalScheduleData[i].date), y: Double(basalScheduleData[i].basalRate))
             BGChart.data?.dataSets[dataIndex].addEntry(value)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 BGChartFull.data?.dataSets[dataIndex].addEntry(value)
             }
         }
@@ -975,7 +975,7 @@ extension MainViewController {
         BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
         BGChart.data?.notifyDataChanged()
         BGChart.notifyDataSetChanged()
-        if UserDefaultsRepository.smallGraphTreatments.value {
+        if Storage.shared.smallGraphTreatments.value {
             BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
             BGChartFull.data?.notifyDataChanged()
             BGChartFull.notifyDataSetChanged()
@@ -1010,12 +1010,12 @@ extension MainViewController {
             }
 
             // skip if outside of visible area
-            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            let graphHours = 24 * Storage.shared.downloadDays.value
             if dateTimeStamp < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
 
             let dot = ChartDataEntry(x: Double(dateTimeStamp), y: Double(bolusData[i].sgv), data: formatter.string(from: NSNumber(value: bolusData[i].value)))
             mainChart.addEntry(dot)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 smallChart.addEntry(dot)
             }
         }
@@ -1040,7 +1040,7 @@ extension MainViewController {
         BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
         BGChart.data?.notifyDataChanged()
         BGChart.notifyDataSetChanged()
-        if UserDefaultsRepository.smallGraphTreatments.value {
+        if Storage.shared.smallGraphTreatments.value {
             BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
             BGChartFull.data?.notifyDataChanged()
             BGChartFull.notifyDataSetChanged()
@@ -1082,12 +1082,12 @@ extension MainViewController {
                 dateTimeStamp = dateTimeStamp - 150
             }
 
-            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            let graphHours = 24 * Storage.shared.downloadDays.value
             if dateTimeStamp < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
 
             let dot = ChartDataEntry(x: Double(dateTimeStamp), y: Double(smbData[i].sgv), data: formatter.string(from: NSNumber(value: smbData[i].value)))
             mainChart.addEntry(dot)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 smallChart.addEntry(dot)
             }
         }
@@ -1095,7 +1095,7 @@ extension MainViewController {
         BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
         BGChart.data?.notifyDataChanged()
         BGChart.notifyDataSetChanged()
-        if UserDefaultsRepository.smallGraphTreatments.value {
+        if Storage.shared.smallGraphTreatments.value {
             BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
             BGChartFull.data?.notifyDataChanged()
             BGChartFull.notifyDataSetChanged()
@@ -1106,8 +1106,8 @@ extension MainViewController {
         var dataIndex = 4
         var mainChart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
         var smallChart = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
-        mainChart.clear()
-        smallChart.clear()
+        mainChart.removeAll(keepingCapacity: true)
+        smallChart.removeAll(keepingCapacity: true)
 
         var colors = [NSUIColor]()
         for i in 0 ..< carbData.count {
@@ -1119,7 +1119,7 @@ extension MainViewController {
             var valueString: String = formatter.string(from: NSNumber(value: carbData[i].value))!
 
             var hours = 3
-            if carbData[i].absorptionTime > 0, UserDefaultsRepository.showAbsorption.value {
+            if carbData[i].absorptionTime > 0, Storage.shared.showAbsorption.value {
                 hours = carbData[i].absorptionTime / 60
                 valueString += " " + String(hours) + "h"
             }
@@ -1131,7 +1131,7 @@ extension MainViewController {
             colors.append(NSUIColor.systemOrange.withAlphaComponent(1.0))
 
             // skip if outside of visible area
-            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            let graphHours = 24 * Storage.shared.downloadDays.value
             if dateTimeStamp < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
 
             if carbShift {
@@ -1140,7 +1140,7 @@ extension MainViewController {
 
             let dot = ChartDataEntry(x: Double(dateTimeStamp), y: Double(carbData[i].sgv), data: valueString)
             BGChart.data?.dataSets[dataIndex].addEntry(dot)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 BGChartFull.data?.dataSets[dataIndex].addEntry(dot)
             }
         }
@@ -1165,7 +1165,7 @@ extension MainViewController {
         BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
         BGChart.data?.notifyDataChanged()
         BGChart.notifyDataSetChanged()
-        if UserDefaultsRepository.smallGraphTreatments.value {
+        if Storage.shared.smallGraphTreatments.value {
             BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
             BGChartFull.data?.notifyDataChanged()
             BGChartFull.notifyDataSetChanged()
@@ -1184,12 +1184,12 @@ extension MainViewController {
             formatter.minimumIntegerDigits = 1
 
             // skip if outside of visible area
-            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            let graphHours = 24 * Storage.shared.downloadDays.value
             if bgCheckData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
 
             let value = ChartDataEntry(x: Double(bgCheckData[i].date), y: Double(bgCheckData[i].sgv), data: formatPillText(line1: Localizer.toDisplayUnits(String(bgCheckData[i].sgv)), time: bgCheckData[i].date))
             BGChart.data?.dataSets[dataIndex].addEntry(value)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 BGChartFull.data?.dataSets[dataIndex].addEntry(value)
             }
         }
@@ -1197,7 +1197,7 @@ extension MainViewController {
         BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
         BGChart.data?.notifyDataChanged()
         BGChart.notifyDataSetChanged()
-        if UserDefaultsRepository.smallGraphTreatments.value {
+        if Storage.shared.smallGraphTreatments.value {
             BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
             BGChartFull.data?.notifyDataChanged()
             BGChartFull.notifyDataSetChanged()
@@ -1211,12 +1211,12 @@ extension MainViewController {
         let thisData = suspendGraphData
         for i in 0 ..< thisData.count {
             // skip if outside of visible area
-            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            let graphHours = 24 * Storage.shared.downloadDays.value
             if thisData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
 
             let value = ChartDataEntry(x: Double(thisData[i].date), y: Double(thisData[i].sgv), data: formatPillText(line1: "Suspend Pump", time: thisData[i].date))
             BGChart.data?.dataSets[dataIndex].addEntry(value)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 BGChartFull.data?.dataSets[dataIndex].addEntry(value)
             }
         }
@@ -1224,7 +1224,7 @@ extension MainViewController {
         BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
         BGChart.data?.notifyDataChanged()
         BGChart.notifyDataSetChanged()
-        if UserDefaultsRepository.smallGraphTreatments.value {
+        if Storage.shared.smallGraphTreatments.value {
             BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
             BGChartFull.data?.notifyDataChanged()
             BGChartFull.notifyDataSetChanged()
@@ -1238,12 +1238,12 @@ extension MainViewController {
         let thisData = resumeGraphData
         for i in 0 ..< thisData.count {
             // skip if outside of visible area
-            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            let graphHours = 24 * Storage.shared.downloadDays.value
             if thisData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
 
             let value = ChartDataEntry(x: Double(thisData[i].date), y: Double(thisData[i].sgv), data: formatPillText(line1: "Resume Pump", time: thisData[i].date))
             BGChart.data?.dataSets[dataIndex].addEntry(value)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 BGChartFull.data?.dataSets[dataIndex].addEntry(value)
             }
         }
@@ -1251,7 +1251,7 @@ extension MainViewController {
         BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
         BGChart.data?.notifyDataChanged()
         BGChart.notifyDataSetChanged()
-        if UserDefaultsRepository.smallGraphTreatments.value {
+        if Storage.shared.smallGraphTreatments.value {
             BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
             BGChartFull.data?.notifyDataChanged()
             BGChartFull.notifyDataSetChanged()
@@ -1265,12 +1265,12 @@ extension MainViewController {
         let thisData = sensorStartGraphData
         for i in 0 ..< thisData.count {
             // skip if outside of visible area
-            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            let graphHours = 24 * Storage.shared.downloadDays.value
             if thisData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
 
             let value = ChartDataEntry(x: Double(thisData[i].date), y: Double(thisData[i].sgv), data: formatPillText(line1: "Start Sensor", time: thisData[i].date))
             BGChart.data?.dataSets[dataIndex].addEntry(value)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 BGChartFull.data?.dataSets[dataIndex].addEntry(value)
             }
         }
@@ -1278,7 +1278,7 @@ extension MainViewController {
         BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
         BGChart.data?.notifyDataChanged()
         BGChart.notifyDataSetChanged()
-        if UserDefaultsRepository.smallGraphTreatments.value {
+        if Storage.shared.smallGraphTreatments.value {
             BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
             BGChartFull.data?.notifyDataChanged()
             BGChartFull.notifyDataSetChanged()
@@ -1292,12 +1292,12 @@ extension MainViewController {
         let thisData = noteGraphData
         for i in 0 ..< thisData.count {
             // skip if outside of visible area
-            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            let graphHours = 24 * Storage.shared.downloadDays.value
             if thisData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
 
             let value = ChartDataEntry(x: Double(thisData[i].date), y: Double(thisData[i].sgv), data: formatPillText(line1: thisData[i].note, time: thisData[i].date))
             BGChart.data?.dataSets[dataIndex].addEntry(value)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 BGChartFull.data?.dataSets[dataIndex].addEntry(value)
             }
         }
@@ -1305,7 +1305,7 @@ extension MainViewController {
         BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
         BGChart.data?.notifyDataChanged()
         BGChart.notifyDataSetChanged()
-        if UserDefaultsRepository.smallGraphTreatments.value {
+        if Storage.shared.smallGraphTreatments.value {
             BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
             BGChartFull.data?.notifyDataChanged()
             BGChartFull.notifyDataSetChanged()
@@ -1316,7 +1316,7 @@ extension MainViewController {
         let entries = bgData
         var bgChartEntry = [ChartDataEntry]()
         var colors = [NSUIColor]()
-        var maxBG: Float = UserDefaultsRepository.minBGScale.value
+        var maxBG = Storage.shared.minBGScale.value
 
         let lineBG = LineChartDataSet(entries: bgChartEntry, label: "")
 
@@ -1346,7 +1346,7 @@ extension MainViewController {
 
         // create Basal graph data
         var chartEntry = [ChartDataEntry]()
-        var maxBasal = UserDefaultsRepository.minBasalScale.value
+        var maxBasal = Storage.shared.minBasalScale.value
         let lineBasal = LineChartDataSet(entries: chartEntry, label: "")
         lineBasal.setDrawHighlightIndicators(false)
         lineBasal.setColor(NSUIColor.systemBlue, alpha: 0.5)
@@ -1651,27 +1651,27 @@ extension MainViewController {
             // Shift dots 30 seconds to create an empty 0 space between consecutive temps
             let preStartDot = ChartDataEntry(x: Double(thisItem.date), y: yBottom, data: labelText)
             BGChart.data?.dataSets[dataIndex].addEntry(preStartDot)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 BGChartFull.data?.dataSets[dataIndex].addEntry(preStartDot)
             }
 
             let startDot = ChartDataEntry(x: Double(thisItem.date + 1), y: yTop, data: labelText)
             BGChart.data?.dataSets[dataIndex].addEntry(startDot)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 BGChartFull.data?.dataSets[dataIndex].addEntry(startDot)
             }
 
             // End Dot
             let endDot = ChartDataEntry(x: Double(thisItem.endDate - 2), y: yTop, data: labelText)
             BGChart.data?.dataSets[dataIndex].addEntry(endDot)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 BGChartFull.data?.dataSets[dataIndex].addEntry(endDot)
             }
 
             // Post end dot
             let postEndDot = ChartDataEntry(x: Double(thisItem.endDate - 1), y: yBottom, data: labelText)
             BGChart.data?.dataSets[dataIndex].addEntry(postEndDot)
-            if UserDefaultsRepository.smallGraphTreatments.value {
+            if Storage.shared.smallGraphTreatments.value {
                 BGChartFull.data?.dataSets[dataIndex].addEntry(postEndDot)
             }
         }
@@ -1679,7 +1679,7 @@ extension MainViewController {
         BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
         BGChart.data?.notifyDataChanged()
         BGChart.notifyDataSetChanged()
-        if UserDefaultsRepository.smallGraphTreatments.value {
+        if Storage.shared.smallGraphTreatments.value {
             BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
             BGChartFull.data?.notifyDataChanged()
             BGChartFull.notifyDataSetChanged()
@@ -1704,7 +1704,7 @@ extension MainViewController {
 
     func addEntryToCharts(entry: ChartDataEntry, chart: LineChartDataSet, smallChart: LineChartDataSet?) {
         chart.addEntry(entry)
-        if UserDefaultsRepository.smallGraphTreatments.value, let smallChart = smallChart {
+        if Storage.shared.smallGraphTreatments.value, let smallChart = smallChart {
             smallChart.addEntry(entry)
         }
     }
@@ -1722,7 +1722,7 @@ extension MainViewController {
         mainChartDataSet.clear()
 
         var smallChartDataSet: LineChartDataSet?
-        if UserDefaultsRepository.smallGraphTreatments.value,
+        if Storage.shared.smallGraphTreatments.value,
            let smallChartData = BGChartFull.lineData,
            smallChartData.dataSets.count > dataIndex,
            let smallDataSet = smallChartData.dataSets[dataIndex] as? LineChartDataSet
@@ -1871,16 +1871,16 @@ extension MainViewController {
         smallChart.clear()
 
         var colors = [NSUIColor]()
-        let maxBGOffset: Float = 20
+        let maxBGOffset: Double = 20
 
         for i in 0 ..< predictionData.count {
             let predictionVal = Double(predictionData[i].sgv)
-            if Float(predictionVal) > topPredictionBG - maxBGOffset {
-                topPredictionBG = Float(predictionVal) + maxBGOffset
+            if predictionVal > topPredictionBG - maxBGOffset {
+                topPredictionBG = predictionVal + maxBGOffset
             }
 
             if i == 0 {
-                if UserDefaultsRepository.showDots.value {
+                if Storage.shared.showDots.value {
                     colors.append(color.withAlphaComponent(0.0))
                 } else {
                     colors.append(color.withAlphaComponent(1.0))

+ 1 - 1
LoopFollow/Controllers/MainViewController+updateStats.swift

@@ -10,7 +10,7 @@ extension MainViewController {
     func updateStats() {
         if bgData.count > 0 {
             var lastDayOfData = bgData
-            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            let graphHours = 24 * Storage.shared.downloadDays.value
             // If we loaded more than 1 day of data, only use the last day for the stats
             if graphHours > 24 {
                 let oneDayAgo = dateTimeUtils.getTimeIntervalNHoursAgo(N: 24)

+ 4 - 4
LoopFollow/Controllers/Nightscout/BGData.swift

@@ -10,7 +10,7 @@ extension MainViewController {
     func webLoadDexShare() {
         // Dexcom Share only returns 24 hrs of data as of now
         // Requesting more just for consistency with NS
-        let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+        let graphHours = 24 * Storage.shared.downloadDays.value
         let count = graphHours * 12
         dexShare?.fetchData(count) { err, result in
             if let error = err {
@@ -52,8 +52,8 @@ extension MainViewController {
 
         var parameters: [String: String] = [:]
         let utcISODateFormatter = ISO8601DateFormatter()
-        let date = Calendar.current.date(byAdding: .day, value: -1 * UserDefaultsRepository.downloadDays.value, to: Date())!
-        parameters["count"] = "\(UserDefaultsRepository.downloadDays.value * 2 * 24 * 60 / 5)"
+        let date = Calendar.current.date(byAdding: .day, value: -1 * Storage.shared.downloadDays.value, to: Date())!
+        parameters["count"] = "\(Storage.shared.downloadDays.value * 2 * 24 * 60 / 5)"
         parameters["find[dateString][$gte]"] = utcISODateFormatter.string(from: date)
 
         // Exclude 'cal' entries
@@ -118,7 +118,7 @@ extension MainViewController {
 
     /// Processes incoming BG data.
     func ProcessDexBGData(data: [ShareGlucoseData], sourceName: String) {
-        let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+        let graphHours = 24 * Storage.shared.downloadDays.value
 
         guard !data.isEmpty else {
             LogManager.shared.log(category: .nightscout, message: "No bg data received. Skipping processing.", limitIdentifier: "No bg data received. Skipping processing.")

+ 1 - 1
LoopFollow/Controllers/Nightscout/DeviceStatusLoop.swift

@@ -69,7 +69,7 @@ extension MainViewController {
                     if UserDefaultsRepository.downloadPrediction.value, previousLastLoopTime < lastLoopTime {
                         predictionData.removeAll()
                         var predictionTime = lastLoopTime
-                        let toLoad = Int(UserDefaultsRepository.predictionToLoad.value * 12)
+                        let toLoad = Int(Storage.shared.predictionToLoad.value * 12)
                         var i = 0
                         while i <= toLoad {
                             if i < prediction.count {

+ 2 - 2
LoopFollow/Controllers/Nightscout/DeviceStatusOpenAPS.swift

@@ -181,7 +181,7 @@ extension MainViewController {
 
             let predictioncolor = UIColor.systemGray
             PredictionLabel.textColor = predictioncolor
-            topPredictionBG = UserDefaultsRepository.minBGScale.value
+            topPredictionBG = Storage.shared.minBGScale.value
             if let predbgdata = predBGsData {
                 let predictionTypes: [(type: String, colorName: String, dataIndex: Int)] = [
                     ("ZT", "ZT", 12),
@@ -197,7 +197,7 @@ extension MainViewController {
                     var predictionData = [ShareGlucoseData]()
                     if let graphdata = predbgdata[type] as? [Double] {
                         var predictionTime = updatedTime ?? Date().timeIntervalSince1970
-                        let toLoad = Int(UserDefaultsRepository.predictionToLoad.value * 12)
+                        let toLoad = Int(Storage.shared.predictionToLoad.value * 12)
 
                         for i in 0 ... toLoad {
                             if i < graphdata.count {

+ 2 - 2
LoopFollow/Controllers/Nightscout/Profile.swift

@@ -36,7 +36,7 @@ extension MainViewController {
 
         var basalSegments: [DataStructs.basalProfileSegment] = []
 
-        let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+        let graphHours = 24 * Storage.shared.downloadDays.value
         // Build scheduled basal segments from right to left by
         // moving pointers to the current midnight and current basal
         var midnight = dateTimeUtils.getTimeIntervalMidnightToday()
@@ -69,7 +69,7 @@ extension MainViewController {
 
         var firstPass = true
         // Runs the scheduled basal to the end of the prediction line
-        var predictionEndTime = dateTimeUtils.getNowTimeIntervalUTC() + (3600 * UserDefaultsRepository.predictionToLoad.value)
+        var predictionEndTime = dateTimeUtils.getNowTimeIntervalUTC() + (3600 * Storage.shared.predictionToLoad.value)
         basalScheduleData.removeAll()
 
         for i in 0 ..< basalSegments.count {

+ 1 - 1
LoopFollow/Controllers/Nightscout/Treatments.swift

@@ -10,7 +10,7 @@ extension MainViewController {
     func WebLoadNSTreatments() {
         if !UserDefaultsRepository.downloadTreatments.value { return }
 
-        let startTimeString = dateTimeUtils.getDateTimeString(addingDays: -1 * UserDefaultsRepository.downloadDays.value)
+        let startTimeString = dateTimeUtils.getDateTimeString(addingDays: -1 * Storage.shared.downloadDays.value)
         let currentTimeString = dateTimeUtils.getDateTimeString(addingHours: 6)
         let parameters: [String: String] = [
             "find[created_at][$gte]": startTimeString,

+ 1 - 2
LoopFollow/Controllers/Nightscout/Treatments/Carbs.swift

@@ -39,7 +39,7 @@ extension MainViewController {
                 offset = bolusTime.offset ? 70 : 20
             }
 
-            if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (3600 * UserDefaultsRepository.predictionToLoad.value)) {
+            if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (3600 * Storage.shared.predictionToLoad.value)) {
                 // Make the dot
                 let dot = carbGraphStruct(value: Double(carbs), date: Double(dateTimeStamp), sgv: Int(sgv.sgv + Double(offset)), absorptionTime: absorptionTime)
                 carbData.append(dot)
@@ -55,7 +55,6 @@ extension MainViewController {
         var totalCarbs = 0.0
 
         let calendar = Calendar.current
-        let now = Date()
 
         for entry in entries {
             var carbDate = ""

+ 2 - 2
LoopFollow/Controllers/Nightscout/Treatments/Overrides.swift

@@ -21,8 +21,8 @@ extension MainViewController {
         }
 
         let now = Date().timeIntervalSince1970
-        let maxEndDate = now + UserDefaultsRepository.predictionToLoad.value * 3600
-        let graphHorizon = dateTimeUtils.getTimeIntervalNHoursAgo(N: 24 * UserDefaultsRepository.downloadDays.value)
+        let maxEndDate = now + Storage.shared.predictionToLoad.value * 3600
+        let graphHorizon = dateTimeUtils.getTimeIntervalNHoursAgo(N: 24 * Storage.shared.downloadDays.value)
 
         for i in 0 ..< sorted.count {
             let e = sorted[i]

+ 1 - 1
LoopFollow/Controllers/Nightscout/Treatments/TemporaryTarget.swift

@@ -17,7 +17,7 @@ extension MainViewController {
             guard let parsedDate = NightscoutUtils.parseDate(dateStr) else { continue }
 
             var dateTimeStamp = parsedDate.timeIntervalSince1970
-            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            let graphHours = 24 * Storage.shared.downloadDays.value
             if dateTimeStamp < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) {
                 dateTimeStamp = dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours)
             }

+ 2 - 2
LoopFollow/Controllers/Stats.swift

@@ -27,9 +27,9 @@ class StatsData {
 
         for i in 0 ..< bgData.count {
             // Set low/range/high counts for pie chart and %'s
-            if Float(bgData[i].sgv) <= UserDefaultsRepository.lowLine.value {
+            if Double(bgData[i].sgv) <= Storage.shared.lowLine.value {
                 countLow += 1
-            } else if Float(bgData[i].sgv) >= UserDefaultsRepository.highLine.value {
+            } else if Double(bgData[i].sgv) >= Storage.shared.highLine.value {
                 countHigh += 1
             } else {
                 countRange += 1

+ 13 - 0
LoopFollow/Helpers/BinaryFloatingPoint+localized.swift

@@ -0,0 +1,13 @@
+// LoopFollow
+// BinaryFloatingPoint+localized.swift
+// Created by Jonas Björkert on 2025-05-26.
+
+import Foundation
+
+extension BinaryFloatingPoint {
+    func localized(maxFractionDigits: Int) -> String {
+        let style = FloatingPointFormatStyle<Self>()
+            .precision(.fractionLength(0 ... maxFractionDigits))
+        return style.format(self)
+    }
+}

+ 43 - 0
LoopFollow/Helpers/Views/SettingsStepperRow.swift

@@ -0,0 +1,43 @@
+// LoopFollow
+// SettingsStepperRow.swift
+// Created by Jonas Björkert on 2025-05-26.
+
+import SwiftUI
+
+struct SettingsStepperRow<Value>: View
+    where Value: Strideable & Comparable,
+    Value.Stride: SignedNumeric & Comparable
+{
+    let title: String
+    let range: ClosedRange<Value>
+    let step: Value.Stride
+
+    private let format: (Value) -> String
+
+    @Binding private var value: Value
+
+    init(
+        title: String,
+        range: ClosedRange<Value>,
+        step: Value.Stride,
+        value: Binding<Value>,
+        format: @escaping (Value) -> String = { "\($0)" }
+    ) {
+        self.title = title
+        self.range = range
+        self.step = step
+        _value = value
+        self.format = format
+    }
+
+    var body: some View {
+        Stepper(value: $value, in: range, step: step) {
+            HStack {
+                Text(title)
+                Spacer()
+                Text(format(value))
+                    .foregroundColor(.secondary)
+            }
+        }
+    }
+}

+ 152 - 0
LoopFollow/Settings/GraphSettingsView.swift

@@ -0,0 +1,152 @@
+// LoopFollow
+// GraphSettingsView.swift
+// Created by Jonas Björkert on 2025-05-26.
+
+import SwiftUI
+
+struct GraphSettingsView: View {
+    @Environment(\.presentationMode) private var presentationMode
+
+    // ── Stored settings ──────────────────────────────────────────────────────
+    @ObservedObject private var showDots = Storage.shared.showDots
+    @ObservedObject private var showLines = Storage.shared.showLines
+    @ObservedObject private var showValues = Storage.shared.showValues
+    @ObservedObject private var showAbsorption = Storage.shared.showAbsorption
+    @ObservedObject private var showDIALines = Storage.shared.showDIALines
+    @ObservedObject private var show30MinLine = Storage.shared.show30MinLine
+    @ObservedObject private var show90MinLine = Storage.shared.show90MinLine
+    @ObservedObject private var showMidnightLines = Storage.shared.showMidnightLines
+    @ObservedObject private var smallGraphTreatments = Storage.shared.smallGraphTreatments
+
+    @ObservedObject private var smallGraphHeight = Storage.shared.smallGraphHeight
+    @ObservedObject private var predictionToLoad = Storage.shared.predictionToLoad
+    @ObservedObject private var minBasalScale = Storage.shared.minBasalScale
+    @ObservedObject private var minBGScale = Storage.shared.minBGScale
+    @ObservedObject private var lowLine = Storage.shared.lowLine
+    @ObservedObject private var highLine = Storage.shared.highLine
+    @ObservedObject private var downloadDays = Storage.shared.downloadDays
+
+    // ─────────────────────────────────────────────────────────────────────────
+    private var nightscoutEnabled: Bool { IsNightscoutEnabled() }
+
+    var body: some View {
+        NavigationStack {
+            Form {
+                // ── Graph Display ────────────────────────────────────────────
+                Section("Graph Display") {
+                    Toggle("Display Dots", isOn: $showDots.value)
+                        .onChange(of: showDots.value) { _ in markDirty() }
+
+                    Toggle("Display Lines", isOn: $showLines.value)
+                        .onChange(of: showLines.value) { _ in markDirty() }
+
+                    if nightscoutEnabled {
+                        Toggle("Show DIA Lines", isOn: $showDIALines.value)
+                            .onChange(of: showDIALines.value) { _ in markDirty() }
+
+                        Toggle("Show −30 min Line", isOn: $show30MinLine.value)
+                            .onChange(of: show30MinLine.value) { _ in markDirty() }
+
+                        Toggle("Show −90 min Line", isOn: $show90MinLine.value)
+                            .onChange(of: show90MinLine.value) { _ in markDirty() }
+                    }
+
+                    Toggle("Show Midnight Lines", isOn: $showMidnightLines.value)
+                        .onChange(of: showMidnightLines.value) { _ in markDirty() }
+                }
+
+                // ── Treatments ───────────────────────────────────────────────
+                if nightscoutEnabled {
+                    Section("Treatments") {
+                        Toggle("Show Carb/Bolus Values", isOn: $showValues.value)
+                        Toggle("Show Carb Absorption", isOn: $showAbsorption.value)
+                        Toggle("Treatments on Small Graph",
+                               isOn: $smallGraphTreatments.value)
+                    }
+                }
+
+                // ── Small Graph ──────────────────────────────────────────────
+                Section("Small Graph") {
+                    SettingsStepperRow(
+                        title: "Height",
+                        range: 40 ... 80,
+                        step: 5,
+                        value: $smallGraphHeight.value,
+                        format: { "\(Int($0)) pt" }
+                    )
+                    .onChange(of: smallGraphHeight.value) { _ in markDirty() }
+                }
+
+                // ── Prediction ───────────────────────────────────────────────
+                if nightscoutEnabled {
+                    Section("Prediction") {
+                        SettingsStepperRow(
+                            title: "Hours of Prediction",
+                            range: 0 ... 6,
+                            step: 0.25,
+                            value: $predictionToLoad.value,
+                            format: { "\($0.localized(maxFractionDigits: 2)) h" }
+                        )
+                    }
+                }
+
+                // ── Basal / BG scale ─────────────────────────────────────────
+                if nightscoutEnabled {
+                    Section("Basal / BG Scale") {
+                        SettingsStepperRow(
+                            title: "Min Basal",
+                            range: 0.5 ... 20,
+                            step: 0.5,
+                            value: $minBasalScale.value,
+                            format: { "\($0.localized(maxFractionDigits: 1)) U/h" }
+                        )
+
+                        BGPicker(
+                            title: "Min BG Scale",
+                            range: 40 ... 400,
+                            value: $minBGScale.value
+                        )
+                        .onChange(of: minBGScale.value) { _ in markDirty() }
+                    }
+                }
+
+                // ── Target lines ─────────────────────────────────────────────
+                Section("Target Lines") {
+                    BGPicker(title: "Low BG Line",
+                             range: 40 ... 120,
+                             value: $lowLine.value)
+                        .onChange(of: lowLine.value) { _ in markDirty() }
+
+                    BGPicker(title: "High BG Line",
+                             range: 120 ... 400,
+                             value: $highLine.value)
+                        .onChange(of: highLine.value) { _ in markDirty() }
+                }
+
+                // ── History window ───────────────────────────────────────────
+                if nightscoutEnabled {
+                    Section("History") {
+                        SettingsStepperRow(
+                            title: "Show Days Back",
+                            range: 1 ... 4,
+                            step: 1,
+                            value: $downloadDays.value,
+                            format: { "\(Int($0)) d" }
+                        )
+                    }
+                }
+            }
+            .navigationTitle("Graph Settings")
+            .toolbar {
+                ToolbarItem(placement: .confirmationAction) {
+                    Button("Done") { presentationMode.wrappedValue.dismiss() }
+                }
+            }
+        }
+    }
+
+    /// Marks the chart as needing a redraw
+    private func markDirty() {
+        Observable.shared.chartSettingsChanged.value = true
+    }
+}

+ 2 - 0
LoopFollow/Storage/Observable.swift

@@ -28,5 +28,7 @@ class Observable {
 
     var debug = ObservableValue<Bool>(default: false)
 
+    var chartSettingsChanged = ObservableValue<Bool>(default: false)
+
     private init() {}
 }

+ 180 - 0
LoopFollow/Storage/Storage+Migrate.swift

@@ -0,0 +1,180 @@
+// LoopFollow
+// Storage+Migrate.swift
+// Created by Jonas Björkert on 2025-05-26.
+
+import Foundation
+
+extension Storage {
+    func migrate() {
+        // Helper: 1-to-1 type -----------------------------------------------------------------
+        func move<T: AnyConvertible & Equatable>(
+            _ legacy: @autoclosure () -> UserDefaultsValue<T>,
+            into newValue: StorageValue<T>
+        ) {
+            let item = legacy()
+            guard item.exists else { return }
+            newValue.value = item.value
+            item.setNil(key: item.key)
+        }
+
+        // Helper: Float  →  Double ------------------------------------------------------------
+        func moveFloatToDouble(
+            _ legacy: @autoclosure () -> UserDefaultsValue<Float>,
+            into newValue: StorageValue<Double>
+        ) {
+            let item = legacy()
+            guard item.exists else { return }
+            newValue.value = Double(item.value)
+            item.setNil(key: item.key)
+        }
+
+        if !UserDefaultsRepository.backgroundRefresh.value {
+            Storage.shared.backgroundRefreshType.value = .none
+            UserDefaultsRepository.backgroundRefresh.value = true
+        }
+
+        // Remove this in a year later than the release of the new Alarms [BEGIN]
+        let legacyColorBGText = UserDefaultsValue<Bool>(key: "colorBGText", default: true)
+        if legacyColorBGText.exists {
+            Storage.shared.colorBGText.value = legacyColorBGText.value
+            legacyColorBGText.setNil(key: "colorBGText")
+        }
+
+        let legacyAppBadge = UserDefaultsValue<Bool>(key: "appBadge", default: true)
+        if legacyAppBadge.exists {
+            Storage.shared.appBadge.value = legacyAppBadge.value
+            legacyAppBadge.setNil(key: "appBadge")
+        }
+
+        let legacyForceDarkMode = UserDefaultsValue<Bool>(key: "forceDarkMode", default: true)
+        if legacyForceDarkMode.exists {
+            Storage.shared.forceDarkMode.value = legacyForceDarkMode.value
+            legacyForceDarkMode.setNil(key: "forceDarkMode")
+        }
+
+        let legacyShowStats = UserDefaultsValue<Bool>(key: "showStats", default: true)
+        if legacyShowStats.exists {
+            Storage.shared.showStats.value = legacyShowStats.value
+            legacyShowStats.setNil(key: "showStats")
+        }
+
+        let legacyUseIFCC = UserDefaultsValue<Bool>(key: "useIFCC", default: false)
+        if legacyUseIFCC.exists {
+            Storage.shared.useIFCC.value = legacyUseIFCC.value
+            legacyUseIFCC.setNil(key: "useIFCC")
+        }
+
+        let legacyShowSmallGraph = UserDefaultsValue<Bool>(key: "showSmallGraph", default: true)
+        if legacyShowSmallGraph.exists {
+            Storage.shared.showSmallGraph.value = legacyShowSmallGraph.value
+            legacyShowSmallGraph.setNil(key: "showSmallGraph")
+        }
+
+        let legacyScreenlockSwitchState = UserDefaultsValue<Bool>(key: "screenlockSwitchState", default: true)
+        if legacyScreenlockSwitchState.exists {
+            Storage.shared.screenlockSwitchState.value = legacyScreenlockSwitchState.value
+            legacyScreenlockSwitchState.setNil(key: "screenlockSwitchState")
+        }
+
+        let legacyShowDisplayName = UserDefaultsValue<Bool>(key: "showDisplayName", default: false)
+        if legacyShowDisplayName.exists {
+            Storage.shared.showDisplayName.value = legacyShowDisplayName.value
+            legacyShowDisplayName.setNil(key: "showDisplayName")
+        }
+
+        let legacySpeakBG = UserDefaultsValue<Bool>(key: "speakBG", default: false)
+        if legacySpeakBG.exists {
+            Storage.shared.speakBG.value = legacySpeakBG.value
+            legacySpeakBG.setNil(key: "speakBG")
+        }
+
+        let legacySpeakBGAlways = UserDefaultsValue<Bool>(key: "speakBGAlways", default: true)
+        if legacySpeakBGAlways.exists {
+            Storage.shared.speakBGAlways.value = legacySpeakBGAlways.value
+            legacySpeakBGAlways.setNil(key: "speakBGAlways")
+        }
+
+        let legacySpeakLowBG = UserDefaultsValue<Bool>(key: "speakLowBG", default: false)
+        if legacySpeakLowBG.exists {
+            Storage.shared.speakLowBG.value = legacySpeakLowBG.value
+            legacySpeakLowBG.setNil(key: "speakLowBG")
+        }
+
+        let legacySpeakProactiveLowBG = UserDefaultsValue<Bool>(key: "speakProactiveLowBG", default: false)
+        if legacySpeakProactiveLowBG.exists {
+            Storage.shared.speakProactiveLowBG.value = legacySpeakProactiveLowBG.value
+            legacySpeakProactiveLowBG.setNil(key: "speakProactiveLowBG")
+        }
+
+        let legacySpeakFastDropDelta = UserDefaultsValue<Float>(key: "speakFastDropDelta", default: 10.0)
+        if legacySpeakFastDropDelta.exists {
+            Storage.shared.speakFastDropDelta.value = Double(legacySpeakFastDropDelta.value)
+            legacySpeakFastDropDelta.setNil(key: "speakFastDropDelta")
+        }
+
+        let legacySpeakLowBGLimit = UserDefaultsValue<Float>(key: "speakLowBGLimit", default: 72.0)
+        if legacySpeakLowBGLimit.exists {
+            Storage.shared.speakLowBGLimit.value = Double(legacySpeakLowBGLimit.value)
+            legacySpeakLowBGLimit.setNil(key: "speakLowBGLimit")
+        }
+
+        let legacySpeakHighBGLimit = UserDefaultsValue<Float>(key: "speakHighBGLimit", default: 180.0)
+        if legacySpeakHighBGLimit.exists {
+            Storage.shared.speakHighBGLimit.value = Double(legacySpeakHighBGLimit.value)
+            legacySpeakHighBGLimit.setNil(key: "speakHighBGLimit")
+        }
+
+        let legacySpeakHighBG = UserDefaultsValue<Bool>(key: "speakHighBG", default: false)
+        if legacySpeakHighBG.exists {
+            Storage.shared.speakHighBG.value = legacySpeakHighBG.value
+            legacySpeakHighBG.setNil(key: "speakHighBG")
+        }
+
+        let legacySpeakLanguage = UserDefaultsValue<String>(key: "speakLanguage", default: "en")
+        if legacySpeakLanguage.exists {
+            Storage.shared.speakLanguage.value = legacySpeakLanguage.value
+            legacySpeakLanguage.setNil(key: "speakLanguage")
+        }
+
+        // ── General (done earlier, but safe to repeat) ──
+        move(UserDefaultsValue<Bool>(key: "colorBGText", default: true), into: Storage.shared.colorBGText)
+        move(UserDefaultsValue<Bool>(key: "appBadge", default: true), into: appBadge)
+        move(UserDefaultsValue<Bool>(key: "forceDarkMode", default: false), into: forceDarkMode)
+        move(UserDefaultsValue<Bool>(key: "showStats", default: true), into: showStats)
+        move(UserDefaultsValue<Bool>(key: "useIFCC", default: false), into: useIFCC)
+        move(UserDefaultsValue<Bool>(key: "showSmallGraph", default: true), into: showSmallGraph)
+        move(UserDefaultsValue<Bool>(key: "screenlockSwitchState", default: false), into: screenlockSwitchState)
+        move(UserDefaultsValue<Bool>(key: "showDisplayName", default: false), into: showDisplayName)
+
+        // ── Speak-BG ──
+        move(UserDefaultsValue<Bool>(key: "speakBG", default: false), into: speakBG)
+        move(UserDefaultsValue<Bool>(key: "speakBGAlways", default: true), into: speakBGAlways)
+        move(UserDefaultsValue<Bool>(key: "speakLowBG", default: false), into: speakLowBG)
+        move(UserDefaultsValue<Bool>(key: "speakProactiveLowBG", default: false), into: speakProactiveLowBG)
+        move(UserDefaultsValue<Bool>(key: "speakHighBG", default: false), into: speakHighBG)
+        moveFloatToDouble(UserDefaultsValue<Float>(key: "speakLowBGLimit", default: 72.0), into: speakLowBGLimit)
+        moveFloatToDouble(UserDefaultsValue<Float>(key: "speakHighBGLimit", default: 180.0), into: speakHighBGLimit)
+        moveFloatToDouble(UserDefaultsValue<Float>(key: "speakFastDropDelta", default: 10.0), into: speakFastDropDelta)
+        move(UserDefaultsValue<String>(key: "speakLanguage", default: "en"), into: speakLanguage)
+
+        // ── Graph ──
+        move(UserDefaultsValue<Bool>(key: "showDots", default: true), into: showDots)
+        move(UserDefaultsValue<Bool>(key: "showLines", default: true), into: showLines)
+        move(UserDefaultsValue<Bool>(key: "showValues", default: true), into: showValues)
+        move(UserDefaultsValue<Bool>(key: "showAbsorption", default: true), into: showAbsorption)
+        move(UserDefaultsValue<Bool>(key: "showDIAMarkers", default: true), into: showDIALines)
+        move(UserDefaultsValue<Bool>(key: "show30MinLine", default: false), into: show30MinLine)
+        move(UserDefaultsValue<Bool>(key: "show90MinLine", default: false), into: show90MinLine)
+        move(UserDefaultsValue<Bool>(key: "showMidnightMarkers", default: false), into: showMidnightLines)
+        move(UserDefaultsValue<Bool>(key: "smallGraphTreatments", default: true), into: smallGraphTreatments)
+
+        move(UserDefaultsValue<Int>(key: "smallGraphHeight", default: 40), into: smallGraphHeight)
+        move(UserDefaultsValue<Double>(key: "predictionToLoad", default: 1.0), into: predictionToLoad)
+        move(UserDefaultsValue<Double>(key: "minBasalScale", default: 5.0), into: minBasalScale)
+        moveFloatToDouble(UserDefaultsValue<Float>(key: "minBGScale", default: 250.0), into: minBGScale)
+        moveFloatToDouble(UserDefaultsValue<Float>(key: "lowLine", default: 70.0), into: lowLine)
+        moveFloatToDouble(UserDefaultsValue<Float>(key: "highLine", default: 180.0), into: highLine)
+        move(UserDefaultsValue<Int>(key: "downloadDays", default: 1), into: downloadDays)
+        // Remove this in a year later than the release of the new Alarms [END]
+    }
+}

+ 20 - 1
LoopFollow/Storage/Storage.swift

@@ -78,9 +78,28 @@ class Storage {
     var speakHighBGLimit = StorageValue<Double>(key: "speakHighBGLimit", defaultValue: 180.0)
     var speakHighBG = StorageValue<Bool>(key: "speakHighBG", defaultValue: false)
     var speakLanguage = StorageValue<String>(key: "speakLanguage", defaultValue: "en")
-
     // General Settings [END]
 
+    // Graph Settings [BEGIN]
+    var showDots = StorageValue<Bool>(key: "showDots", defaultValue: true)
+    var showLines = StorageValue<Bool>(key: "showLines", defaultValue: true)
+    var showValues = StorageValue<Bool>(key: "showValues", defaultValue: true)
+    var showAbsorption = StorageValue<Bool>(key: "showAbsorption", defaultValue: true)
+    var showDIALines = StorageValue<Bool>(key: "showDIAMarkers", defaultValue: true)
+    var show30MinLine = StorageValue<Bool>(key: "show30MinLine", defaultValue: false)
+    var show90MinLine = StorageValue<Bool>(key: "show90MinLine", defaultValue: false)
+    var showMidnightLines = StorageValue<Bool>(key: "showMidnightMarkers", defaultValue: false)
+    var smallGraphTreatments = StorageValue<Bool>(key: "smallGraphTreatments", defaultValue: true)
+
+    var smallGraphHeight = StorageValue<Int>(key: "smallGraphHeight", defaultValue: 40)
+    var predictionToLoad = StorageValue<Double>(key: "predictionToLoad", defaultValue: 1.0)
+    var minBasalScale = StorageValue<Double>(key: "minBasalScale", defaultValue: 5.0)
+    var minBGScale = StorageValue<Double>(key: "minBGScale", defaultValue: 250.0)
+    var lowLine = StorageValue<Double>(key: "lowLine", defaultValue: 70.0)
+    var highLine = StorageValue<Double>(key: "highLine", defaultValue: 180.0)
+    var downloadDays = StorageValue<Int>(key: "downloadDays", defaultValue: 1)
+    // Graph Settings [END]
+
     static let shared = Storage()
     private init() {}
 }

+ 0 - 19
LoopFollow/Storage/UserDefaults.swift

@@ -81,30 +81,12 @@ class UserDefaultsRepository {
 
     // Graph Settings
     static let chartScaleX = UserDefaultsValue<Float>(key: "chartScaleX", default: 18.0)
-    static let showDots = UserDefaultsValue<Bool>(key: "showDots", default: true)
-    static let smallGraphTreatments = UserDefaultsValue<Bool>(key: "smallGraphTreatments", default: true)
-    static let showValues = UserDefaultsValue<Bool>(key: "showValues", default: true)
-    static let showAbsorption = UserDefaultsValue<Bool>(key: "showAbsorption", default: true)
-    static let showLines = UserDefaultsValue<Bool>(key: "showLines", default: true)
     static let hoursToLoad = UserDefaultsValue<Int>(key: "hoursToLoad", default: 24)
-    static let predictionToLoad = UserDefaultsValue<Double>(key: "predictionToLoad", default: 1)
-    static let minBasalScale = UserDefaultsValue<Double>(key: "minBasalScale", default: 5.0)
-    static let minBGScale = UserDefaultsValue<Float>(key: "minBGScale", default: 250.0)
-    static let showDIALines = UserDefaultsValue<Bool>(key: "showDIAMarkers", default: true)
-    static let show30MinLine = UserDefaultsValue<Bool>(key: "show30MinLine", default: false)
-    static let show90MinLine = UserDefaultsValue<Bool>(key: "show90MinLine", default: false)
-    static let showMidnightLines = UserDefaultsValue<Bool>(key: "showMidnightMarkers", default: false)
-    static let lowLine = UserDefaultsValue<Float>(key: "lowLine", default: 70.0)
-    static let highLine = UserDefaultsValue<Float>(key: "highLine", default: 180.0)
-    static let smallGraphHeight = UserDefaultsValue<Int>(key: "smallGraphHeight", default: 40)
 
     // Deprecated, used to detect if backgroundRefresh was set to off. TODO: Remove in the beginning of 2026
     static let backgroundRefresh = UserDefaultsValue<Bool>(key: "backgroundRefresh", default: true)
 
-    static let dimScreenWhenIdle = UserDefaultsValue<Int>(key: "dimScreenWhenIdle", default: 0)
-
     // Advanced Settings
-    // static let onlyDownloadBG = UserDefaultsValue<Bool>(key: "onlyDownloadBG", default: false)
     static let downloadTreatments = UserDefaultsValue<Bool>(key: "downloadTreatments", default: true)
     static let downloadPrediction = UserDefaultsValue<Bool>(key: "downloadPrediction", default: true)
     static let graphOtherTreatments = UserDefaultsValue<Bool>(key: "graphOtherTreatments", default: true)
@@ -112,7 +94,6 @@ class UserDefaultsRepository {
     static let graphBolus = UserDefaultsValue<Bool>(key: "graphBolus", default: true)
     static let graphCarbs = UserDefaultsValue<Bool>(key: "graphCarbs", default: true)
     static let bgUpdateDelay = UserDefaultsValue<Int>(key: "bgUpdateDelay", default: 10)
-    static let downloadDays = UserDefaultsValue<Int>(key: "downloadDays", default: 1)
 
     // Watch Calendar Settings
     static let calendarIdentifier = UserDefaultsValue<String>(key: "calendarIdentifier", default: "")

+ 0 - 286
LoopFollow/ViewControllers/GraphSettingsViewController.swift

@@ -1,286 +0,0 @@
-// LoopFollow
-// GraphSettingsViewController.swift
-// Created by Jose Paredes on 2020-07-17.
-
-import Eureka
-import EventKit
-import EventKitUI
-import Foundation
-
-class GraphSettingsViewController: FormViewController {
-    var appStateController: AppStateController?
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        if Storage.shared.forceDarkMode.value {
-            overrideUserInterfaceStyle = .dark
-        }
-
-        buildGraphSettings()
-
-        showHideNSDetails()
-    }
-
-    func showHideNSDetails() {
-        var isHidden = false
-        var isEnabled = true
-        if !IsNightscoutEnabled() {
-            isHidden = true
-            isEnabled = false
-        }
-
-        if let row1 = form.rowBy(tag: "predictionToLoad") as? StepperRow {
-            row1.hidden = .function(["hide"]) { _ in
-                isHidden
-            }
-            row1.evaluateHidden()
-        }
-        if let row2 = form.rowBy(tag: "smallGraphTreatments") as? SwitchRow {
-            row2.hidden = .function(["hide"]) { _ in
-                isHidden
-            }
-            row2.evaluateHidden()
-        }
-        if let row3 = form.rowBy(tag: "minBasalScale") as? StepperRow {
-            row3.hidden = .function(["hide"]) { _ in
-                isHidden
-            }
-            row3.evaluateHidden()
-        }
-
-        if let row4 = form.rowBy(tag: "showValues") as? SwitchRow {
-            row4.hidden = .function(["hide"]) { _ in
-                isHidden
-            }
-            row4.evaluateHidden()
-        }
-        if let row5 = form.rowBy(tag: "showAbsorption") as? SwitchRow {
-            row5.hidden = .function(["hide"]) { _ in
-                isHidden
-            }
-            row5.evaluateHidden()
-        }
-    }
-
-    private func buildGraphSettings() {
-        form
-            +++ Section("Graph Settings")
-
-            <<< SwitchRow("switchRowDots") { row in
-                row.title = "Display Dots"
-                row.value = UserDefaultsRepository.showDots.value
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.showDots.value = value
-                // Force main screen update
-                // guard let mainScreen = self?.tabBarController!.viewControllers?[0] as? MainViewController else { return }
-                // mainScreen.updateBGGraphSettings()
-
-                // tell main screen that grap needs updating
-                if let appState = self!.appStateController {
-                    appState.chartSettingsChanged = true
-                }
-            }
-            <<< SwitchRow("switchRowLines") { row in
-                row.title = "Display Lines"
-                row.value = UserDefaultsRepository.showLines.value
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.showLines.value = value
-                // Force main screen update
-                // guard let mainScreen = self?.tabBarController!.viewControllers?[0] as? MainViewController else { return }
-                // mainScreen.updateBGGraphSettings()
-
-                if let appState = self!.appStateController {
-                    appState.chartSettingsChanged = true
-                }
-            }
-            <<< SwitchRow("showValues") { row in
-                row.title = "Show Carb/Bolus Values"
-                row.value = UserDefaultsRepository.showValues.value
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.showValues.value = value
-            }
-            <<< SwitchRow("showAbsorption") { row in
-                row.title = "Show Carb Absorption"
-                row.value = UserDefaultsRepository.showAbsorption.value
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.showAbsorption.value = value
-            }
-            <<< SwitchRow("showDIAMarkers") { row in
-                row.title = "Show DIA Lines"
-                row.value = UserDefaultsRepository.showDIALines.value
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.showDIALines.value = value
-
-                // tell main screen that graph needs updating
-                if let appState = self!.appStateController {
-                    appState.chartSettingsChanged = true
-                }
-            }
-            <<< SwitchRow("show30MinLine") { row in
-                row.title = "Show -30 min line"
-                row.value = UserDefaultsRepository.show30MinLine.value
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.show30MinLine.value = value
-
-                // Tell the main screen that graph needs updating
-                if let appState = self!.appStateController {
-                    appState.chartSettingsChanged = true
-                }
-            }
-            <<< SwitchRow("show90MinLine") { row in
-                row.title = "Show -90 min line"
-                row.value = UserDefaultsRepository.show90MinLine.value
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.show90MinLine.value = value
-
-                // Tell the main screen that graph needs updating
-                if let appState = self!.appStateController {
-                    appState.chartSettingsChanged = true
-                }
-            }
-            <<< SwitchRow("smallGraphTreatments") { row in
-                row.title = "Treatments on Small Graph"
-                row.value = UserDefaultsRepository.smallGraphTreatments.value
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.smallGraphTreatments.value = value
-            }
-            <<< StepperRow("smallGraphHeight") { row in
-                row.title = "Small Graph Height"
-                row.cell.stepper.stepValue = 5
-                row.cell.stepper.minimumValue = 40
-                row.cell.stepper.maximumValue = 80
-                row.value = Double(UserDefaultsRepository.smallGraphHeight.value)
-                row.displayValueFor = { value in
-                    guard let value = value else { return nil }
-                    return "\(Int(value))"
-                }
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.smallGraphHeight.value = Int(value)
-
-                if let appState = self!.appStateController {
-                    appState.chartSettingsChanged = true
-                }
-            }
-            <<< StepperRow("predictionToLoad") { row in
-                row.title = "Hours of Prediction"
-                row.cell.stepper.stepValue = 0.25
-                row.cell.stepper.minimumValue = 0.0
-                row.cell.stepper.maximumValue = 6.0
-                row.value = Double(UserDefaultsRepository.predictionToLoad.value)
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.predictionToLoad.value = value
-            }
-            <<< StepperRow("minBGScale") { row in
-                row.title = "Min BG Scale"
-                row.cell.stepper.stepValue = 1
-                row.cell.stepper.minimumValue = Double(UserDefaultsRepository.highLine.value)
-                row.cell.stepper.maximumValue = 400
-                row.value = Double(UserDefaultsRepository.minBGScale.value)
-                row.displayValueFor = { value in
-                    guard let value = value else { return nil }
-                    return Localizer.toDisplayUnits(String(value))
-                }
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.minBGScale.value = Float(value)
-            }
-
-            <<< StepperRow("minBasalScale") { row in
-                row.title = "Min Basal Scale"
-                row.cell.stepper.stepValue = 0.5
-                row.cell.stepper.minimumValue = 0.5
-                row.cell.stepper.maximumValue = 20
-                row.value = Double(UserDefaultsRepository.minBasalScale.value)
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.minBasalScale.value = value
-            }
-            <<< StepperRow("lowLine") { row in
-                row.title = "Low BG Display Value"
-                row.cell.stepper.stepValue = 1
-                row.cell.stepper.minimumValue = 40
-                row.cell.stepper.maximumValue = 120
-                row.value = Double(UserDefaultsRepository.lowLine.value)
-                row.displayValueFor = { value in
-                    guard let value = value else { return nil }
-                    return Localizer.toDisplayUnits(String(value))
-                }
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.lowLine.value = Float(value)
-                // Force main screen update
-                // guard let mainScreen = self?.tabBarController!.viewControllers?[0] as? MainViewController else { return }
-                // mainScreen.updateBGGraphSettings()
-
-                // tell main screen to update
-                if let appState = self!.appStateController {
-                    appState.chartSettingsChanged = true
-                }
-            }
-            <<< StepperRow("highLine") { row in
-                row.title = "High BG Display Value"
-                row.cell.stepper.stepValue = 1
-                row.cell.stepper.minimumValue = 120
-                row.cell.stepper.maximumValue = 400
-                row.value = Double(UserDefaultsRepository.highLine.value)
-                row.displayValueFor = { value in
-                    guard let value = value else { return nil }
-                    return Localizer.toDisplayUnits(String(value))
-                }
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.highLine.value = Float(value)
-                // Force main screen update
-                // guard let mainScreen = self?.tabBarController!.viewControllers?[0] as? MainViewController else { return }
-                // mainScreen.updateBGGraphSettings()
-
-                // let app state know of the change
-                if let appState = self!.appStateController {
-                    appState.chartSettingsChanged = true
-                }
-            }
-            <<< StepperRow("downloadDays") { row in
-                // NS supports up to 4 days
-                row.title = "Show Days Back"
-                row.cell.stepper.stepValue = 1
-                row.cell.stepper.minimumValue = 1
-                row.cell.stepper.maximumValue = 4
-                row.value = Double(UserDefaultsRepository.downloadDays.value)
-                row.displayValueFor = { value in
-                    guard let value = value else { return nil }
-                    return "\(Int(value))"
-                }
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.downloadDays.value = Int(value)
-            }
-            <<< SwitchRow("showMidnightMarkers") { row in
-                row.title = "Show Midnight Lines"
-                row.value = UserDefaultsRepository.showMidnightLines.value
-            }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.showMidnightLines.value = value
-
-                // tell main screen that graph needs updating
-                if let appState = self!.appStateController {
-                    appState.chartSettingsChanged = true
-                }
-            }
-
-            +++ ButtonRow {
-                $0.title = "DONE"
-            }.onCellSelection { _, _ in
-                self.dismiss(animated: true, completion: nil)
-            }
-    }
-}

+ 12 - 128
LoopFollow/ViewControllers/MainViewController.swift

@@ -43,8 +43,6 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
 
     let speechSynthesizer = AVSpeechSynthesizer()
 
-    var appStateController: AppStateController?
-
     // Variables for BG Charts
     var firstGraphLoad: Bool = true
     var currentOverride = 1.0
@@ -89,8 +87,8 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     var lastOverrideStartTime: TimeInterval = 0
     var lastOverrideEndTime: TimeInterval = 0
 
-    var topBG: Float = UserDefaultsRepository.minBGScale.value
-    var topPredictionBG: Float = UserDefaultsRepository.minBGScale.value
+    var topBG: Double = Storage.shared.minBGScale.value
+    var topPredictionBG: Double = Storage.shared.minBGScale.value
 
     var lastOverrideAlarm: TimeInterval = 0
 
@@ -125,116 +123,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
             Storage.shared.remoteType.value = .none
         }
 
-        // Migration of UserDefaultsRepository -> Storage handling
-        if !UserDefaultsRepository.backgroundRefresh.value {
-            Storage.shared.backgroundRefreshType.value = .none
-            UserDefaultsRepository.backgroundRefresh.value = true
-        }
-
-        // Remove this in a year later than the release of the new Alarms [BEGIN]
-        let legacyColorBGText = UserDefaultsValue<Bool>(key: "colorBGText", default: true)
-        if legacyColorBGText.exists {
-            Storage.shared.colorBGText.value = legacyColorBGText.value
-            legacyColorBGText.setNil(key: "colorBGText")
-        }
-
-        let legacyAppBadge = UserDefaultsValue<Bool>(key: "appBadge", default: true)
-        if legacyAppBadge.exists {
-            Storage.shared.appBadge.value = legacyAppBadge.value
-            legacyAppBadge.setNil(key: "appBadge")
-        }
-
-        let legacyForceDarkMode = UserDefaultsValue<Bool>(key: "forceDarkMode", default: true)
-        if legacyForceDarkMode.exists {
-            Storage.shared.forceDarkMode.value = legacyForceDarkMode.value
-            legacyForceDarkMode.setNil(key: "forceDarkMode")
-        }
-
-        let legacyShowStats = UserDefaultsValue<Bool>(key: "showStats", default: true)
-        if legacyShowStats.exists {
-            Storage.shared.showStats.value = legacyShowStats.value
-            legacyShowStats.setNil(key: "showStats")
-        }
-
-        let legacyUseIFCC = UserDefaultsValue<Bool>(key: "useIFCC", default: false)
-        if legacyUseIFCC.exists {
-            Storage.shared.useIFCC.value = legacyUseIFCC.value
-            legacyUseIFCC.setNil(key: "useIFCC")
-        }
-
-        let legacyShowSmallGraph = UserDefaultsValue<Bool>(key: "showSmallGraph", default: true)
-        if legacyShowSmallGraph.exists {
-            Storage.shared.showSmallGraph.value = legacyShowSmallGraph.value
-            legacyShowSmallGraph.setNil(key: "showSmallGraph")
-        }
-
-        let legacyScreenlockSwitchState = UserDefaultsValue<Bool>(key: "screenlockSwitchState", default: true)
-        if legacyScreenlockSwitchState.exists {
-            Storage.shared.screenlockSwitchState.value = legacyScreenlockSwitchState.value
-            legacyScreenlockSwitchState.setNil(key: "screenlockSwitchState")
-        }
-
-        let legacyShowDisplayName = UserDefaultsValue<Bool>(key: "showDisplayName", default: false)
-        if legacyShowDisplayName.exists {
-            Storage.shared.showDisplayName.value = legacyShowDisplayName.value
-            legacyShowDisplayName.setNil(key: "showDisplayName")
-        }
-
-        let legacySpeakBG = UserDefaultsValue<Bool>(key: "speakBG", default: false)
-        if legacySpeakBG.exists {
-            Storage.shared.speakBG.value = legacySpeakBG.value
-            legacySpeakBG.setNil(key: "speakBG")
-        }
-
-        let legacySpeakBGAlways = UserDefaultsValue<Bool>(key: "speakBGAlways", default: true)
-        if legacySpeakBGAlways.exists {
-            Storage.shared.speakBGAlways.value = legacySpeakBGAlways.value
-            legacySpeakBGAlways.setNil(key: "speakBGAlways")
-        }
-
-        let legacySpeakLowBG = UserDefaultsValue<Bool>(key: "speakLowBG", default: false)
-        if legacySpeakLowBG.exists {
-            Storage.shared.speakLowBG.value = legacySpeakLowBG.value
-            legacySpeakLowBG.setNil(key: "speakLowBG")
-        }
-
-        let legacySpeakProactiveLowBG = UserDefaultsValue<Bool>(key: "speakProactiveLowBG", default: false)
-        if legacySpeakProactiveLowBG.exists {
-            Storage.shared.speakProactiveLowBG.value = legacySpeakProactiveLowBG.value
-            legacySpeakProactiveLowBG.setNil(key: "speakProactiveLowBG")
-        }
-
-        let legacySpeakFastDropDelta = UserDefaultsValue<Float>(key: "speakFastDropDelta", default: 10.0)
-        if legacySpeakFastDropDelta.exists {
-            Storage.shared.speakFastDropDelta.value = Double(legacySpeakFastDropDelta.value)
-            legacySpeakFastDropDelta.setNil(key: "speakFastDropDelta")
-        }
-
-        let legacySpeakLowBGLimit = UserDefaultsValue<Float>(key: "speakLowBGLimit", default: 72.0)
-        if legacySpeakLowBGLimit.exists {
-            Storage.shared.speakLowBGLimit.value = Double(legacySpeakLowBGLimit.value)
-            legacySpeakLowBGLimit.setNil(key: "speakLowBGLimit")
-        }
-
-        let legacySpeakHighBGLimit = UserDefaultsValue<Float>(key: "speakHighBGLimit", default: 180.0)
-        if legacySpeakHighBGLimit.exists {
-            Storage.shared.speakHighBGLimit.value = Double(legacySpeakHighBGLimit.value)
-            legacySpeakHighBGLimit.setNil(key: "speakHighBGLimit")
-        }
-
-        let legacySpeakHighBG = UserDefaultsValue<Bool>(key: "speakHighBG", default: false)
-        if legacySpeakHighBG.exists {
-            Storage.shared.speakHighBG.value = legacySpeakHighBG.value
-            legacySpeakHighBG.setNil(key: "speakHighBG")
-        }
-
-        let legacySpeakLanguage = UserDefaultsValue<String>(key: "speakLanguage", default: "en")
-        if legacySpeakLanguage.exists {
-            Storage.shared.speakLanguage.value = legacySpeakLanguage.value
-            legacySpeakLanguage.setNil(key: "speakLanguage")
-        }
-
-        // Remove this in a year later than the release of the new Alarms [END]
+        Storage.shared.migrate()
 
         // Ensure alertNotLooping has a minimum value of 16.
         if UserDefaultsRepository.alertNotLooping.value < 16 {
@@ -252,7 +141,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
 
         infoManager = InfoManager(tableView: infoTable)
 
-        smallGraphHeightConstraint.constant = CGFloat(UserDefaultsRepository.smallGraphHeight.value)
+        smallGraphHeightConstraint.constant = CGFloat(Storage.shared.smallGraphHeight.value)
         view.layoutIfNeeded()
 
         let shareUserName = UserDefaultsRepository.shareUserName.value
@@ -461,20 +350,15 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     }
 
     override func viewWillAppear(_: Bool) {
-        // set screen lock
         UIApplication.shared.isIdleTimerDisabled = Storage.shared.screenlockSwitchState.value
 
-        // check the app state
-        if let appState = appStateController {
-            if appState.chartSettingsChanged {
-                updateBGGraphSettings()
+        if Observable.shared.chartSettingsChanged.value {
+            updateBGGraphSettings()
 
-                smallGraphHeightConstraint.constant = CGFloat(UserDefaultsRepository.smallGraphHeight.value)
-                view.layoutIfNeeded()
+            smallGraphHeightConstraint.constant = CGFloat(Storage.shared.smallGraphHeight.value)
+            view.layoutIfNeeded()
 
-                // reset the app state
-                appState.chartSettingsChanged = false
-            }
+            Observable.shared.chartSettingsChanged.value = false
         }
     }
 
@@ -640,10 +524,10 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
             let latestBG = bgData[bgData.count - 1].sgv
             var color = NSUIColor.label
             if Storage.shared.colorBGText.value {
-                if Float(latestBG) >= UserDefaultsRepository.highLine.value {
+                if Double(latestBG) >= Storage.shared.highLine.value {
                     color = NSUIColor.systemYellow
                     Observable.shared.bgTextColor.value = .yellow
-                } else if Float(latestBG) <= UserDefaultsRepository.lowLine.value {
+                } else if Double(latestBG) <= Storage.shared.lowLine.value {
                     color = NSUIColor.systemRed
                     Observable.shared.bgTextColor.value = .red
                 } else {
@@ -796,7 +680,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     }
 
     func calculateMaxBgGraphValue() -> Float {
-        return max(topBG, topPredictionBG)
+        return max(Float(topBG), Float(topPredictionBG))
     }
 
     func loadDebugData() {

+ 0 - 2
LoopFollow/ViewControllers/NightScoutViewController.swift

@@ -8,8 +8,6 @@ import WebKit
 class NightscoutViewController: UIViewController {
     @IBOutlet var webView: WKWebView!
 
-    var appStateController: AppStateController?
-
     override func viewDidLoad() {
         super.viewDidLoad()
         if Storage.shared.forceDarkMode.value {

+ 16 - 7
LoopFollow/ViewControllers/SettingsViewController.swift

@@ -10,7 +10,6 @@ import UIKit
 
 class SettingsViewController: FormViewController, NightscoutSettingsViewModelDelegate {
     var tokenRow: TextRow?
-    var appStateController: AppStateController?
     var statusLabelRow: LabelRow!
 
     func showHideNSDetails() {
@@ -108,11 +107,10 @@ class SettingsViewController: FormViewController, NightscoutSettingsViewModelDel
                 $0.title = "Graph Settings"
                 $0.presentationMode = .show(
                     controllerProvider: .callback(builder: {
-                        let controller = GraphSettingsViewController()
-                        controller.appStateController = self.appStateController
-                        return controller
-                    }
-                    ), onDismiss: nil
+                        self.presentGraphSettings()
+                        return UIViewController()
+                    }),
+                    onDismiss: nil
                 )
             }
             <<< ButtonRow("informationDisplaySettings") {
@@ -165,7 +163,6 @@ class SettingsViewController: FormViewController, NightscoutSettingsViewModelDel
                 $0.presentationMode = .show(
                     controllerProvider: .callback(builder: {
                         let controller = WatchSettingsViewController()
-                        controller.appStateController = self.appStateController
                         return controller
                     }
                     ), onDismiss: nil
@@ -421,6 +418,18 @@ class SettingsViewController: FormViewController, NightscoutSettingsViewModelDel
         present(hostingController, animated: true)
     }
 
+    func presentGraphSettings() {
+        let view = GraphSettingsView()
+        let host = UIHostingController(rootView: view)
+        host.modalPresentationStyle = .formSheet
+
+        if Storage.shared.forceDarkMode.value {
+            host.overrideUserInterfaceStyle = .dark
+        }
+
+        present(host, animated: true)
+    }
+
     private func shareLogs() {
         let logFilesToShare = LogManager.shared.logFilesForTodayAndYesterday()
 

+ 0 - 2
LoopFollow/ViewControllers/WatchSettingsViewController.swift

@@ -8,8 +8,6 @@ import EventKitUI
 import Foundation
 
 class WatchSettingsViewController: FormViewController {
-    var appStateController: AppStateController?
-
     override func viewDidLoad() {
         super.viewDidLoad()
         if Storage.shared.forceDarkMode.value {