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

Reapply "Merge branch 'settings-update' of https://github.com/tmhastings/Trio-dev into settings-update"

This reverts commit 95151d0cf00306e0b94df1bd1105ebe717abf864.
tmhastings 1 год назад
Родитель
Сommit
bf40efccd0
100 измененных файлов с 5589 добавлено и 934 удалено
  1. 2 2
      Config.xcconfig
  2. 211 98
      FreeAPS.xcodeproj/project.pbxproj
  3. 38 0
      FreeAPS/Resources/Assets.xcassets/Colors/ApnBackground.colorset/Contents.json
  4. 20 0
      FreeAPS/Resources/Assets.xcassets/Colors/ApnBackgroundLightDark.colorset/Contents.json
  5. 2 2
      FreeAPS/Resources/Info.plist
  6. 1 1
      FreeAPS/Resources/javascript/bundle/autosens.js
  7. 1 1
      FreeAPS/Resources/javascript/bundle/autotune-prep.js
  8. 1 1
      FreeAPS/Resources/javascript/bundle/determine-basal.js
  9. 1 1
      FreeAPS/Resources/javascript/bundle/iob.js
  10. 1 1
      FreeAPS/Resources/javascript/bundle/meal.js
  11. 3 3
      FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json
  12. 1 1
      FreeAPS/Resources/json/defaults/preferences.json
  13. 1 1
      FreeAPS/Resources/json/defaults/settings/settings.json
  14. 18 19
      FreeAPS/Sources/APS/APSManager.swift
  15. 0 10
      FreeAPS/Sources/APS/DeviceDataManager.swift
  16. 1 1
      FreeAPS/Sources/APS/FetchGlucoseManager.swift
  17. 36 3
      FreeAPS/Sources/APS/FetchTreatmentsManager.swift
  18. 112 192
      FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift
  19. 6 5
      FreeAPS/Sources/APS/Storage/CarbsStorage.swift
  20. 29 3
      FreeAPS/Sources/APS/Storage/GlucoseStorage.swift
  21. 15 22
      FreeAPS/Sources/APS/Storage/OverrideStorage.swift
  22. 1 1
      FreeAPS/Sources/APS/Storage/PumpHistoryStorage.swift
  23. 266 50
      FreeAPS/Sources/APS/Storage/TempTargetsStorage.swift
  24. 0 1
      FreeAPS/Sources/Application/AppDelegate.swift
  25. 20 0
      FreeAPS/Sources/Application/AppState.swift
  26. 22 3
      FreeAPS/Sources/Application/FreeAPSApp.swift
  27. 1 1
      FreeAPS/Sources/Assemblies/ServiceAssembly.swift
  28. 4 0
      FreeAPS/Sources/Helpers/ConstantValues.swift
  29. 6 0
      FreeAPS/Sources/Helpers/Decimal+Extensions.swift
  30. 51 0
      FreeAPS/Sources/Helpers/Formatters.swift
  31. 138 28
      FreeAPS/Sources/Helpers/MainChartHelper.swift
  32. 1 1
      FreeAPS/Sources/Helpers/ProgressBar.swift
  33. 2 2
      FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings
  34. 1 1
      FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings
  35. 2 2
      FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings
  36. 2 2
      FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings
  37. 2 2
      FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings
  38. 2 2
      FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings
  39. 2 2
      FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings
  40. 2 2
      FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings
  41. 2 2
      FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings
  42. 2 2
      FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings
  43. 2 2
      FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings
  44. 2 2
      FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings
  45. 2 2
      FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings
  46. 2 2
      FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings
  47. 2 2
      FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings
  48. 2 2
      FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings
  49. 2 2
      FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings
  50. 2 2
      FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings
  51. 2 2
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  52. 2 2
      FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings
  53. 2 2
      FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings
  54. 2 2
      FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings
  55. 2 2
      FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings
  56. 1 1
      FreeAPS/Sources/Models/AlertEntry.swift
  57. 2 0
      FreeAPS/Sources/Models/Battery.swift
  58. 2 2
      FreeAPS/Sources/Models/CarbsEntry.swift
  59. 9 9
      FreeAPS/Sources/Models/DecimalPickerSettings.swift
  60. 27 7
      FreeAPS/Sources/Models/FreeAPSSettings.swift
  61. 16 0
      FreeAPS/Sources/Models/HbA1cDisplayUnit.swift
  62. 1 0
      FreeAPS/Sources/Models/NightscoutStatus.swift
  63. 12 12
      FreeAPS/Sources/Models/Oref2_variables.swift
  64. 1 1
      FreeAPS/Sources/Models/Override.swift
  65. 219 2
      FreeAPS/Sources/Models/Preferences.swift
  66. 13 4
      FreeAPS/Sources/Models/TempTarget.swift
  67. 16 0
      FreeAPS/Sources/Models/TimeInRangeChartStyle.swift
  68. 2 2
      FreeAPS/Sources/Models/TotalInsulinDisplayType.swift
  69. 2 2
      FreeAPS/Sources/Modules/OverrideConfig/OverrideDataFlow.swift
  70. 9 0
      FreeAPS/Sources/Modules/Adjustments/AdjustmentsProvider.swift
  71. 93 0
      FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+Helpers.swift
  72. 338 0
      FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+Overrides.swift
  73. 422 0
      FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+TempTargets.swift
  74. 272 0
      FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel.swift
  75. 707 0
      FreeAPS/Sources/Modules/Adjustments/View/AdjustmentsRootView.swift
  76. 474 0
      FreeAPS/Sources/Modules/Adjustments/View/Overrides/AddOverrideForm.swift
  77. 635 0
      FreeAPS/Sources/Modules/Adjustments/View/Overrides/EditOverrideForm.swift
  78. 388 0
      FreeAPS/Sources/Modules/Adjustments/View/TempTargets/AddTempTargetForm.swift
  79. 410 0
      FreeAPS/Sources/Modules/Adjustments/View/TempTargets/EditTempTargetForm.swift
  80. 19 0
      FreeAPS/Sources/Modules/Adjustments/View/ViewElements/RadioButton.swift
  81. 67 0
      FreeAPS/Sources/Modules/Adjustments/View/ViewElements/TargetPicker.swift
  82. 10 1
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsProvider.swift
  83. 34 69
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift
  84. 6 21
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift
  85. 9 32
      FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift
  86. 6 24
      FreeAPS/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift
  87. 2 17
      FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift
  88. 34 9
      FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift
  89. 112 60
      FreeAPS/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift
  90. 15 0
      FreeAPS/Sources/Modules/Base/BaseStateModel.swift
  91. 14 27
      FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift
  92. 6 21
      FreeAPS/Sources/Modules/CGM/View/CGMRootView.swift
  93. 3 18
      FreeAPS/Sources/Modules/CalendarEventSettings/View/CalendarEventSettingsRootView.swift
  94. 2 17
      FreeAPS/Sources/Modules/Calibrations/View/CalibrationsRootView.swift
  95. 1 1
      FreeAPS/Sources/Modules/CarbRatioEditor/CarbRatioEditorStateModel.swift
  96. 117 60
      FreeAPS/Sources/Modules/CarbRatioEditor/View/CarbRatioEditorRootView.swift
  97. 2 17
      FreeAPS/Sources/Modules/ConfigEditor/View/ConfigEditorRootView.swift
  98. 3 0
      FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift
  99. 1 1
      FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift
  100. 0 0
      FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift

+ 2 - 2
Config.xcconfig

@@ -7,6 +7,6 @@ BUNDLE_IDENTIFIER = org.nightscout.$(DEVELOPMENT_TEAM).trio
 APP_ICON = trioBlack
 APP_ICON = trioBlack
 APP_URL_SCHEME = Trio
 APP_URL_SCHEME = Trio
 
 
-#include? "../../ConfigOverride.xcconfig"
+// Optional overrides
+#include? "../ConfigOverride.xcconfig"
 #include? "ConfigOverride.xcconfig"
 #include? "ConfigOverride.xcconfig"
-#include? "../../ConfigOverride.xcconfig"

+ 211 - 98
FreeAPS.xcodeproj/project.pbxproj

@@ -7,7 +7,7 @@
 	objects = {
 	objects = {
 
 
 /* Begin PBXBuildFile section */
 /* Begin PBXBuildFile section */
-		041D1E995A6AE92E9289DC49 /* BolusDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */; };
+		041D1E995A6AE92E9289DC49 /* TreatmentsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D1A7CA8C10C4403D4BBFA7 /* TreatmentsDataFlow.swift */; };
 		0437CE46C12535A56504EC19 /* SnoozeRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5822B15939E719628E9FF7C /* SnoozeRootView.swift */; };
 		0437CE46C12535A56504EC19 /* SnoozeRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5822B15939E719628E9FF7C /* SnoozeRootView.swift */; };
 		0D9A5E34A899219C5C4CDFAF /* DataTableStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9455FA2D92E77A6C4AFED8A3 /* DataTableStateModel.swift */; };
 		0D9A5E34A899219C5C4CDFAF /* DataTableStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9455FA2D92E77A6C4AFED8A3 /* DataTableStateModel.swift */; };
 		0F7A65FBD2CD8D6477ED4539 /* GlucoseNotificationSettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E625985B47742D498CB1681A /* GlucoseNotificationSettingsProvider.swift */; };
 		0F7A65FBD2CD8D6477ED4539 /* GlucoseNotificationSettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E625985B47742D498CB1681A /* GlucoseNotificationSettingsProvider.swift */; };
@@ -19,7 +19,6 @@
 		110AEDEE2C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE92C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift */; };
 		110AEDEE2C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE92C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift */; };
 		118DF76A2C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7642C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift */; };
 		118DF76A2C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7642C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift */; };
 		118DF76B2C5ECBC60067FEB7 /* CancelOverrideIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7652C5ECBC60067FEB7 /* CancelOverrideIntent.swift */; };
 		118DF76B2C5ECBC60067FEB7 /* CancelOverrideIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7652C5ECBC60067FEB7 /* CancelOverrideIntent.swift */; };
-		118DF76C2C5ECBC60067FEB7 /* ListOverridePresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7662C5ECBC60067FEB7 /* ListOverridePresetIntent.swift */; };
 		118DF76D2C5ECBC60067FEB7 /* OverridePresetEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7672C5ECBC60067FEB7 /* OverridePresetEntity.swift */; };
 		118DF76D2C5ECBC60067FEB7 /* OverridePresetEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7672C5ECBC60067FEB7 /* OverridePresetEntity.swift */; };
 		118DF76E2C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7682C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift */; };
 		118DF76E2C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118DF7682C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift */; };
 		17A9D0899046B45E87834820 /* CarbRatioEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C8D5F457B5AFF763F8CF3DF /* CarbRatioEditorProvider.swift */; };
 		17A9D0899046B45E87834820 /* CarbRatioEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C8D5F457B5AFF763F8CF3DF /* CarbRatioEditorProvider.swift */; };
@@ -64,7 +63,7 @@
 		19F95FFA29F1102A00314DDC /* StatRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19F95FF929F1102A00314DDC /* StatRootView.swift */; };
 		19F95FFA29F1102A00314DDC /* StatRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19F95FF929F1102A00314DDC /* StatRootView.swift */; };
 		1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */; };
 		1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */; };
 		1D845DF2E3324130E1D95E67 /* DataTableProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60744C3E9BB3652895C908CC /* DataTableProvider.swift */; };
 		1D845DF2E3324130E1D95E67 /* DataTableProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60744C3E9BB3652895C908CC /* DataTableProvider.swift */; };
-		23888883D4EA091C88480FF2 /* BolusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19984D62EFC0035A9E9644D /* BolusProvider.swift */; };
+		23888883D4EA091C88480FF2 /* TreatmentsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19984D62EFC0035A9E9644D /* TreatmentsProvider.swift */; };
 		3083261C4B268E353F36CD0B /* AutotuneConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DCCCCE633F5E98E41B0CD3C /* AutotuneConfigDataFlow.swift */; };
 		3083261C4B268E353F36CD0B /* AutotuneConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DCCCCE633F5E98E41B0CD3C /* AutotuneConfigDataFlow.swift */; };
 		3171D2818C7C72CD1584BB5E /* GlucoseNotificationSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2C6489D29ECCCAD78E0721 /* GlucoseNotificationSettingsStateModel.swift */; };
 		3171D2818C7C72CD1584BB5E /* GlucoseNotificationSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2C6489D29ECCCAD78E0721 /* GlucoseNotificationSettingsStateModel.swift */; };
 		320D030F724170A637F06D50 /* (null) in Sources */ = {isa = PBXBuildFile; };
 		320D030F724170A637F06D50 /* (null) in Sources */ = {isa = PBXBuildFile; };
@@ -247,6 +246,7 @@
 		581516A92BCEEDF800BF67D7 /* NSPredicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581516A82BCEEDF800BF67D7 /* NSPredicates.swift */; };
 		581516A92BCEEDF800BF67D7 /* NSPredicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581516A82BCEEDF800BF67D7 /* NSPredicates.swift */; };
 		581AC4392BE22ED10038760C /* JSONConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581AC4382BE22ED10038760C /* JSONConverter.swift */; };
 		581AC4392BE22ED10038760C /* JSONConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581AC4382BE22ED10038760C /* JSONConverter.swift */; };
 		58237D9E2BCF0A6B00A47A79 /* PopupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58237D9D2BCF0A6B00A47A79 /* PopupView.swift */; };
 		58237D9E2BCF0A6B00A47A79 /* PopupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58237D9D2BCF0A6B00A47A79 /* PopupView.swift */; };
+		5825A1BE2C97335C0046467E /* EditTempTargetForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5825A1BD2C97335C0046467E /* EditTempTargetForm.swift */; };
 		582DF9752C8CDB92001F516D /* GlucoseChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582DF9742C8CDB92001F516D /* GlucoseChartView.swift */; };
 		582DF9752C8CDB92001F516D /* GlucoseChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582DF9742C8CDB92001F516D /* GlucoseChartView.swift */; };
 		582DF9772C8CDBE7001F516D /* InsulinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582DF9762C8CDBE7001F516D /* InsulinView.swift */; };
 		582DF9772C8CDBE7001F516D /* InsulinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582DF9762C8CDBE7001F516D /* InsulinView.swift */; };
 		582DF9792C8CE1E5001F516D /* MainChartHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582DF9782C8CE1E5001F516D /* MainChartHelper.swift */; };
 		582DF9792C8CE1E5001F516D /* MainChartHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582DF9782C8CE1E5001F516D /* MainChartHelper.swift */; };
@@ -267,11 +267,16 @@
 		5864E8592C42CFAE00294306 /* DeterminationStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5864E8582C42CFAE00294306 /* DeterminationStorage.swift */; };
 		5864E8592C42CFAE00294306 /* DeterminationStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5864E8582C42CFAE00294306 /* DeterminationStorage.swift */; };
 		587DA1F62B77F3DD00B28F8A /* SettingsRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587DA1F52B77F3DD00B28F8A /* SettingsRowView.swift */; };
 		587DA1F62B77F3DD00B28F8A /* SettingsRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587DA1F52B77F3DD00B28F8A /* SettingsRowView.swift */; };
 		5887527C2BD986E1008B081D /* OpenAPSBattery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5887527B2BD986E1008B081D /* OpenAPSBattery.swift */; };
 		5887527C2BD986E1008B081D /* OpenAPSBattery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5887527B2BD986E1008B081D /* OpenAPSBattery.swift */; };
+		58A3D53A2C96D4DE003F90FC /* AddTempTargetForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3D5392C96D4DE003F90FC /* AddTempTargetForm.swift */; };
+		58A3D5442C96DE11003F90FC /* TempTargetStored+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3D5432C96DE11003F90FC /* TempTargetStored+Helper.swift */; };
+		58A3D5512C96EFA8003F90FC /* TempTargetStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3D54D2C96EFA8003F90FC /* TempTargetStored+CoreDataClass.swift */; };
+		58A3D5522C96EFA8003F90FC /* TempTargetStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3D54E2C96EFA8003F90FC /* TempTargetStored+CoreDataProperties.swift */; };
+		58A3D5532C96EFA8003F90FC /* TempTargetRunStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3D54F2C96EFA8003F90FC /* TempTargetRunStored+CoreDataClass.swift */; };
+		58A3D5542C96EFA8003F90FC /* TempTargetRunStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3D5502C96EFA8003F90FC /* TempTargetRunStored+CoreDataProperties.swift */; };
 		58D08B222C8DAA8E00AA37D3 /* OverrideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D08B212C8DAA8E00AA37D3 /* OverrideView.swift */; };
 		58D08B222C8DAA8E00AA37D3 /* OverrideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D08B212C8DAA8E00AA37D3 /* OverrideView.swift */; };
 		58D08B302C8DEA7500AA37D3 /* ForecastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D08B2F2C8DEA7500AA37D3 /* ForecastView.swift */; };
 		58D08B302C8DEA7500AA37D3 /* ForecastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D08B2F2C8DEA7500AA37D3 /* ForecastView.swift */; };
 		58D08B322C8DF88900AA37D3 /* DummyCharts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D08B312C8DF88900AA37D3 /* DummyCharts.swift */; };
 		58D08B322C8DF88900AA37D3 /* DummyCharts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D08B312C8DF88900AA37D3 /* DummyCharts.swift */; };
-		58D08B342C8DF9A700AA37D3 /* CobChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D08B332C8DF9A700AA37D3 /* CobChart.swift */; };
-		58D08B362C8DFAC600AA37D3 /* IobChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D08B352C8DFAC600AA37D3 /* IobChart.swift */; };
+		58D08B342C8DF9A700AA37D3 /* CobIobChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D08B332C8DF9A700AA37D3 /* CobIobChart.swift */; };
 		58D08B382C8DFB6000AA37D3 /* BasalChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D08B372C8DFB6000AA37D3 /* BasalChart.swift */; };
 		58D08B382C8DFB6000AA37D3 /* BasalChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D08B372C8DFB6000AA37D3 /* BasalChart.swift */; };
 		58D08B3A2C8DFECD00AA37D3 /* TempTargets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D08B392C8DFECD00AA37D3 /* TempTargets.swift */; };
 		58D08B3A2C8DFECD00AA37D3 /* TempTargets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D08B392C8DFECD00AA37D3 /* TempTargets.swift */; };
 		58F107742BD1A4D000B1A680 /* Determination+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F107732BD1A4D000B1A680 /* Determination+helper.swift */; };
 		58F107742BD1A4D000B1A680 /* Determination+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F107732BD1A4D000B1A680 /* Determination+helper.swift */; };
@@ -283,7 +288,7 @@
 		642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8782B44544F38F2B2D82C38E /* NightscoutConfigRootView.swift */; };
 		642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8782B44544F38F2B2D82C38E /* NightscoutConfigRootView.swift */; };
 		65070A332BFDCB83006F213F /* TidepoolStartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65070A322BFDCB83006F213F /* TidepoolStartView.swift */; };
 		65070A332BFDCB83006F213F /* TidepoolStartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65070A322BFDCB83006F213F /* TidepoolStartView.swift */; };
 		6632A0DC746872439A858B44 /* ISFEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */; };
 		6632A0DC746872439A858B44 /* ISFEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */; };
-		69A31254F2451C20361D172F /* BolusStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223EC0494F55A91E3EA69EF4 /* BolusStateModel.swift */; };
+		69A31254F2451C20361D172F /* TreatmentsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223EC0494F55A91E3EA69EF4 /* TreatmentsStateModel.swift */; };
 		69B9A368029F7EB39F525422 /* CarbRatioEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA5E04A2761F6EEA6568E1 /* CarbRatioEditorStateModel.swift */; };
 		69B9A368029F7EB39F525422 /* CarbRatioEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA5E04A2761F6EEA6568E1 /* CarbRatioEditorStateModel.swift */; };
 		6B1A8D192B14D91600E76752 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B1A8D182B14D91600E76752 /* WidgetKit.framework */; };
 		6B1A8D192B14D91600E76752 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B1A8D182B14D91600E76752 /* WidgetKit.framework */; };
 		6B1A8D1B2B14D91600E76752 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B1A8D1A2B14D91600E76752 /* SwiftUI.framework */; };
 		6B1A8D1B2B14D91600E76752 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B1A8D1A2B14D91600E76752 /* SwiftUI.framework */; };
@@ -298,6 +303,7 @@
 		6EADD581738D64431902AC0A /* (null) in Sources */ = {isa = PBXBuildFile; };
 		6EADD581738D64431902AC0A /* (null) in Sources */ = {isa = PBXBuildFile; };
 		6FFAE524D1D9C262F2407CAE /* SnoozeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAE81192B118804DCD23034 /* SnoozeProvider.swift */; };
 		6FFAE524D1D9C262F2407CAE /* SnoozeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAE81192B118804DCD23034 /* SnoozeProvider.swift */; };
 		711C0CB42CAABE788916BC9D /* ManualTempBasalDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96653287EDB276A111288305 /* ManualTempBasalDataFlow.swift */; };
 		711C0CB42CAABE788916BC9D /* ManualTempBasalDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96653287EDB276A111288305 /* ManualTempBasalDataFlow.swift */; };
+		71D44AAB2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D44AAA2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift */; };
 		72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44080E4709E3AE4B73054563 /* ConfigEditorProvider.swift */; };
 		72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44080E4709E3AE4B73054563 /* ConfigEditorProvider.swift */; };
 		7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */; };
 		7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */; };
 		7F7B756BE8543965D9FDF1A2 /* DataTableDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A401509D21F7F35D4E109EDA /* DataTableDataFlow.swift */; };
 		7F7B756BE8543965D9FDF1A2 /* DataTableDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A401509D21F7F35D4E109EDA /* DataTableDataFlow.swift */; };
@@ -321,11 +327,15 @@
 		BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */; };
 		BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */; };
 		BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3CC0712B0B89D50013189E /* MainChartView.swift */; };
 		BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3CC0712B0B89D50013189E /* MainChartView.swift */; };
 		BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */; };
 		BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */; };
+		BD4ED4FD2CF9D5E8000EDC9C /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4ED4FC2CF9D5E8000EDC9C /* AppState.swift */; };
 		BD6EB2D62C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */; };
 		BD6EB2D62C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */; };
+		BD793CB02CE7C61500D669AC /* OverrideRunStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */; };
+		BD793CB22CE8033500D669AC /* TempTargetRunStored.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD793CB12CE8032E00D669AC /* TempTargetRunStored.swift */; };
 		BD7DA9A52AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */; };
 		BD7DA9A52AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */; };
 		BD7DA9A72AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */; };
 		BD7DA9A72AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */; };
 		BD7DA9A92AE06E9200601B20 /* BolusCalculatorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */; };
 		BD7DA9A92AE06E9200601B20 /* BolusCalculatorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */; };
 		BD7DA9AC2AE06EB900601B20 /* BolusCalculatorConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9AB2AE06EB900601B20 /* BolusCalculatorConfigRootView.swift */; };
 		BD7DA9AC2AE06EB900601B20 /* BolusCalculatorConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9AB2AE06EB900601B20 /* BolusCalculatorConfigRootView.swift */; };
+		BDA6CC882CAF219B00F942F9 /* TempTargetSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA6CC872CAF219800F942F9 /* TempTargetSetup.swift */; };
 		BDB3C1192C03DD1000CEEAA1 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */; };
 		BDB3C1192C03DD1000CEEAA1 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */; };
 		BDB899882C564509006F3298 /* ForecastChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB899872C564509006F3298 /* ForecastChart.swift */; };
 		BDB899882C564509006F3298 /* ForecastChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB899872C564509006F3298 /* ForecastChart.swift */; };
 		BDB8998A2C565D0C006F3298 /* CarbsGlucose+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */; };
 		BDB8998A2C565D0C006F3298 /* CarbsGlucose+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */; };
@@ -334,6 +344,7 @@
 		BDC2EA472C3045AD00E5BBD0 /* Override.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC2EA462C3045AD00E5BBD0 /* Override.swift */; };
 		BDC2EA472C3045AD00E5BBD0 /* Override.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC2EA462C3045AD00E5BBD0 /* Override.swift */; };
 		BDCAF2382C639F35002DC907 /* SettingItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCAF2372C639F35002DC907 /* SettingItems.swift */; };
 		BDCAF2382C639F35002DC907 /* SettingItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCAF2372C639F35002DC907 /* SettingItems.swift */; };
 		BDCD47AF2C1F3F1700F8BCD5 /* OverrideStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCD47AE2C1F3F1700F8BCD5 /* OverrideStored+helper.swift */; };
 		BDCD47AF2C1F3F1700F8BCD5 /* OverrideStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCD47AE2C1F3F1700F8BCD5 /* OverrideStored+helper.swift */; };
+		BDDAF9EF2D00554500B34E7A /* SelectionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDDAF9EE2D00553E00B34E7A /* SelectionPopoverView.swift */; };
 		BDF34EBE2C0A31D100D51995 /* CustomNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34EBD2C0A31D000D51995 /* CustomNotification.swift */; };
 		BDF34EBE2C0A31D100D51995 /* CustomNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34EBD2C0A31D000D51995 /* CustomNotification.swift */; };
 		BDF34F832C10C5B600D51995 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F822C10C5B600D51995 /* DataManager.swift */; };
 		BDF34F832C10C5B600D51995 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F822C10C5B600D51995 /* DataManager.swift */; };
 		BDF34F852C10C62E00D51995 /* GlucoseData.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F842C10C62E00D51995 /* GlucoseData.swift */; };
 		BDF34F852C10C62E00D51995 /* GlucoseData.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F842C10C62E00D51995 /* GlucoseData.swift */; };
@@ -341,8 +352,9 @@
 		BDF34F932C10D0E100D51995 /* LiveActivityAttributes+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F922C10D0E100D51995 /* LiveActivityAttributes+Helper.swift */; };
 		BDF34F932C10D0E100D51995 /* LiveActivityAttributes+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F922C10D0E100D51995 /* LiveActivityAttributes+Helper.swift */; };
 		BDF34F952C10D27300D51995 /* DeterminationData.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F942C10D27300D51995 /* DeterminationData.swift */; };
 		BDF34F952C10D27300D51995 /* DeterminationData.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F942C10D27300D51995 /* DeterminationData.swift */; };
 		BDF530D82B40F8AC002CAF43 /* LockScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF530D72B40F8AC002CAF43 /* LockScreenView.swift */; };
 		BDF530D82B40F8AC002CAF43 /* LockScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF530D72B40F8AC002CAF43 /* LockScreenView.swift */; };
-		BDFD165A2AE40438007F0DDA /* BolusRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFD16592AE40438007F0DDA /* BolusRootView.swift */; };
+		BDFD165A2AE40438007F0DDA /* TreatmentsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFD16592AE40438007F0DDA /* TreatmentsRootView.swift */; };
 		BF1667ADE69E4B5B111CECAE /* ManualTempBasalProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680C4420C9A345D46D90D06C /* ManualTempBasalProvider.swift */; };
 		BF1667ADE69E4B5B111CECAE /* ManualTempBasalProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680C4420C9A345D46D90D06C /* ManualTempBasalProvider.swift */; };
+		C2A0A42F2CE03131003B98E8 /* ConstantValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */; };
 		C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */; };
 		C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */; };
 		CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */; };
 		CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */; };
 		CC6C406E2ACDD69E009B8058 /* RawFetchedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */; };
 		CC6C406E2ACDD69E009B8058 /* RawFetchedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */; };
@@ -366,8 +378,7 @@
 		CE7CA34F2A064973004BE681 /* BaseIntentsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3442A064973004BE681 /* BaseIntentsRequest.swift */; };
 		CE7CA34F2A064973004BE681 /* BaseIntentsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3442A064973004BE681 /* BaseIntentsRequest.swift */; };
 		CE7CA3502A064973004BE681 /* CancelTempPresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3462A064973004BE681 /* CancelTempPresetIntent.swift */; };
 		CE7CA3502A064973004BE681 /* CancelTempPresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3462A064973004BE681 /* CancelTempPresetIntent.swift */; };
 		CE7CA3512A064973004BE681 /* ApplyTempPresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3472A064973004BE681 /* ApplyTempPresetIntent.swift */; };
 		CE7CA3512A064973004BE681 /* ApplyTempPresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3472A064973004BE681 /* ApplyTempPresetIntent.swift */; };
-		CE7CA3522A064973004BE681 /* ListTempPresetsIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3482A064973004BE681 /* ListTempPresetsIntent.swift */; };
-		CE7CA3532A064973004BE681 /* tempPresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3492A064973004BE681 /* tempPresetIntent.swift */; };
+		CE7CA3532A064973004BE681 /* TempPresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA3492A064973004BE681 /* TempPresetIntent.swift */; };
 		CE7CA3542A064973004BE681 /* TempPresetsIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA34A2A064973004BE681 /* TempPresetsIntentRequest.swift */; };
 		CE7CA3542A064973004BE681 /* TempPresetsIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA34A2A064973004BE681 /* TempPresetsIntentRequest.swift */; };
 		CE7CA3552A064973004BE681 /* ListStateIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA34C2A064973004BE681 /* ListStateIntent.swift */; };
 		CE7CA3552A064973004BE681 /* ListStateIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA34C2A064973004BE681 /* ListStateIntent.swift */; };
 		CE7CA3562A064973004BE681 /* StateIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA34D2A064973004BE681 /* StateIntentRequest.swift */; };
 		CE7CA3562A064973004BE681 /* StateIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7CA34D2A064973004BE681 /* StateIntentRequest.swift */; };
@@ -408,6 +419,7 @@
 		D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */; };
 		D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */; };
 		D76333C9256787610B3B4875 /* AutotuneConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D295A3F870E826BE371C0BB5 /* AutotuneConfigStateModel.swift */; };
 		D76333C9256787610B3B4875 /* AutotuneConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D295A3F870E826BE371C0BB5 /* AutotuneConfigStateModel.swift */; };
 		DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9F137F126D9F8DEB799F26 /* ISFEditorProvider.swift */; };
 		DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9F137F126D9F8DEB799F26 /* ISFEditorProvider.swift */; };
+		DD07CA142CE80B73002D45A9 /* TimeInRangeChartStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD07CA132CE80B73002D45A9 /* TimeInRangeChartStyle.swift */; };
 		DD09D47B2C5986D1003FEA5D /* CalendarEventSettingsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD09D47A2C5986D1003FEA5D /* CalendarEventSettingsDataFlow.swift */; };
 		DD09D47B2C5986D1003FEA5D /* CalendarEventSettingsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD09D47A2C5986D1003FEA5D /* CalendarEventSettingsDataFlow.swift */; };
 		DD09D47D2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD09D47C2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift */; };
 		DD09D47D2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD09D47C2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift */; };
 		DD09D47F2C5986E5003FEA5D /* CalendarEventSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD09D47E2C5986E5003FEA5D /* CalendarEventSettingsStateModel.swift */; };
 		DD09D47F2C5986E5003FEA5D /* CalendarEventSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD09D47E2C5986E5003FEA5D /* CalendarEventSettingsStateModel.swift */; };
@@ -448,6 +460,11 @@
 		DD32CF9E2CC824C5003686D6 /* TrioRemoteControl+Override.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CF9D2CC824C2003686D6 /* TrioRemoteControl+Override.swift */; };
 		DD32CF9E2CC824C5003686D6 /* TrioRemoteControl+Override.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CF9D2CC824C2003686D6 /* TrioRemoteControl+Override.swift */; };
 		DD32CFA02CC824D6003686D6 /* TrioRemoteControl+APNS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CF9F2CC824D3003686D6 /* TrioRemoteControl+APNS.swift */; };
 		DD32CFA02CC824D6003686D6 /* TrioRemoteControl+APNS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CF9F2CC824D3003686D6 /* TrioRemoteControl+APNS.swift */; };
 		DD32CFA22CC824E2003686D6 /* TrioRemoteControl+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CFA12CC824E1003686D6 /* TrioRemoteControl+Helpers.swift */; };
 		DD32CFA22CC824E2003686D6 /* TrioRemoteControl+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CFA12CC824E1003686D6 /* TrioRemoteControl+Helpers.swift */; };
+		DD5DC9F12CF3D97C00AB8703 /* AdjustmentsStateModel+Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5DC9F02CF3D96E00AB8703 /* AdjustmentsStateModel+Overrides.swift */; };
+		DD5DC9F32CF3D9DD00AB8703 /* AdjustmentsStateModel+TempTargets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5DC9F22CF3D9D600AB8703 /* AdjustmentsStateModel+TempTargets.swift */; };
+		DD5DC9F72CF3DA9300AB8703 /* TargetPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5DC9F62CF3DA9300AB8703 /* TargetPicker.swift */; };
+		DD5DC9F92CF3DAA900AB8703 /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5DC9F82CF3DAA900AB8703 /* RadioButton.swift */; };
+		DD5DC9FB2CF3E1B100AB8703 /* AdjustmentsStateModel+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5DC9FA2CF3E1AA00AB8703 /* AdjustmentsStateModel+Helpers.swift */; };
 		DD68889D2C386E17006E3C44 /* NightscoutExercise.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD68889C2C386E17006E3C44 /* NightscoutExercise.swift */; };
 		DD68889D2C386E17006E3C44 /* NightscoutExercise.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD68889C2C386E17006E3C44 /* NightscoutExercise.swift */; };
 		DD6B7CB22C7B6F0800B75029 /* Rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B7CB12C7B6F0800B75029 /* Rounding.swift */; };
 		DD6B7CB22C7B6F0800B75029 /* Rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B7CB12C7B6F0800B75029 /* Rounding.swift */; };
 		DD6B7CB42C7B71F700B75029 /* ForecastDisplayType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B7CB32C7B71F700B75029 /* ForecastDisplayType.swift */; };
 		DD6B7CB42C7B71F700B75029 /* ForecastDisplayType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B7CB32C7B71F700B75029 /* ForecastDisplayType.swift */; };
@@ -465,13 +482,14 @@
 		DD9ECB722CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB6F2CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift */; };
 		DD9ECB722CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB6F2CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift */; };
 		DD9ECB742CA9A0C300AA7C45 /* RemoteControlConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */; };
 		DD9ECB742CA9A0C300AA7C45 /* RemoteControlConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */; };
 		DDCEBF5B2CC1B76400DF4C36 /* LiveActivity+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */; };
 		DDCEBF5B2CC1B76400DF4C36 /* LiveActivity+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */; };
-		DDD163122C4C689900CD525A /* OverrideStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163112C4C689900CD525A /* OverrideStateModel.swift */; };
-		DDD163142C4C68D300CD525A /* OverrideProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163132C4C68D300CD525A /* OverrideProvider.swift */; };
-		DDD163162C4C690300CD525A /* OverrideDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163152C4C690300CD525A /* OverrideDataFlow.swift */; };
-		DDD163182C4C694000CD525A /* OverrideRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163172C4C694000CD525A /* OverrideRootView.swift */; };
+		DDD163122C4C689900CD525A /* AdjustmentsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163112C4C689900CD525A /* AdjustmentsStateModel.swift */; };
+		DDD163142C4C68D300CD525A /* AdjustmentsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163132C4C68D300CD525A /* AdjustmentsProvider.swift */; };
+		DDD163162C4C690300CD525A /* AdjustmentsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163152C4C690300CD525A /* AdjustmentsDataFlow.swift */; };
+		DDD163182C4C694000CD525A /* AdjustmentsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163172C4C694000CD525A /* AdjustmentsRootView.swift */; };
 		DDD1631A2C4C695E00CD525A /* EditOverrideForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163192C4C695E00CD525A /* EditOverrideForm.swift */; };
 		DDD1631A2C4C695E00CD525A /* EditOverrideForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163192C4C695E00CD525A /* EditOverrideForm.swift */; };
 		DDD1631C2C4C697400CD525A /* AddOverrideForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD1631B2C4C697400CD525A /* AddOverrideForm.swift */; };
 		DDD1631C2C4C697400CD525A /* AddOverrideForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD1631B2C4C697400CD525A /* AddOverrideForm.swift */; };
 		DDD1631F2C4C6F6900CD525A /* TrioCoreDataPersistentContainer.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DDD1631D2C4C6F6900CD525A /* TrioCoreDataPersistentContainer.xcdatamodeld */; };
 		DDD1631F2C4C6F6900CD525A /* TrioCoreDataPersistentContainer.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DDD1631D2C4C6F6900CD525A /* TrioCoreDataPersistentContainer.xcdatamodeld */; };
+		DDD6D4D32CDE90720029439A /* HbA1cDisplayUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD6D4D22CDE90720029439A /* HbA1cDisplayUnit.swift */; };
 		DDE179522C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179322C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift */; };
 		DDE179522C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179322C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift */; };
 		DDE179532C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179332C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift */; };
 		DDE179532C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179332C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift */; };
 		DDE179542C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179342C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift */; };
 		DDE179542C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179342C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift */; };
@@ -482,8 +500,6 @@
 		DDE179592C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179392C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift */; };
 		DDE179592C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179392C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift */; };
 		DDE1795A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1793A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift */; };
 		DDE1795A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1793A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift */; };
 		DDE1795B2C910127003CDDB7 /* CarbEntryStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1793B2C910127003CDDB7 /* CarbEntryStored+CoreDataProperties.swift */; };
 		DDE1795B2C910127003CDDB7 /* CarbEntryStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1793B2C910127003CDDB7 /* CarbEntryStored+CoreDataProperties.swift */; };
-		DDE1795C2C910127003CDDB7 /* TempTargets+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1793C2C910127003CDDB7 /* TempTargets+CoreDataClass.swift */; };
-		DDE1795D2C910127003CDDB7 /* TempTargets+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1793D2C910127003CDDB7 /* TempTargets+CoreDataProperties.swift */; };
 		DDE1795E2C910127003CDDB7 /* PumpEventStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1793E2C910127003CDDB7 /* PumpEventStored+CoreDataClass.swift */; };
 		DDE1795E2C910127003CDDB7 /* PumpEventStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1793E2C910127003CDDB7 /* PumpEventStored+CoreDataClass.swift */; };
 		DDE1795F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1793F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift */; };
 		DDE1795F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1793F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift */; };
 		DDE179602C910127003CDDB7 /* StatsData+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179402C910127003CDDB7 /* StatsData+CoreDataClass.swift */; };
 		DDE179602C910127003CDDB7 /* StatsData+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179402C910127003CDDB7 /* StatsData+CoreDataClass.swift */; };
@@ -496,8 +512,6 @@
 		DDE179672C910127003CDDB7 /* OpenAPS_Battery+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179472C910127003CDDB7 /* OpenAPS_Battery+CoreDataProperties.swift */; };
 		DDE179672C910127003CDDB7 /* OpenAPS_Battery+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179472C910127003CDDB7 /* OpenAPS_Battery+CoreDataProperties.swift */; };
 		DDE179682C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179482C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift */; };
 		DDE179682C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179482C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift */; };
 		DDE179692C910127003CDDB7 /* TempBasalStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179492C910127003CDDB7 /* TempBasalStored+CoreDataProperties.swift */; };
 		DDE179692C910127003CDDB7 /* TempBasalStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179492C910127003CDDB7 /* TempBasalStored+CoreDataProperties.swift */; };
-		DDE1796A2C910127003CDDB7 /* TempTargetsSlider+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1794A2C910127003CDDB7 /* TempTargetsSlider+CoreDataClass.swift */; };
-		DDE1796B2C910127003CDDB7 /* TempTargetsSlider+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1794B2C910127003CDDB7 /* TempTargetsSlider+CoreDataProperties.swift */; };
 		DDE1796C2C910127003CDDB7 /* OverrideRunStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1794C2C910127003CDDB7 /* OverrideRunStored+CoreDataClass.swift */; };
 		DDE1796C2C910127003CDDB7 /* OverrideRunStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1794C2C910127003CDDB7 /* OverrideRunStored+CoreDataClass.swift */; };
 		DDE1796D2C910127003CDDB7 /* OverrideRunStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1794D2C910127003CDDB7 /* OverrideRunStored+CoreDataProperties.swift */; };
 		DDE1796D2C910127003CDDB7 /* OverrideRunStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1794D2C910127003CDDB7 /* OverrideRunStored+CoreDataProperties.swift */; };
 		DDE1796E2C910127003CDDB7 /* OrefDetermination+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1794E2C910127003CDDB7 /* OrefDetermination+CoreDataClass.swift */; };
 		DDE1796E2C910127003CDDB7 /* OrefDetermination+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1794E2C910127003CDDB7 /* OrefDetermination+CoreDataClass.swift */; };
@@ -633,7 +647,6 @@
 		110AEDE92C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConfigStateModel.swift; sourceTree = "<group>"; };
 		110AEDE92C51A0AE00615CC9 /* ShortcutsConfigStateModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConfigStateModel.swift; sourceTree = "<group>"; };
 		118DF7642C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplyOverridePresetIntent.swift; sourceTree = "<group>"; };
 		118DF7642C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplyOverridePresetIntent.swift; sourceTree = "<group>"; };
 		118DF7652C5ECBC60067FEB7 /* CancelOverrideIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CancelOverrideIntent.swift; sourceTree = "<group>"; };
 		118DF7652C5ECBC60067FEB7 /* CancelOverrideIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CancelOverrideIntent.swift; sourceTree = "<group>"; };
-		118DF7662C5ECBC60067FEB7 /* ListOverridePresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOverridePresetIntent.swift; sourceTree = "<group>"; };
 		118DF7672C5ECBC60067FEB7 /* OverridePresetEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridePresetEntity.swift; sourceTree = "<group>"; };
 		118DF7672C5ECBC60067FEB7 /* OverridePresetEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridePresetEntity.swift; sourceTree = "<group>"; };
 		118DF7682C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridePresetsIntentRequest.swift; sourceTree = "<group>"; };
 		118DF7682C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridePresetsIntentRequest.swift; sourceTree = "<group>"; };
 		19012CDB291D2CB900FB8210 /* LoopStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStats.swift; sourceTree = "<group>"; };
 		19012CDB291D2CB900FB8210 /* LoopStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStats.swift; sourceTree = "<group>"; };
@@ -718,7 +731,7 @@
 		19F95FF629F10FEE00314DDC /* StatStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatStateModel.swift; sourceTree = "<group>"; };
 		19F95FF629F10FEE00314DDC /* StatStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatStateModel.swift; sourceTree = "<group>"; };
 		19F95FF929F1102A00314DDC /* StatRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatRootView.swift; sourceTree = "<group>"; };
 		19F95FF929F1102A00314DDC /* StatRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatRootView.swift; sourceTree = "<group>"; };
 		1CAE81192B118804DCD23034 /* SnoozeProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeProvider.swift; sourceTree = "<group>"; };
 		1CAE81192B118804DCD23034 /* SnoozeProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeProvider.swift; sourceTree = "<group>"; };
-		223EC0494F55A91E3EA69EF4 /* BolusStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusStateModel.swift; sourceTree = "<group>"; };
+		223EC0494F55A91E3EA69EF4 /* TreatmentsStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TreatmentsStateModel.swift; sourceTree = "<group>"; };
 		22963BD06A9C83959D4914E4 /* GlucoseNotificationSettingsRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationSettingsRootView.swift; sourceTree = "<group>"; };
 		22963BD06A9C83959D4914E4 /* GlucoseNotificationSettingsRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationSettingsRootView.swift; sourceTree = "<group>"; };
 		2AD22C985B79A2F0D2EA3D9D /* PumpConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigRootView.swift; sourceTree = "<group>"; };
 		2AD22C985B79A2F0D2EA3D9D /* PumpConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigRootView.swift; sourceTree = "<group>"; };
 		2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigDataFlow.swift; sourceTree = "<group>"; };
 		2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigDataFlow.swift; sourceTree = "<group>"; };
@@ -923,6 +936,7 @@
 		581516A82BCEEDF800BF67D7 /* NSPredicates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSPredicates.swift; sourceTree = "<group>"; };
 		581516A82BCEEDF800BF67D7 /* NSPredicates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSPredicates.swift; sourceTree = "<group>"; };
 		581AC4382BE22ED10038760C /* JSONConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONConverter.swift; sourceTree = "<group>"; };
 		581AC4382BE22ED10038760C /* JSONConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONConverter.swift; sourceTree = "<group>"; };
 		58237D9D2BCF0A6B00A47A79 /* PopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupView.swift; sourceTree = "<group>"; };
 		58237D9D2BCF0A6B00A47A79 /* PopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupView.swift; sourceTree = "<group>"; };
+		5825A1BD2C97335C0046467E /* EditTempTargetForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditTempTargetForm.swift; sourceTree = "<group>"; };
 		582DF9742C8CDB92001F516D /* GlucoseChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseChartView.swift; sourceTree = "<group>"; };
 		582DF9742C8CDB92001F516D /* GlucoseChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseChartView.swift; sourceTree = "<group>"; };
 		582DF9762C8CDBE7001F516D /* InsulinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsulinView.swift; sourceTree = "<group>"; };
 		582DF9762C8CDBE7001F516D /* InsulinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsulinView.swift; sourceTree = "<group>"; };
 		582DF9782C8CE1E5001F516D /* MainChartHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartHelper.swift; sourceTree = "<group>"; };
 		582DF9782C8CE1E5001F516D /* MainChartHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartHelper.swift; sourceTree = "<group>"; };
@@ -943,11 +957,16 @@
 		5864E8582C42CFAE00294306 /* DeterminationStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeterminationStorage.swift; sourceTree = "<group>"; };
 		5864E8582C42CFAE00294306 /* DeterminationStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeterminationStorage.swift; sourceTree = "<group>"; };
 		587DA1F52B77F3DD00B28F8A /* SettingsRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRowView.swift; sourceTree = "<group>"; };
 		587DA1F52B77F3DD00B28F8A /* SettingsRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRowView.swift; sourceTree = "<group>"; };
 		5887527B2BD986E1008B081D /* OpenAPSBattery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAPSBattery.swift; sourceTree = "<group>"; };
 		5887527B2BD986E1008B081D /* OpenAPSBattery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAPSBattery.swift; sourceTree = "<group>"; };
+		58A3D5392C96D4DE003F90FC /* AddTempTargetForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTempTargetForm.swift; sourceTree = "<group>"; };
+		58A3D5432C96DE11003F90FC /* TempTargetStored+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetStored+Helper.swift"; sourceTree = "<group>"; };
+		58A3D54D2C96EFA8003F90FC /* TempTargetStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetStored+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; };
+		58A3D54E2C96EFA8003F90FC /* TempTargetStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetStored+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
+		58A3D54F2C96EFA8003F90FC /* TempTargetRunStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetRunStored+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; };
+		58A3D5502C96EFA8003F90FC /* TempTargetRunStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetRunStored+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
 		58D08B212C8DAA8E00AA37D3 /* OverrideView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideView.swift; sourceTree = "<group>"; };
 		58D08B212C8DAA8E00AA37D3 /* OverrideView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideView.swift; sourceTree = "<group>"; };
 		58D08B2F2C8DEA7500AA37D3 /* ForecastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastView.swift; sourceTree = "<group>"; };
 		58D08B2F2C8DEA7500AA37D3 /* ForecastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastView.swift; sourceTree = "<group>"; };
 		58D08B312C8DF88900AA37D3 /* DummyCharts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyCharts.swift; sourceTree = "<group>"; };
 		58D08B312C8DF88900AA37D3 /* DummyCharts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyCharts.swift; sourceTree = "<group>"; };
-		58D08B332C8DF9A700AA37D3 /* CobChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CobChart.swift; sourceTree = "<group>"; };
-		58D08B352C8DFAC600AA37D3 /* IobChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IobChart.swift; sourceTree = "<group>"; };
+		58D08B332C8DF9A700AA37D3 /* CobIobChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CobIobChart.swift; sourceTree = "<group>"; };
 		58D08B372C8DFB6000AA37D3 /* BasalChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasalChart.swift; sourceTree = "<group>"; };
 		58D08B372C8DFB6000AA37D3 /* BasalChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasalChart.swift; sourceTree = "<group>"; };
 		58D08B392C8DFECD00AA37D3 /* TempTargets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargets.swift; sourceTree = "<group>"; };
 		58D08B392C8DFECD00AA37D3 /* TempTargets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargets.swift; sourceTree = "<group>"; };
 		58F107732BD1A4D000B1A680 /* Determination+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Determination+helper.swift"; sourceTree = "<group>"; };
 		58F107732BD1A4D000B1A680 /* Determination+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Determination+helper.swift"; sourceTree = "<group>"; };
@@ -971,6 +990,7 @@
 		6B1A8D252B14D91700E76752 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		6B1A8D252B14D91700E76752 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		6B1A8D2D2B156EEF00E76752 /* LiveActivityBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityBridge.swift; sourceTree = "<group>"; };
 		6B1A8D2D2B156EEF00E76752 /* LiveActivityBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityBridge.swift; sourceTree = "<group>"; };
 		6BCF84DC2B16843A003AD46E /* LiveActitiyAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActitiyAttributes.swift; sourceTree = "<group>"; };
 		6BCF84DC2B16843A003AD46E /* LiveActitiyAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActitiyAttributes.swift; sourceTree = "<group>"; };
+		71D44AAA2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertPermissionsChecker.swift; sourceTree = "<group>"; };
 		79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorDataFlow.swift; sourceTree = "<group>"; };
 		79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorDataFlow.swift; sourceTree = "<group>"; };
 		7E22146D3DF4853786C78132 /* CarbRatioEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CarbRatioEditorDataFlow.swift; sourceTree = "<group>"; };
 		7E22146D3DF4853786C78132 /* CarbRatioEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CarbRatioEditorDataFlow.swift; sourceTree = "<group>"; };
 		8782B44544F38F2B2D82C38E /* NightscoutConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigRootView.swift; sourceTree = "<group>"; };
 		8782B44544F38F2B2D82C38E /* NightscoutConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigRootView.swift; sourceTree = "<group>"; };
@@ -998,11 +1018,15 @@
 		BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxToggleStyle.swift; sourceTree = "<group>"; };
 		BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxToggleStyle.swift; sourceTree = "<group>"; };
 		BD3CC0712B0B89D50013189E /* MainChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartView.swift; sourceTree = "<group>"; };
 		BD3CC0712B0B89D50013189E /* MainChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartView.swift; sourceTree = "<group>"; };
 		BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataObserver.swift; sourceTree = "<group>"; };
 		BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataObserver.swift; sourceTree = "<group>"; };
+		BD4ED4FC2CF9D5E8000EDC9C /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
 		BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityWidgetConfiguration.swift; sourceTree = "<group>"; };
 		BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityWidgetConfiguration.swift; sourceTree = "<group>"; };
+		BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideRunStored+helper.swift"; sourceTree = "<group>"; };
+		BD793CB12CE8032E00D669AC /* TempTargetRunStored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetRunStored.swift; sourceTree = "<group>"; };
 		BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigDataFlow.swift; sourceTree = "<group>"; };
 		BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigDataFlow.swift; sourceTree = "<group>"; };
 		BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigProvider.swift; sourceTree = "<group>"; };
 		BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigProvider.swift; sourceTree = "<group>"; };
 		BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorStateModel.swift; sourceTree = "<group>"; };
 		BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorStateModel.swift; sourceTree = "<group>"; };
 		BD7DA9AB2AE06EB900601B20 /* BolusCalculatorConfigRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigRootView.swift; sourceTree = "<group>"; };
 		BD7DA9AB2AE06EB900601B20 /* BolusCalculatorConfigRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigRootView.swift; sourceTree = "<group>"; };
+		BDA6CC872CAF219800F942F9 /* TempTargetSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetSetup.swift; sourceTree = "<group>"; };
 		BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtension.swift; sourceTree = "<group>"; };
 		BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtension.swift; sourceTree = "<group>"; };
 		BDB899872C564509006F3298 /* ForecastChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastChart.swift; sourceTree = "<group>"; };
 		BDB899872C564509006F3298 /* ForecastChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastChart.swift; sourceTree = "<group>"; };
 		BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CarbsGlucose+helper.swift"; sourceTree = "<group>"; };
 		BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CarbsGlucose+helper.swift"; sourceTree = "<group>"; };
@@ -1011,6 +1035,7 @@
 		BDC2EA462C3045AD00E5BBD0 /* Override.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Override.swift; sourceTree = "<group>"; };
 		BDC2EA462C3045AD00E5BBD0 /* Override.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Override.swift; sourceTree = "<group>"; };
 		BDCAF2372C639F35002DC907 /* SettingItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingItems.swift; sourceTree = "<group>"; };
 		BDCAF2372C639F35002DC907 /* SettingItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingItems.swift; sourceTree = "<group>"; };
 		BDCD47AE2C1F3F1700F8BCD5 /* OverrideStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideStored+helper.swift"; sourceTree = "<group>"; };
 		BDCD47AE2C1F3F1700F8BCD5 /* OverrideStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideStored+helper.swift"; sourceTree = "<group>"; };
+		BDDAF9EE2D00553E00B34E7A /* SelectionPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionPopoverView.swift; sourceTree = "<group>"; };
 		BDF34EBD2C0A31D000D51995 /* CustomNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNotification.swift; sourceTree = "<group>"; };
 		BDF34EBD2C0A31D000D51995 /* CustomNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNotification.swift; sourceTree = "<group>"; };
 		BDF34F822C10C5B600D51995 /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = "<group>"; };
 		BDF34F822C10C5B600D51995 /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = "<group>"; };
 		BDF34F842C10C62E00D51995 /* GlucoseData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseData.swift; sourceTree = "<group>"; };
 		BDF34F842C10C62E00D51995 /* GlucoseData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseData.swift; sourceTree = "<group>"; };
@@ -1018,11 +1043,13 @@
 		BDF34F922C10D0E100D51995 /* LiveActivityAttributes+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LiveActivityAttributes+Helper.swift"; sourceTree = "<group>"; };
 		BDF34F922C10D0E100D51995 /* LiveActivityAttributes+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LiveActivityAttributes+Helper.swift"; sourceTree = "<group>"; };
 		BDF34F942C10D27300D51995 /* DeterminationData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeterminationData.swift; sourceTree = "<group>"; };
 		BDF34F942C10D27300D51995 /* DeterminationData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeterminationData.swift; sourceTree = "<group>"; };
 		BDF530D72B40F8AC002CAF43 /* LockScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenView.swift; sourceTree = "<group>"; };
 		BDF530D72B40F8AC002CAF43 /* LockScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenView.swift; sourceTree = "<group>"; };
-		BDFD16592AE40438007F0DDA /* BolusRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusRootView.swift; sourceTree = "<group>"; };
+		BDFD16592AE40438007F0DDA /* TreatmentsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreatmentsRootView.swift; sourceTree = "<group>"; };
 		BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorRootView.swift; sourceTree = "<group>"; };
 		BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorRootView.swift; sourceTree = "<group>"; };
 		C19984D62EFC0035A9E9644D /* BolusProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusProvider.swift; sourceTree = "<group>"; };
 		C19984D62EFC0035A9E9644D /* BolusProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusProvider.swift; sourceTree = "<group>"; };
+		C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantValues.swift; sourceTree = "<group>"; };
+		C19984D62EFC0035A9E9644D /* TreatmentsProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TreatmentsProvider.swift; sourceTree = "<group>"; };
 		C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalRootView.swift; sourceTree = "<group>"; };
 		C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalRootView.swift; sourceTree = "<group>"; };
-		C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusDataFlow.swift; sourceTree = "<group>"; };
+		C8D1A7CA8C10C4403D4BBFA7 /* TreatmentsDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TreatmentsDataFlow.swift; sourceTree = "<group>"; };
 		CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawFetchedProfile.swift; sourceTree = "<group>"; };
 		CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawFetchedProfile.swift; sourceTree = "<group>"; };
 		CC76E9502BD4812E008BEB61 /* Forecast+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Forecast+helper.swift"; sourceTree = "<group>"; };
 		CC76E9502BD4812E008BEB61 /* Forecast+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Forecast+helper.swift"; sourceTree = "<group>"; };
 		CE1856F42ADC4858007E39C7 /* AddCarbPresetIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCarbPresetIntent.swift; sourceTree = "<group>"; };
 		CE1856F42ADC4858007E39C7 /* AddCarbPresetIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCarbPresetIntent.swift; sourceTree = "<group>"; };
@@ -1048,8 +1075,7 @@
 		CE7CA3442A064973004BE681 /* BaseIntentsRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseIntentsRequest.swift; sourceTree = "<group>"; };
 		CE7CA3442A064973004BE681 /* BaseIntentsRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseIntentsRequest.swift; sourceTree = "<group>"; };
 		CE7CA3462A064973004BE681 /* CancelTempPresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CancelTempPresetIntent.swift; sourceTree = "<group>"; };
 		CE7CA3462A064973004BE681 /* CancelTempPresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CancelTempPresetIntent.swift; sourceTree = "<group>"; };
 		CE7CA3472A064973004BE681 /* ApplyTempPresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplyTempPresetIntent.swift; sourceTree = "<group>"; };
 		CE7CA3472A064973004BE681 /* ApplyTempPresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplyTempPresetIntent.swift; sourceTree = "<group>"; };
-		CE7CA3482A064973004BE681 /* ListTempPresetsIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTempPresetsIntent.swift; sourceTree = "<group>"; };
-		CE7CA3492A064973004BE681 /* tempPresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = tempPresetIntent.swift; sourceTree = "<group>"; };
+		CE7CA3492A064973004BE681 /* TempPresetIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempPresetIntent.swift; sourceTree = "<group>"; };
 		CE7CA34A2A064973004BE681 /* TempPresetsIntentRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempPresetsIntentRequest.swift; sourceTree = "<group>"; };
 		CE7CA34A2A064973004BE681 /* TempPresetsIntentRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempPresetsIntentRequest.swift; sourceTree = "<group>"; };
 		CE7CA34C2A064973004BE681 /* ListStateIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListStateIntent.swift; sourceTree = "<group>"; };
 		CE7CA34C2A064973004BE681 /* ListStateIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListStateIntent.swift; sourceTree = "<group>"; };
 		CE7CA34D2A064973004BE681 /* StateIntentRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateIntentRequest.swift; sourceTree = "<group>"; };
 		CE7CA34D2A064973004BE681 /* StateIntentRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateIntentRequest.swift; sourceTree = "<group>"; };
@@ -1087,6 +1113,7 @@
 		D0BDC6993C1087310EDFC428 /* CarbRatioEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CarbRatioEditorRootView.swift; sourceTree = "<group>"; };
 		D0BDC6993C1087310EDFC428 /* CarbRatioEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CarbRatioEditorRootView.swift; sourceTree = "<group>"; };
 		D295A3F870E826BE371C0BB5 /* AutotuneConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AutotuneConfigStateModel.swift; sourceTree = "<group>"; };
 		D295A3F870E826BE371C0BB5 /* AutotuneConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AutotuneConfigStateModel.swift; sourceTree = "<group>"; };
 		DC2C6489D29ECCCAD78E0721 /* GlucoseNotificationSettingsStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationSettingsStateModel.swift; sourceTree = "<group>"; };
 		DC2C6489D29ECCCAD78E0721 /* GlucoseNotificationSettingsStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationSettingsStateModel.swift; sourceTree = "<group>"; };
+		DD07CA132CE80B73002D45A9 /* TimeInRangeChartStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInRangeChartStyle.swift; sourceTree = "<group>"; };
 		DD09D47A2C5986D1003FEA5D /* CalendarEventSettingsDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarEventSettingsDataFlow.swift; sourceTree = "<group>"; };
 		DD09D47A2C5986D1003FEA5D /* CalendarEventSettingsDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarEventSettingsDataFlow.swift; sourceTree = "<group>"; };
 		DD09D47C2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarEventSettingsProvider.swift; sourceTree = "<group>"; };
 		DD09D47C2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarEventSettingsProvider.swift; sourceTree = "<group>"; };
 		DD09D47E2C5986E5003FEA5D /* CalendarEventSettingsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarEventSettingsStateModel.swift; sourceTree = "<group>"; };
 		DD09D47E2C5986E5003FEA5D /* CalendarEventSettingsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarEventSettingsStateModel.swift; sourceTree = "<group>"; };
@@ -1127,6 +1154,11 @@
 		DD32CF9D2CC824C2003686D6 /* TrioRemoteControl+Override.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+Override.swift"; sourceTree = "<group>"; };
 		DD32CF9D2CC824C2003686D6 /* TrioRemoteControl+Override.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+Override.swift"; sourceTree = "<group>"; };
 		DD32CF9F2CC824D3003686D6 /* TrioRemoteControl+APNS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+APNS.swift"; sourceTree = "<group>"; };
 		DD32CF9F2CC824D3003686D6 /* TrioRemoteControl+APNS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+APNS.swift"; sourceTree = "<group>"; };
 		DD32CFA12CC824E1003686D6 /* TrioRemoteControl+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+Helpers.swift"; sourceTree = "<group>"; };
 		DD32CFA12CC824E1003686D6 /* TrioRemoteControl+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+Helpers.swift"; sourceTree = "<group>"; };
+		DD5DC9F02CF3D96E00AB8703 /* AdjustmentsStateModel+Overrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentsStateModel+Overrides.swift"; sourceTree = "<group>"; };
+		DD5DC9F22CF3D9D600AB8703 /* AdjustmentsStateModel+TempTargets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentsStateModel+TempTargets.swift"; sourceTree = "<group>"; };
+		DD5DC9F62CF3DA9300AB8703 /* TargetPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetPicker.swift; sourceTree = "<group>"; };
+		DD5DC9F82CF3DAA900AB8703 /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = "<group>"; };
+		DD5DC9FA2CF3E1AA00AB8703 /* AdjustmentsStateModel+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentsStateModel+Helpers.swift"; sourceTree = "<group>"; };
 		DD68889C2C386E17006E3C44 /* NightscoutExercise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutExercise.swift; sourceTree = "<group>"; };
 		DD68889C2C386E17006E3C44 /* NightscoutExercise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutExercise.swift; sourceTree = "<group>"; };
 		DD6B7CB12C7B6F0800B75029 /* Rounding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Rounding.swift; sourceTree = "<group>"; };
 		DD6B7CB12C7B6F0800B75029 /* Rounding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Rounding.swift; sourceTree = "<group>"; };
 		DD6B7CB32C7B71F700B75029 /* ForecastDisplayType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastDisplayType.swift; sourceTree = "<group>"; };
 		DD6B7CB32C7B71F700B75029 /* ForecastDisplayType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastDisplayType.swift; sourceTree = "<group>"; };
@@ -1144,13 +1176,14 @@
 		DD9ECB6F2CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfigDataFlow.swift; sourceTree = "<group>"; };
 		DD9ECB6F2CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfigDataFlow.swift; sourceTree = "<group>"; };
 		DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfig.swift; sourceTree = "<group>"; };
 		DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfig.swift; sourceTree = "<group>"; };
 		DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LiveActivity+Helper.swift"; sourceTree = "<group>"; };
 		DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LiveActivity+Helper.swift"; sourceTree = "<group>"; };
-		DDD163112C4C689900CD525A /* OverrideStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideStateModel.swift; sourceTree = "<group>"; };
-		DDD163132C4C68D300CD525A /* OverrideProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideProvider.swift; sourceTree = "<group>"; };
-		DDD163152C4C690300CD525A /* OverrideDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideDataFlow.swift; sourceTree = "<group>"; };
-		DDD163172C4C694000CD525A /* OverrideRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideRootView.swift; sourceTree = "<group>"; };
+		DDD163112C4C689900CD525A /* AdjustmentsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdjustmentsStateModel.swift; sourceTree = "<group>"; };
+		DDD163132C4C68D300CD525A /* AdjustmentsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdjustmentsProvider.swift; sourceTree = "<group>"; };
+		DDD163152C4C690300CD525A /* AdjustmentsDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdjustmentsDataFlow.swift; sourceTree = "<group>"; };
+		DDD163172C4C694000CD525A /* AdjustmentsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdjustmentsRootView.swift; sourceTree = "<group>"; };
 		DDD163192C4C695E00CD525A /* EditOverrideForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditOverrideForm.swift; sourceTree = "<group>"; };
 		DDD163192C4C695E00CD525A /* EditOverrideForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditOverrideForm.swift; sourceTree = "<group>"; };
 		DDD1631B2C4C697400CD525A /* AddOverrideForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddOverrideForm.swift; sourceTree = "<group>"; };
 		DDD1631B2C4C697400CD525A /* AddOverrideForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddOverrideForm.swift; sourceTree = "<group>"; };
 		DDD1631E2C4C6F6900CD525A /* TrioCoreDataPersistentContainer.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = TrioCoreDataPersistentContainer.xcdatamodel; sourceTree = "<group>"; };
 		DDD1631E2C4C6F6900CD525A /* TrioCoreDataPersistentContainer.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = TrioCoreDataPersistentContainer.xcdatamodel; sourceTree = "<group>"; };
+		DDD6D4D22CDE90720029439A /* HbA1cDisplayUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HbA1cDisplayUnit.swift; sourceTree = "<group>"; };
 		DDE179322C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MealPresetStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE179322C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MealPresetStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE179332C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MealPresetStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE179332C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MealPresetStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE179342C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoopStatRecord+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE179342C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoopStatRecord+CoreDataClass.swift"; sourceTree = "<group>"; };
@@ -1161,8 +1194,6 @@
 		DDE179392C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ForecastValue+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE179392C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ForecastValue+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE1793A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CarbEntryStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE1793A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CarbEntryStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE1793B2C910127003CDDB7 /* CarbEntryStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CarbEntryStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE1793B2C910127003CDDB7 /* CarbEntryStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CarbEntryStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
-		DDE1793C2C910127003CDDB7 /* TempTargets+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargets+CoreDataClass.swift"; sourceTree = "<group>"; };
-		DDE1793D2C910127003CDDB7 /* TempTargets+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargets+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE1793E2C910127003CDDB7 /* PumpEventStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PumpEventStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE1793E2C910127003CDDB7 /* PumpEventStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PumpEventStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE1793F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PumpEventStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE1793F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PumpEventStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE179402C910127003CDDB7 /* StatsData+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatsData+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE179402C910127003CDDB7 /* StatsData+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatsData+CoreDataClass.swift"; sourceTree = "<group>"; };
@@ -1175,8 +1206,6 @@
 		DDE179472C910127003CDDB7 /* OpenAPS_Battery+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenAPS_Battery+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE179472C910127003CDDB7 /* OpenAPS_Battery+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenAPS_Battery+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE179482C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempBasalStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE179482C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempBasalStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE179492C910127003CDDB7 /* TempBasalStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempBasalStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE179492C910127003CDDB7 /* TempBasalStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempBasalStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
-		DDE1794A2C910127003CDDB7 /* TempTargetsSlider+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetsSlider+CoreDataClass.swift"; sourceTree = "<group>"; };
-		DDE1794B2C910127003CDDB7 /* TempTargetsSlider+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetsSlider+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE1794C2C910127003CDDB7 /* OverrideRunStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideRunStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE1794C2C910127003CDDB7 /* OverrideRunStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideRunStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE1794D2C910127003CDDB7 /* OverrideRunStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideRunStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE1794D2C910127003CDDB7 /* OverrideRunStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideRunStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE1794E2C910127003CDDB7 /* OrefDetermination+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrefDetermination+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE1794E2C910127003CDDB7 /* OrefDetermination+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrefDetermination+CoreDataClass.swift"; sourceTree = "<group>"; };
@@ -1341,7 +1370,6 @@
 			children = (
 			children = (
 				118DF7642C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift */,
 				118DF7642C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift */,
 				118DF7652C5ECBC60067FEB7 /* CancelOverrideIntent.swift */,
 				118DF7652C5ECBC60067FEB7 /* CancelOverrideIntent.swift */,
-				118DF7662C5ECBC60067FEB7 /* ListOverridePresetIntent.swift */,
 				118DF7672C5ECBC60067FEB7 /* OverridePresetEntity.swift */,
 				118DF7672C5ECBC60067FEB7 /* OverridePresetEntity.swift */,
 				118DF7682C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift */,
 				118DF7682C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift */,
 			);
 			);
@@ -1501,20 +1529,18 @@
 		3811DE0325C9D31700A708ED /* Modules */ = {
 		3811DE0325C9D31700A708ED /* Modules */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				DD9ECB6B2CA99FA400AA7C45 /* RemoteControlConfig */,
-				DD9ECB662CA99EFE00AA7C45 /* RemoteControl */,
+				DDD163032C4C67B400CD525A /* Adjustments */,
 				DD1745382C55BF8B00211FAC /* AlgorithmAdvancedSettings */,
 				DD1745382C55BF8B00211FAC /* AlgorithmAdvancedSettings */,
 				DD1745422C55C5C400211FAC /* AutosensSettings */,
 				DD1745422C55C5C400211FAC /* AutosensSettings */,
 				672F63EEAE27400625E14BAD /* AutotuneConfig */,
 				672F63EEAE27400625E14BAD /* AutotuneConfig */,
 				A42F1FEDFFD0DDE00AAD54D3 /* BasalProfileEditor */,
 				A42F1FEDFFD0DDE00AAD54D3 /* BasalProfileEditor */,
 				3811DE0425C9D32E00A708ED /* Base */,
 				3811DE0425C9D32E00A708ED /* Base */,
-				C2C98283C436DB934D7E7994 /* Bolus */,
 				BD7DA9A32AE06DBA00601B20 /* BolusCalculatorConfig */,
 				BD7DA9A32AE06DBA00601B20 /* BolusCalculatorConfig */,
 				DD09D4792C5986BA003FEA5D /* CalendarEventSettings */,
 				DD09D4792C5986BA003FEA5D /* CalendarEventSettings */,
 				CEE9A64D2BBB411C00EB5194 /* Calibrations */,
 				CEE9A64D2BBB411C00EB5194 /* Calibrations */,
+				E42231DBF0DBE2B4B92D1B15 /* CarbRatioEditor */,
 				F75CB57ED6971B46F8756083 /* CGM */,
 				F75CB57ED6971B46F8756083 /* CGM */,
 				0610F7D6D2EC00E3BA1569F0 /* ConfigEditor */,
 				0610F7D6D2EC00E3BA1569F0 /* ConfigEditor */,
-				E42231DBF0DBE2B4B92D1B15 /* CarbRatioEditor */,
 				9E56E3626FAD933385101B76 /* DataTable */,
 				9E56E3626FAD933385101B76 /* DataTable */,
 				195D80B22AF696EE00D25097 /* DynamicSettings */,
 				195D80B22AF696EE00D25097 /* DynamicSettings */,
 				DD17454C2C55CA0200211FAC /* GeneralSettings */,
 				DD17454C2C55CA0200211FAC /* GeneralSettings */,
@@ -1528,8 +1554,8 @@
 				5031FE61F63C2A8A8B7674DD /* ManualTempBasal */,
 				5031FE61F63C2A8A8B7674DD /* ManualTempBasal */,
 				19D466A129AA2B0A004D5F33 /* MealSettings */,
 				19D466A129AA2B0A004D5F33 /* MealSettings */,
 				D533BF261CDC1C3F871E7BFD /* NightscoutConfig */,
 				D533BF261CDC1C3F871E7BFD /* NightscoutConfig */,
-				DDD163032C4C67B400CD525A /* OverrideConfig */,
 				99C01B871ACAB3F32CE755C7 /* PumpConfig */,
 				99C01B871ACAB3F32CE755C7 /* PumpConfig */,
+				DD9ECB6B2CA99FA400AA7C45 /* RemoteControlConfig */,
 				3811DE3825C9D4A100A708ED /* Settings */,
 				3811DE3825C9D4A100A708ED /* Settings */,
 				110AEDEA2C51A0AE00615CC9 /* ShortcutsConfig */,
 				110AEDEA2C51A0AE00615CC9 /* ShortcutsConfig */,
 				DD17451E2C55520000211FAC /* SMBSettings */,
 				DD17451E2C55520000211FAC /* SMBSettings */,
@@ -1537,6 +1563,7 @@
 				19F95FF129F10F9C00314DDC /* Stat */,
 				19F95FF129F10F9C00314DDC /* Stat */,
 				DD17452C2C55AE3500211FAC /* TargetBehavoir */,
 				DD17452C2C55AE3500211FAC /* TargetBehavoir */,
 				6517011F19F244F64E1FF14B /* TargetsEditor */,
 				6517011F19F244F64E1FF14B /* TargetsEditor */,
+				C2C98283C436DB934D7E7994 /* Treatments */,
 				190EBCC229FF134900BA767D /* UserInterfaceSettings */,
 				190EBCC229FF134900BA767D /* UserInterfaceSettings */,
 				CE94597C29E9E1CD0047C9C6 /* WatchConfig */,
 				CE94597C29E9E1CD0047C9C6 /* WatchConfig */,
 			);
 			);
@@ -1650,18 +1677,19 @@
 		3811DE9125C9D88200A708ED /* Services */ = {
 		3811DE9125C9D88200A708ED /* Services */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				6B1A8D2C2B156EC100E76752 /* LiveActivity */,
+				3811DE9225C9D88200A708ED /* Appearance */,
 				CEB434E128B8F9BC00B70274 /* Bluetooth */,
 				CEB434E128B8F9BC00B70274 /* Bluetooth */,
-				F90692A8274B7A980037068D /* HealthKit */,
-				38E8754D275556E100975559 /* WatchManager */,
-				38E87406274F9AA500975559 /* UserNotifications */,
 				3862CC2C2743F9DC00BF832C /* Calendar */,
 				3862CC2C2743F9DC00BF832C /* Calendar */,
-				38AEE75025F021F10013F05B /* SettingsManager */,
-				38B4F3C425E5016800E76A18 /* Notifications */,
-				3811DE9225C9D88200A708ED /* Appearance */,
+				F90692A8274B7A980037068D /* HealthKit */,
+				6B1A8D2C2B156EC100E76752 /* LiveActivity */,
 				3811DE9425C9D88200A708ED /* Network */,
 				3811DE9425C9D88200A708ED /* Network */,
+				38B4F3C425E5016800E76A18 /* Notifications */,
+				DD9ECB662CA99EFE00AA7C45 /* RemoteControl */,
+				38AEE75025F021F10013F05B /* SettingsManager */,
 				3811DE9825C9D88300A708ED /* Storage */,
 				3811DE9825C9D88300A708ED /* Storage */,
 				3811DEA525C9D88300A708ED /* UnlockManager */,
 				3811DEA525C9D88300A708ED /* UnlockManager */,
+				38E87406274F9AA500975559 /* UserNotifications */,
+				38E8754D275556E100975559 /* WatchManager */,
 			);
 			);
 			path = Services;
 			path = Services;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -1680,9 +1708,8 @@
 				38E44521274E3DDC00EC9A94 /* NetworkReachabilityManager.swift */,
 				38E44521274E3DDC00EC9A94 /* NetworkReachabilityManager.swift */,
 				38192E03261B82FA0094D973 /* ReachabilityManager.swift */,
 				38192E03261B82FA0094D973 /* ReachabilityManager.swift */,
 				3811DE9625C9D88300A708ED /* HTTPResponseStatus.swift */,
 				3811DE9625C9D88300A708ED /* HTTPResponseStatus.swift */,
-				3811DE9725C9D88300A708ED /* NightscoutManager.swift */,
+				DDC9B9962CFD2332003E7721 /* Nightscout */,
 				38FE826925CC82DB001FF17A /* NetworkService.swift */,
 				38FE826925CC82DB001FF17A /* NetworkService.swift */,
-				38FE826C25CC8461001FF17A /* NightscoutAPI.swift */,
 				CE1F6DDA2BAE08B60064EB8D /* TidepoolManager.swift */,
 				CE1F6DDA2BAE08B60064EB8D /* TidepoolManager.swift */,
 			);
 			);
 			path = Network;
 			path = Network;
@@ -1746,6 +1773,7 @@
 			children = (
 			children = (
 				38E4451D274DB04600EC9A94 /* AppDelegate.swift */,
 				38E4451D274DB04600EC9A94 /* AppDelegate.swift */,
 				388E595B25AD948C0019842D /* FreeAPSApp.swift */,
 				388E595B25AD948C0019842D /* FreeAPSApp.swift */,
+				BD4ED4FC2CF9D5E8000EDC9C /* AppState.swift */,
 			);
 			);
 			path = Application;
 			path = Application;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -1833,16 +1861,7 @@
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
 				BD3CC0712B0B89D50013189E /* MainChartView.swift */,
 				BD3CC0712B0B89D50013189E /* MainChartView.swift */,
-				582DF9742C8CDB92001F516D /* GlucoseChartView.swift */,
-				582DF9762C8CDBE7001F516D /* InsulinView.swift */,
-				582DF97A2C8CE209001F516D /* CarbView.swift */,
-				58D08B212C8DAA8E00AA37D3 /* OverrideView.swift */,
-				58D08B2F2C8DEA7500AA37D3 /* ForecastView.swift */,
-				58D08B312C8DF88900AA37D3 /* DummyCharts.swift */,
-				58D08B332C8DF9A700AA37D3 /* CobChart.swift */,
-				58D08B352C8DFAC600AA37D3 /* IobChart.swift */,
-				58D08B372C8DFB6000AA37D3 /* BasalChart.swift */,
-				58D08B392C8DFECD00AA37D3 /* TempTargets.swift */,
+				BDDAF9F12D0055CC00B34E7A /* ChartElements */,
 			);
 			);
 			path = Chart;
 			path = Chart;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -1950,6 +1969,7 @@
 		388E5A5925B6F0250019842D /* Models */ = {
 		388E5A5925B6F0250019842D /* Models */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				DD07CA132CE80B73002D45A9 /* TimeInRangeChartStyle.swift */,
 				DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */,
 				DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */,
 				DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */,
 				DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */,
 				385CEAC025F2EA52002D6D5B /* Announcement.swift */,
 				385CEAC025F2EA52002D6D5B /* Announcement.swift */,
@@ -1996,6 +2016,7 @@
 				DD6B7CB32C7B71F700B75029 /* ForecastDisplayType.swift */,
 				DD6B7CB32C7B71F700B75029 /* ForecastDisplayType.swift */,
 				DD6B7CB52C7B748B00B75029 /* TotalInsulinDisplayType.swift */,
 				DD6B7CB52C7B748B00B75029 /* TotalInsulinDisplayType.swift */,
 				DD9ECB692CA99F6C00AA7C45 /* PushMessage.swift */,
 				DD9ECB692CA99F6C00AA7C45 /* PushMessage.swift */,
+				DDD6D4D22CDE90720029439A /* HbA1cDisplayUnit.swift */,
 			);
 			);
 			path = Models;
 			path = Models;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -2003,6 +2024,7 @@
 		388E5A5A25B6F05F0019842D /* Helpers */ = {
 		388E5A5A25B6F05F0019842D /* Helpers */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */,
 				DD940BAB2CA75889000830A5 /* DynamicGlucoseColor.swift */,
 				DD940BAB2CA75889000830A5 /* DynamicGlucoseColor.swift */,
 				38F37827261260DC009DB701 /* Color+Extensions.swift */,
 				38F37827261260DC009DB701 /* Color+Extensions.swift */,
 				389ECE042601144100D86C4F /* ConcurrentMap.swift */,
 				389ECE042601144100D86C4F /* ConcurrentMap.swift */,
@@ -2078,6 +2100,7 @@
 		38B4F3C425E5016800E76A18 /* Notifications */ = {
 		38B4F3C425E5016800E76A18 /* Notifications */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				71D44AAA2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift */,
 				38B4F3CC25E5031100E76A18 /* Broadcaster.swift */,
 				38B4F3CC25E5031100E76A18 /* Broadcaster.swift */,
 				38B4F3C525E5017E00E76A18 /* NotificationCenter.swift */,
 				38B4F3C525E5017E00E76A18 /* NotificationCenter.swift */,
 				38B4F3C725E502C000E76A18 /* SwiftNotificationCenter */,
 				38B4F3C725E502C000E76A18 /* SwiftNotificationCenter */,
@@ -2300,7 +2323,10 @@
 				582FAE422C05102C00D1C13F /* CoreDataError.swift */,
 				582FAE422C05102C00D1C13F /* CoreDataError.swift */,
 				BDF34EBD2C0A31D000D51995 /* CustomNotification.swift */,
 				BDF34EBD2C0A31D000D51995 /* CustomNotification.swift */,
 				BDCD47AE2C1F3F1700F8BCD5 /* OverrideStored+helper.swift */,
 				BDCD47AE2C1F3F1700F8BCD5 /* OverrideStored+helper.swift */,
+				BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */,
 				BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */,
 				BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */,
+				58A3D5432C96DE11003F90FC /* TempTargetStored+Helper.swift */,
+				BD793CB12CE8032E00D669AC /* TempTargetRunStored.swift */,
 			);
 			);
 			path = Helper;
 			path = Helper;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -2308,6 +2334,7 @@
 		58645B972CA2D16A008AFCE7 /* HomeStateModel+Setup */ = {
 		58645B972CA2D16A008AFCE7 /* HomeStateModel+Setup */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				BDA6CC872CAF219800F942F9 /* TempTargetSetup.swift */,
 				58645B982CA2D1A4008AFCE7 /* GlucoseSetup.swift */,
 				58645B982CA2D1A4008AFCE7 /* GlucoseSetup.swift */,
 				58645B9A2CA2D24F008AFCE7 /* CarbSetup.swift */,
 				58645B9A2CA2D24F008AFCE7 /* CarbSetup.swift */,
 				58645B9C2CA2D275008AFCE7 /* DeterminationSetup.swift */,
 				58645B9C2CA2D275008AFCE7 /* DeterminationSetup.swift */,
@@ -2422,15 +2449,32 @@
 		B9488883C59C31550E0B4CEC /* View */ = {
 		B9488883C59C31550E0B4CEC /* View */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				BDFD16592AE40438007F0DDA /* BolusRootView.swift */,
+				BDFD16592AE40438007F0DDA /* TreatmentsRootView.swift */,
 				58237D9D2BCF0A6B00A47A79 /* PopupView.swift */,
 				58237D9D2BCF0A6B00A47A79 /* PopupView.swift */,
 				BDB899872C564509006F3298 /* ForecastChart.swift */,
 				BDB899872C564509006F3298 /* ForecastChart.swift */,
-				BD0B2EF22C5998E600B3298F /* MealPresetView.swift */,
-				DDF847E52C5D66490049BB3B /* AddMealPresetView.swift */,
+				DD07CA5A2CE950B9002D45A9 /* MealPreset */,
 			);
 			);
 			path = View;
 			path = View;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
+		BD793CAD2CE7660C00D669AC /* Overrides */ = {
+			isa = PBXGroup;
+			children = (
+				DDD1631B2C4C697400CD525A /* AddOverrideForm.swift */,
+				DDD163192C4C695E00CD525A /* EditOverrideForm.swift */,
+			);
+			path = Overrides;
+			sourceTree = "<group>";
+		};
+		BD793CAE2CE7661D00D669AC /* TempTargets */ = {
+			isa = PBXGroup;
+			children = (
+				58A3D5392C96D4DE003F90FC /* AddTempTargetForm.swift */,
+				5825A1BD2C97335C0046467E /* EditTempTargetForm.swift */,
+			);
+			path = TempTargets;
+			sourceTree = "<group>";
+		};
 		BD7DA9A32AE06DBA00601B20 /* BolusCalculatorConfig */ = {
 		BD7DA9A32AE06DBA00601B20 /* BolusCalculatorConfig */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -2450,6 +2494,23 @@
 			path = View;
 			path = View;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
+		BDDAF9F12D0055CC00B34E7A /* ChartElements */ = {
+			isa = PBXGroup;
+			children = (
+				BDDAF9EE2D00553E00B34E7A /* SelectionPopoverView.swift */,
+				582DF9742C8CDB92001F516D /* GlucoseChartView.swift */,
+				582DF9762C8CDBE7001F516D /* InsulinView.swift */,
+				582DF97A2C8CE209001F516D /* CarbView.swift */,
+				58D08B212C8DAA8E00AA37D3 /* OverrideView.swift */,
+				58D08B2F2C8DEA7500AA37D3 /* ForecastView.swift */,
+				58D08B312C8DF88900AA37D3 /* DummyCharts.swift */,
+				58D08B332C8DF9A700AA37D3 /* CobIobChart.swift */,
+				58D08B372C8DFB6000AA37D3 /* BasalChart.swift */,
+				58D08B392C8DFECD00AA37D3 /* TempTargets.swift */,
+			);
+			path = ChartElements;
+			sourceTree = "<group>";
+		};
 		BDF34F882C10C65E00D51995 /* Data */ = {
 		BDF34F882C10C65E00D51995 /* Data */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -2461,15 +2522,15 @@
 			path = Data;
 			path = Data;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
-		C2C98283C436DB934D7E7994 /* Bolus */ = {
+		C2C98283C436DB934D7E7994 /* Treatments */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */,
-				C19984D62EFC0035A9E9644D /* BolusProvider.swift */,
-				223EC0494F55A91E3EA69EF4 /* BolusStateModel.swift */,
+				C8D1A7CA8C10C4403D4BBFA7 /* TreatmentsDataFlow.swift */,
+				C19984D62EFC0035A9E9644D /* TreatmentsProvider.swift */,
+				223EC0494F55A91E3EA69EF4 /* TreatmentsStateModel.swift */,
 				B9488883C59C31550E0B4CEC /* View */,
 				B9488883C59C31550E0B4CEC /* View */,
 			);
 			);
-			path = Bolus;
+			path = Treatments;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
 		CE1856F32ADC4835007E39C7 /* Carbs */ = {
 		CE1856F32ADC4835007E39C7 /* Carbs */ = {
@@ -2498,10 +2559,9 @@
 		CE7CA3452A064973004BE681 /* TempPresets */ = {
 		CE7CA3452A064973004BE681 /* TempPresets */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				CE7CA3462A064973004BE681 /* CancelTempPresetIntent.swift */,
 				CE7CA3472A064973004BE681 /* ApplyTempPresetIntent.swift */,
 				CE7CA3472A064973004BE681 /* ApplyTempPresetIntent.swift */,
-				CE7CA3482A064973004BE681 /* ListTempPresetsIntent.swift */,
-				CE7CA3492A064973004BE681 /* tempPresetIntent.swift */,
+				CE7CA3462A064973004BE681 /* CancelTempPresetIntent.swift */,
+				CE7CA3492A064973004BE681 /* TempPresetIntent.swift */,
 				CE7CA34A2A064973004BE681 /* TempPresetsIntentRequest.swift */,
 				CE7CA34A2A064973004BE681 /* TempPresetsIntentRequest.swift */,
 			);
 			);
 			path = TempPresets;
 			path = TempPresets;
@@ -2596,6 +2656,15 @@
 			path = ISFEditor;
 			path = ISFEditor;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
+		DD07CA5A2CE950B9002D45A9 /* MealPreset */ = {
+			isa = PBXGroup;
+			children = (
+				BD0B2EF22C5998E600B3298F /* MealPresetView.swift */,
+				DDF847E52C5D66490049BB3B /* AddMealPresetView.swift */,
+			);
+			path = MealPreset;
+			sourceTree = "<group>";
+		};
 		DD09D4792C5986BA003FEA5D /* CalendarEventSettings */ = {
 		DD09D4792C5986BA003FEA5D /* CalendarEventSettings */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -2723,6 +2792,25 @@
 			path = View;
 			path = View;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
+		DD5DC9EF2CF3D95400AB8703 /* AdjustmentsStateModel+Extensions */ = {
+			isa = PBXGroup;
+			children = (
+				DD5DC9FA2CF3E1AA00AB8703 /* AdjustmentsStateModel+Helpers.swift */,
+				DD5DC9F22CF3D9D600AB8703 /* AdjustmentsStateModel+TempTargets.swift */,
+				DD5DC9F02CF3D96E00AB8703 /* AdjustmentsStateModel+Overrides.swift */,
+			);
+			path = "AdjustmentsStateModel+Extensions";
+			sourceTree = "<group>";
+		};
+		DD5DC9F52CF3DA8900AB8703 /* ViewElements */ = {
+			isa = PBXGroup;
+			children = (
+				DD5DC9F82CF3DAA900AB8703 /* RadioButton.swift */,
+				DD5DC9F62CF3DA9300AB8703 /* TargetPicker.swift */,
+			);
+			path = ViewElements;
+			sourceTree = "<group>";
+		};
 		DD6B7CB72C7BAC1B00B75029 /* ProfileImport */ = {
 		DD6B7CB72C7BAC1B00B75029 /* ProfileImport */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -2765,23 +2853,34 @@
 			path = View;
 			path = View;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
-		DDD163032C4C67B400CD525A /* OverrideConfig */ = {
+		DDC9B9962CFD2332003E7721 /* Nightscout */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE9725C9D88300A708ED /* NightscoutManager.swift */,
+				38FE826C25CC8461001FF17A /* NightscoutAPI.swift */,
+			);
+			path = Nightscout;
+			sourceTree = "<group>";
+		};
+		DDD163032C4C67B400CD525A /* Adjustments */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				DD5DC9EF2CF3D95400AB8703 /* AdjustmentsStateModel+Extensions */,
 				DDD1630A2C4C67F000CD525A /* View */,
 				DDD1630A2C4C67F000CD525A /* View */,
-				DDD163112C4C689900CD525A /* OverrideStateModel.swift */,
-				DDD163132C4C68D300CD525A /* OverrideProvider.swift */,
-				DDD163152C4C690300CD525A /* OverrideDataFlow.swift */,
+				DDD163112C4C689900CD525A /* AdjustmentsStateModel.swift */,
+				DDD163132C4C68D300CD525A /* AdjustmentsProvider.swift */,
+				DDD163152C4C690300CD525A /* AdjustmentsDataFlow.swift */,
 			);
 			);
-			path = OverrideConfig;
+			path = Adjustments;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
 		DDD1630A2C4C67F000CD525A /* View */ = {
 		DDD1630A2C4C67F000CD525A /* View */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				DDD163172C4C694000CD525A /* OverrideRootView.swift */,
-				DDD163192C4C695E00CD525A /* EditOverrideForm.swift */,
-				DDD1631B2C4C697400CD525A /* AddOverrideForm.swift */,
+				DD5DC9F52CF3DA8900AB8703 /* ViewElements */,
+				DDD163172C4C694000CD525A /* AdjustmentsRootView.swift */,
+				BD793CAD2CE7660C00D669AC /* Overrides */,
+				BD793CAE2CE7661D00D669AC /* TempTargets */,
 			);
 			);
 			path = View;
 			path = View;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -2789,6 +2888,10 @@
 		DDE179112C9100FA003CDDB7 /* Classes+Properties */ = {
 		DDE179112C9100FA003CDDB7 /* Classes+Properties */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				58A3D54D2C96EFA8003F90FC /* TempTargetStored+CoreDataClass.swift */,
+				58A3D54E2C96EFA8003F90FC /* TempTargetStored+CoreDataProperties.swift */,
+				58A3D54F2C96EFA8003F90FC /* TempTargetRunStored+CoreDataClass.swift */,
+				58A3D5502C96EFA8003F90FC /* TempTargetRunStored+CoreDataProperties.swift */,
 				DDE179322C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift */,
 				DDE179322C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift */,
 				DDE179332C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift */,
 				DDE179332C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift */,
 				DDE179342C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift */,
 				DDE179342C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift */,
@@ -2799,8 +2902,6 @@
 				DDE179392C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift */,
 				DDE179392C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift */,
 				DDE1793A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift */,
 				DDE1793A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift */,
 				DDE1793B2C910127003CDDB7 /* CarbEntryStored+CoreDataProperties.swift */,
 				DDE1793B2C910127003CDDB7 /* CarbEntryStored+CoreDataProperties.swift */,
-				DDE1793C2C910127003CDDB7 /* TempTargets+CoreDataClass.swift */,
-				DDE1793D2C910127003CDDB7 /* TempTargets+CoreDataProperties.swift */,
 				DDE1793E2C910127003CDDB7 /* PumpEventStored+CoreDataClass.swift */,
 				DDE1793E2C910127003CDDB7 /* PumpEventStored+CoreDataClass.swift */,
 				DDE1793F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift */,
 				DDE1793F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift */,
 				DDE179402C910127003CDDB7 /* StatsData+CoreDataClass.swift */,
 				DDE179402C910127003CDDB7 /* StatsData+CoreDataClass.swift */,
@@ -2813,8 +2914,6 @@
 				DDE179472C910127003CDDB7 /* OpenAPS_Battery+CoreDataProperties.swift */,
 				DDE179472C910127003CDDB7 /* OpenAPS_Battery+CoreDataProperties.swift */,
 				DDE179482C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift */,
 				DDE179482C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift */,
 				DDE179492C910127003CDDB7 /* TempBasalStored+CoreDataProperties.swift */,
 				DDE179492C910127003CDDB7 /* TempBasalStored+CoreDataProperties.swift */,
-				DDE1794A2C910127003CDDB7 /* TempTargetsSlider+CoreDataClass.swift */,
-				DDE1794B2C910127003CDDB7 /* TempTargetsSlider+CoreDataProperties.swift */,
 				DDE1794C2C910127003CDDB7 /* OverrideRunStored+CoreDataClass.swift */,
 				DDE1794C2C910127003CDDB7 /* OverrideRunStored+CoreDataClass.swift */,
 				DDE1794D2C910127003CDDB7 /* OverrideRunStored+CoreDataProperties.swift */,
 				DDE1794D2C910127003CDDB7 /* OverrideRunStored+CoreDataProperties.swift */,
 				DDE1794E2C910127003CDDB7 /* OrefDetermination+CoreDataClass.swift */,
 				DDE1794E2C910127003CDDB7 /* OrefDetermination+CoreDataClass.swift */,
@@ -3256,13 +3355,16 @@
 			isa = PBXSourcesBuildPhase;
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
+				DD5DC9F12CF3D97C00AB8703 /* AdjustmentsStateModel+Overrides.swift in Sources */,
 				3811DE2325C9D48300A708ED /* MainDataFlow.swift in Sources */,
 				3811DE2325C9D48300A708ED /* MainDataFlow.swift in Sources */,
+				C2A0A42F2CE03131003B98E8 /* ConstantValues.swift in Sources */,
 				BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */,
 				BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */,
 				3811DEEB25CA063400A708ED /* PersistedProperty.swift in Sources */,
 				3811DEEB25CA063400A708ED /* PersistedProperty.swift in Sources */,
 				38E44537274E411700EC9A94 /* Disk+Helpers.swift in Sources */,
 				38E44537274E411700EC9A94 /* Disk+Helpers.swift in Sources */,
 				388E5A6025B6F2310019842D /* Autosens.swift in Sources */,
 				388E5A6025B6F2310019842D /* Autosens.swift in Sources */,
 				DD9ECB6A2CA99F6C00AA7C45 /* PushMessage.swift in Sources */,
 				DD9ECB6A2CA99F6C00AA7C45 /* PushMessage.swift in Sources */,
 				3811DE8F25C9D80400A708ED /* User.swift in Sources */,
 				3811DE8F25C9D80400A708ED /* User.swift in Sources */,
+				5825A1BE2C97335C0046467E /* EditTempTargetForm.swift in Sources */,
 				19D466A329AA2B80004D5F33 /* MealSettingsDataFlow.swift in Sources */,
 				19D466A329AA2B80004D5F33 /* MealSettingsDataFlow.swift in Sources */,
 				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
 				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
 				385CEAC425F2F154002D6D5B /* AnnouncementsStorage.swift in Sources */,
 				385CEAC425F2F154002D6D5B /* AnnouncementsStorage.swift in Sources */,
@@ -3275,6 +3377,7 @@
 				CE7CA3552A064973004BE681 /* ListStateIntent.swift in Sources */,
 				CE7CA3552A064973004BE681 /* ListStateIntent.swift in Sources */,
 				BDF530D82B40F8AC002CAF43 /* LockScreenView.swift in Sources */,
 				BDF530D82B40F8AC002CAF43 /* LockScreenView.swift in Sources */,
 				195D80B72AF697B800D25097 /* DynamicSettingsDataFlow.swift in Sources */,
 				195D80B72AF697B800D25097 /* DynamicSettingsDataFlow.swift in Sources */,
+				58A3D5512C96EFA8003F90FC /* TempTargetStored+CoreDataClass.swift in Sources */,
 				3862CC2E2743F9F700BF832C /* CalendarManager.swift in Sources */,
 				3862CC2E2743F9F700BF832C /* CalendarManager.swift in Sources */,
 				CEA4F62329BE10F70011ADF7 /* SavitzkyGolayFilter.swift in Sources */,
 				CEA4F62329BE10F70011ADF7 /* SavitzkyGolayFilter.swift in Sources */,
 				38B4F3C325E2A20B00E76A18 /* PumpSetupView.swift in Sources */,
 				38B4F3C325E2A20B00E76A18 /* PumpSetupView.swift in Sources */,
@@ -3285,6 +3388,7 @@
 				382C134B25F14E3700715CE1 /* BGTargets.swift in Sources */,
 				382C134B25F14E3700715CE1 /* BGTargets.swift in Sources */,
 				38AEE75725F0F18E0013F05B /* CarbsStorage.swift in Sources */,
 				38AEE75725F0F18E0013F05B /* CarbsStorage.swift in Sources */,
 				38B4F3CA25E502E200E76A18 /* SwiftNotificationCenter.swift in Sources */,
 				38B4F3CA25E502E200E76A18 /* SwiftNotificationCenter.swift in Sources */,
+				DD07CA142CE80B73002D45A9 /* TimeInRangeChartStyle.swift in Sources */,
 				38AEE75225F022080013F05B /* SettingsManager.swift in Sources */,
 				38AEE75225F022080013F05B /* SettingsManager.swift in Sources */,
 				3894873A2614928B004DF424 /* DispatchTimer.swift in Sources */,
 				3894873A2614928B004DF424 /* DispatchTimer.swift in Sources */,
 				3895E4C625B9E00D00214B37 /* Preferences.swift in Sources */,
 				3895E4C625B9E00D00214B37 /* Preferences.swift in Sources */,
@@ -3318,6 +3422,7 @@
 				DD1745552C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift in Sources */,
 				DD1745552C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift in Sources */,
 				384E803825C388640086DB71 /* Script.swift in Sources */,
 				384E803825C388640086DB71 /* Script.swift in Sources */,
 				CE94597E29E9E1EE0047C9C6 /* GarminManager.swift in Sources */,
 				CE94597E29E9E1EE0047C9C6 /* GarminManager.swift in Sources */,
+				58A3D5542C96EFA8003F90FC /* TempTargetRunStored+CoreDataProperties.swift in Sources */,
 				3883583425EEB38000E024B2 /* PumpSettings.swift in Sources */,
 				3883583425EEB38000E024B2 /* PumpSettings.swift in Sources */,
 				38DAB280260CBB7F00F74C1A /* PumpView.swift in Sources */,
 				38DAB280260CBB7F00F74C1A /* PumpView.swift in Sources */,
 				DDD1631C2C4C697400CD525A /* AddOverrideForm.swift in Sources */,
 				DDD1631C2C4C697400CD525A /* AddOverrideForm.swift in Sources */,
@@ -3326,6 +3431,7 @@
 				CE95BF572BA5F5FE00DC3DE3 /* PluginManager.swift in Sources */,
 				CE95BF572BA5F5FE00DC3DE3 /* PluginManager.swift in Sources */,
 				382C133725F13A1E00715CE1 /* InsulinSensitivities.swift in Sources */,
 				382C133725F13A1E00715CE1 /* InsulinSensitivities.swift in Sources */,
 				19D466A529AA2BD4004D5F33 /* MealSettingsProvider.swift in Sources */,
 				19D466A529AA2BD4004D5F33 /* MealSettingsProvider.swift in Sources */,
+				DD5DC9F72CF3DA9300AB8703 /* TargetPicker.swift in Sources */,
 				383948D625CD4D8900E91849 /* FileStorage.swift in Sources */,
 				383948D625CD4D8900E91849 /* FileStorage.swift in Sources */,
 				CEE9A6572BBB418300EB5194 /* CalibrationsChart.swift in Sources */,
 				CEE9A6572BBB418300EB5194 /* CalibrationsChart.swift in Sources */,
 				3811DE4125C9D4A100A708ED /* SettingsRootView.swift in Sources */,
 				3811DE4125C9D4A100A708ED /* SettingsRootView.swift in Sources */,
@@ -3356,6 +3462,7 @@
 				389A572026079BAA00BC102F /* Interpolation.swift in Sources */,
 				389A572026079BAA00BC102F /* Interpolation.swift in Sources */,
 				DD9ECB702CA9A0BA00AA7C45 /* RemoteControlConfigStateModel.swift in Sources */,
 				DD9ECB702CA9A0BA00AA7C45 /* RemoteControlConfigStateModel.swift in Sources */,
 				19A910382A24EF3200C8951B /* ChartsView.swift in Sources */,
 				19A910382A24EF3200C8951B /* ChartsView.swift in Sources */,
+				58A3D5522C96EFA8003F90FC /* TempTargetStored+CoreDataProperties.swift in Sources */,
 				DD32CF9A2CC8247B003686D6 /* TrioRemoteControl+Meal.swift in Sources */,
 				DD32CF9A2CC8247B003686D6 /* TrioRemoteControl+Meal.swift in Sources */,
 				BDF34F832C10C5B600D51995 /* DataManager.swift in Sources */,
 				BDF34F832C10C5B600D51995 /* DataManager.swift in Sources */,
 				38B4F3C625E5017E00E76A18 /* NotificationCenter.swift in Sources */,
 				38B4F3C625E5017E00E76A18 /* NotificationCenter.swift in Sources */,
@@ -3382,6 +3489,7 @@
 				DD1745482C55C61D00211FAC /* AutosensSettingsStateModel.swift in Sources */,
 				DD1745482C55C61D00211FAC /* AutosensSettingsStateModel.swift in Sources */,
 				DD1745462C55C61500211FAC /* AutosensSettingsProvider.swift in Sources */,
 				DD1745462C55C61500211FAC /* AutosensSettingsProvider.swift in Sources */,
 				3811DEAF25C9D88300A708ED /* KeyValueStorage.swift in Sources */,
 				3811DEAF25C9D88300A708ED /* KeyValueStorage.swift in Sources */,
+				DDD6D4D32CDE90720029439A /* HbA1cDisplayUnit.swift in Sources */,
 				38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */,
 				38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */,
 				388358C825EEF6D200E024B2 /* BasalProfileEntry.swift in Sources */,
 				388358C825EEF6D200E024B2 /* BasalProfileEntry.swift in Sources */,
 				3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */,
 				3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */,
@@ -3399,6 +3507,7 @@
 				BD0B2EF32C5998E600B3298F /* MealPresetView.swift in Sources */,
 				BD0B2EF32C5998E600B3298F /* MealPresetView.swift in Sources */,
 				DD6D67E42C9C253500660C9B /* ColorSchemeOption.swift in Sources */,
 				DD6D67E42C9C253500660C9B /* ColorSchemeOption.swift in Sources */,
 				582DF9752C8CDB92001F516D /* GlucoseChartView.swift in Sources */,
 				582DF9752C8CDB92001F516D /* GlucoseChartView.swift in Sources */,
+				58A3D53A2C96D4DE003F90FC /* AddTempTargetForm.swift in Sources */,
 				DD1745302C55AE5300211FAC /* TargetBehaviorProvider.swift in Sources */,
 				DD1745302C55AE5300211FAC /* TargetBehaviorProvider.swift in Sources */,
 				58D08B382C8DFB6000AA37D3 /* BasalChart.swift in Sources */,
 				58D08B382C8DFB6000AA37D3 /* BasalChart.swift in Sources */,
 				118DF76D2C5ECBC60067FEB7 /* OverridePresetEntity.swift in Sources */,
 				118DF76D2C5ECBC60067FEB7 /* OverridePresetEntity.swift in Sources */,
@@ -3406,6 +3515,7 @@
 				DD17454E2C55CA4D00211FAC /* UnitsLimitsSettingsDataFlow.swift in Sources */,
 				DD17454E2C55CA4D00211FAC /* UnitsLimitsSettingsDataFlow.swift in Sources */,
 				DDF847E62C5D66490049BB3B /* AddMealPresetView.swift in Sources */,
 				DDF847E62C5D66490049BB3B /* AddMealPresetView.swift in Sources */,
 				3811DEAE25C9D88300A708ED /* Cache.swift in Sources */,
 				3811DEAE25C9D88300A708ED /* Cache.swift in Sources */,
+				BDA6CC882CAF219B00F942F9 /* TempTargetSetup.swift in Sources */,
 				383420D625FFE38C002D46C1 /* LoopView.swift in Sources */,
 				383420D625FFE38C002D46C1 /* LoopView.swift in Sources */,
 				DD1745192C543B5700211FAC /* NotificationsView.swift in Sources */,
 				DD1745192C543B5700211FAC /* NotificationsView.swift in Sources */,
 				3811DEAD25C9D88300A708ED /* UserDefaults+Cache.swift in Sources */,
 				3811DEAD25C9D88300A708ED /* UserDefaults+Cache.swift in Sources */,
@@ -3423,10 +3533,10 @@
 				CEE9A6592BBB418300EB5194 /* CalibrationsDataFlow.swift in Sources */,
 				CEE9A6592BBB418300EB5194 /* CalibrationsDataFlow.swift in Sources */,
 				3811DE3525C9D49500A708ED /* HomeRootView.swift in Sources */,
 				3811DE3525C9D49500A708ED /* HomeRootView.swift in Sources */,
 				38E98A2925F52C9300C0CED0 /* Error+Extensions.swift in Sources */,
 				38E98A2925F52C9300C0CED0 /* Error+Extensions.swift in Sources */,
-				118DF76C2C5ECBC60067FEB7 /* ListOverridePresetIntent.swift in Sources */,
 				38EA05DA261F6E7C0064E39B /* SimpleLogReporter.swift in Sources */,
 				38EA05DA261F6E7C0064E39B /* SimpleLogReporter.swift in Sources */,
 				3811DE6125C9D4D500A708ED /* ViewModifiers.swift in Sources */,
 				3811DE6125C9D4D500A708ED /* ViewModifiers.swift in Sources */,
 				3811DEAC25C9D88300A708ED /* NightscoutManager.swift in Sources */,
 				3811DEAC25C9D88300A708ED /* NightscoutManager.swift in Sources */,
+				BD793CB22CE8033500D669AC /* TempTargetRunStored.swift in Sources */,
 				19A910302A24BF6300C8951B /* StatsView.swift in Sources */,
 				19A910302A24BF6300C8951B /* StatsView.swift in Sources */,
 				BD7DA9A92AE06E9200601B20 /* BolusCalculatorStateModel.swift in Sources */,
 				BD7DA9A92AE06E9200601B20 /* BolusCalculatorStateModel.swift in Sources */,
 				CEB434E528B8FF5D00B70274 /* UIColor.swift in Sources */,
 				CEB434E528B8FF5D00B70274 /* UIColor.swift in Sources */,
@@ -3434,11 +3544,12 @@
 				3811DEA925C9D88300A708ED /* AppearanceManager.swift in Sources */,
 				3811DEA925C9D88300A708ED /* AppearanceManager.swift in Sources */,
 				CE7950242997D81700FA576E /* CGMSettingsView.swift in Sources */,
 				CE7950242997D81700FA576E /* CGMSettingsView.swift in Sources */,
 				58237D9E2BCF0A6B00A47A79 /* PopupView.swift in Sources */,
 				58237D9E2BCF0A6B00A47A79 /* PopupView.swift in Sources */,
+				BD793CB02CE7C61500D669AC /* OverrideRunStored+helper.swift in Sources */,
 				38D0B3D925EC07C400CB6E88 /* CarbsEntry.swift in Sources */,
 				38D0B3D925EC07C400CB6E88 /* CarbsEntry.swift in Sources */,
 				DD32CF9C2CC82499003686D6 /* TrioRemoteControl+TempTarget.swift in Sources */,
 				DD32CF9C2CC82499003686D6 /* TrioRemoteControl+TempTarget.swift in Sources */,
 				38A9260525F012D8009E3739 /* CarbRatios.swift in Sources */,
 				38A9260525F012D8009E3739 /* CarbRatios.swift in Sources */,
 				38FCF3D625E8FDF40078B0D1 /* MD5.swift in Sources */,
 				38FCF3D625E8FDF40078B0D1 /* MD5.swift in Sources */,
-				DDD163142C4C68D300CD525A /* OverrideProvider.swift in Sources */,
+				DDD163142C4C68D300CD525A /* AdjustmentsProvider.swift in Sources */,
 				3871F39C25ED892B0013ECB5 /* TempTarget.swift in Sources */,
 				3871F39C25ED892B0013ECB5 /* TempTarget.swift in Sources */,
 				191F62682AD6B05A004D7911 /* NightscoutSettings.swift in Sources */,
 				191F62682AD6B05A004D7911 /* NightscoutSettings.swift in Sources */,
 				3811DEAB25C9D88300A708ED /* HTTPResponseStatus.swift in Sources */,
 				3811DEAB25C9D88300A708ED /* HTTPResponseStatus.swift in Sources */,
@@ -3471,11 +3582,10 @@
 				BDCD47AF2C1F3F1700F8BCD5 /* OverrideStored+helper.swift in Sources */,
 				BDCD47AF2C1F3F1700F8BCD5 /* OverrideStored+helper.swift in Sources */,
 				3811DE3F25C9D4A100A708ED /* SettingsStateModel.swift in Sources */,
 				3811DE3F25C9D4A100A708ED /* SettingsStateModel.swift in Sources */,
 				CE7CA3582A064E2F004BE681 /* ListStateView.swift in Sources */,
 				CE7CA3582A064E2F004BE681 /* ListStateView.swift in Sources */,
-				58D08B362C8DFAC600AA37D3 /* IobChart.swift in Sources */,
 				193F6CDD2A512C8F001240FD /* Loops.swift in Sources */,
 				193F6CDD2A512C8F001240FD /* Loops.swift in Sources */,
 				38B4F3CB25E502E200E76A18 /* WeakObjectSet.swift in Sources */,
 				38B4F3CB25E502E200E76A18 /* WeakObjectSet.swift in Sources */,
 				38E989DD25F5021400C0CED0 /* PumpStatus.swift in Sources */,
 				38E989DD25F5021400C0CED0 /* PumpStatus.swift in Sources */,
-				BDFD165A2AE40438007F0DDA /* BolusRootView.swift in Sources */,
+				BDFD165A2AE40438007F0DDA /* TreatmentsRootView.swift in Sources */,
 				38E98A2525F52C9300C0CED0 /* IssueReporter.swift in Sources */,
 				38E98A2525F52C9300C0CED0 /* IssueReporter.swift in Sources */,
 				DD1745522C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift in Sources */,
 				DD1745522C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift in Sources */,
 				190EBCC429FF136900BA767D /* UserInterfaceSettingsDataFlow.swift in Sources */,
 				190EBCC429FF136900BA767D /* UserInterfaceSettingsDataFlow.swift in Sources */,
@@ -3497,7 +3607,7 @@
 				E39E418C56A5A46B61D960EE /* ConfigEditorStateModel.swift in Sources */,
 				E39E418C56A5A46B61D960EE /* ConfigEditorStateModel.swift in Sources */,
 				45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */,
 				45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */,
 				DD32CFA02CC824D6003686D6 /* TrioRemoteControl+APNS.swift in Sources */,
 				DD32CFA02CC824D6003686D6 /* TrioRemoteControl+APNS.swift in Sources */,
-				CE7CA3532A064973004BE681 /* tempPresetIntent.swift in Sources */,
+				CE7CA3532A064973004BE681 /* TempPresetIntent.swift in Sources */,
 				D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */,
 				D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */,
 				DD6B7CBB2C7FBBFA00B75029 /* ReviewInsulinActionView.swift in Sources */,
 				DD6B7CBB2C7FBBFA00B75029 /* ReviewInsulinActionView.swift in Sources */,
 				38E98A3025F52FF700C0CED0 /* Config.swift in Sources */,
 				38E98A3025F52FF700C0CED0 /* Config.swift in Sources */,
@@ -3513,7 +3623,7 @@
 				38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */,
 				38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */,
 				DD1745442C55C60E00211FAC /* AutosensSettingsDataFlow.swift in Sources */,
 				DD1745442C55C60E00211FAC /* AutosensSettingsDataFlow.swift in Sources */,
 				BDCAF2382C639F35002DC907 /* SettingItems.swift in Sources */,
 				BDCAF2382C639F35002DC907 /* SettingItems.swift in Sources */,
-				58D08B342C8DF9A700AA37D3 /* CobChart.swift in Sources */,
+				58D08B342C8DF9A700AA37D3 /* CobIobChart.swift in Sources */,
 				642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */,
 				642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */,
 				BD7DA9AC2AE06EB900601B20 /* BolusCalculatorConfigRootView.swift in Sources */,
 				BD7DA9AC2AE06EB900601B20 /* BolusCalculatorConfigRootView.swift in Sources */,
 				AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */,
 				AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */,
@@ -3524,7 +3634,6 @@
 				E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */,
 				E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */,
 				DD1745502C55CA5500211FAC /* UnitsLimitsSettingsProvider.swift in Sources */,
 				DD1745502C55CA5500211FAC /* UnitsLimitsSettingsProvider.swift in Sources */,
 				581AC4392BE22ED10038760C /* JSONConverter.swift in Sources */,
 				581AC4392BE22ED10038760C /* JSONConverter.swift in Sources */,
-				CE7CA3522A064973004BE681 /* ListTempPresetsIntent.swift in Sources */,
 				BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */,
 				BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */,
 				58645B9B2CA2D24F008AFCE7 /* CarbSetup.swift in Sources */,
 				58645B9B2CA2D24F008AFCE7 /* CarbSetup.swift in Sources */,
 				38E44536274E411700EC9A94 /* Disk.swift in Sources */,
 				38E44536274E411700EC9A94 /* Disk.swift in Sources */,
@@ -3593,7 +3702,10 @@
 				58D08B3A2C8DFECD00AA37D3 /* TempTargets.swift in Sources */,
 				58D08B3A2C8DFECD00AA37D3 /* TempTargets.swift in Sources */,
 				38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */,
 				38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */,
 				CE7CA3542A064973004BE681 /* TempPresetsIntentRequest.swift in Sources */,
 				CE7CA3542A064973004BE681 /* TempPresetsIntentRequest.swift in Sources */,
+				58A3D5442C96DE11003F90FC /* TempTargetStored+Helper.swift in Sources */,
+				58A3D5532C96EFA8003F90FC /* TempTargetRunStored+CoreDataClass.swift in Sources */,
 				DD6B7CB42C7B71F700B75029 /* ForecastDisplayType.swift in Sources */,
 				DD6B7CB42C7B71F700B75029 /* ForecastDisplayType.swift in Sources */,
+				DD5DC9F32CF3D9DD00AB8703 /* AdjustmentsStateModel+TempTargets.swift in Sources */,
 				F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */,
 				F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */,
 				DD17453A2C55BFA600211FAC /* AlgorithmAdvancedSettingsDataFlow.swift in Sources */,
 				DD17453A2C55BFA600211FAC /* AlgorithmAdvancedSettingsDataFlow.swift in Sources */,
 				5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */,
 				5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */,
@@ -3601,7 +3713,7 @@
 				9702FF92A09C53942F20D7EA /* TargetsEditorRootView.swift in Sources */,
 				9702FF92A09C53942F20D7EA /* TargetsEditorRootView.swift in Sources */,
 				1967DFBE29D052C200759F30 /* Icons.swift in Sources */,
 				1967DFBE29D052C200759F30 /* Icons.swift in Sources */,
 				38E8754F275556FA00975559 /* WatchManager.swift in Sources */,
 				38E8754F275556FA00975559 /* WatchManager.swift in Sources */,
-				DDD163182C4C694000CD525A /* OverrideRootView.swift in Sources */,
+				DDD163182C4C694000CD525A /* AdjustmentsRootView.swift in Sources */,
 				389ECE052601144100D86C4F /* ConcurrentMap.swift in Sources */,
 				389ECE052601144100D86C4F /* ConcurrentMap.swift in Sources */,
 				110AEDEC2C51A0AE00615CC9 /* ShortcutsConfigDataFlow.swift in Sources */,
 				110AEDEC2C51A0AE00615CC9 /* ShortcutsConfigDataFlow.swift in Sources */,
 				CE7CA3562A064973004BE681 /* StateIntentRequest.swift in Sources */,
 				CE7CA3562A064973004BE681 /* StateIntentRequest.swift in Sources */,
@@ -3609,24 +3721,25 @@
 				DD09D4822C5986F6003FEA5D /* CalendarEventSettingsRootView.swift in Sources */,
 				DD09D4822C5986F6003FEA5D /* CalendarEventSettingsRootView.swift in Sources */,
 				CE48C86428CA69D5007C0598 /* OmniBLEPumpManagerExtensions.swift in Sources */,
 				CE48C86428CA69D5007C0598 /* OmniBLEPumpManagerExtensions.swift in Sources */,
 				38E8755427561E9800975559 /* DataFlow.swift in Sources */,
 				38E8755427561E9800975559 /* DataFlow.swift in Sources */,
+				DD5DC9F92CF3DAA900AB8703 /* RadioButton.swift in Sources */,
 				38E44522274E3DDC00EC9A94 /* NetworkReachabilityManager.swift in Sources */,
 				38E44522274E3DDC00EC9A94 /* NetworkReachabilityManager.swift in Sources */,
 				CE7CA34F2A064973004BE681 /* BaseIntentsRequest.swift in Sources */,
 				CE7CA34F2A064973004BE681 /* BaseIntentsRequest.swift in Sources */,
 				CE82E02728E869DF00473A9C /* AlertEntry.swift in Sources */,
 				CE82E02728E869DF00473A9C /* AlertEntry.swift in Sources */,
 				38E4451E274DB04600EC9A94 /* AppDelegate.swift in Sources */,
 				38E4451E274DB04600EC9A94 /* AppDelegate.swift in Sources */,
 				BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */,
 				BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */,
-				DDD163162C4C690300CD525A /* OverrideDataFlow.swift in Sources */,
+				DDD163162C4C690300CD525A /* AdjustmentsDataFlow.swift in Sources */,
 				BDF34F932C10D0E100D51995 /* LiveActivityAttributes+Helper.swift in Sources */,
 				BDF34F932C10D0E100D51995 /* LiveActivityAttributes+Helper.swift in Sources */,
 				E0D4F80527513ECF00BDF1FE /* HealthKitSample.swift in Sources */,
 				E0D4F80527513ECF00BDF1FE /* HealthKitSample.swift in Sources */,
 				38A00B1F25FC00F7006BC0B0 /* Autotune.swift in Sources */,
 				38A00B1F25FC00F7006BC0B0 /* Autotune.swift in Sources */,
 				38AAF85525FFF846004AF583 /* CurrentGlucoseView.swift in Sources */,
 				38AAF85525FFF846004AF583 /* CurrentGlucoseView.swift in Sources */,
-				041D1E995A6AE92E9289DC49 /* BolusDataFlow.swift in Sources */,
+				041D1E995A6AE92E9289DC49 /* TreatmentsDataFlow.swift in Sources */,
 				DD32CF9E2CC824C5003686D6 /* TrioRemoteControl+Override.swift in Sources */,
 				DD32CF9E2CC824C5003686D6 /* TrioRemoteControl+Override.swift in Sources */,
-				23888883D4EA091C88480FF2 /* BolusProvider.swift in Sources */,
+				23888883D4EA091C88480FF2 /* TreatmentsProvider.swift in Sources */,
 				38E98A2D25F52DC400C0CED0 /* NSLocking+Extensions.swift in Sources */,
 				38E98A2D25F52DC400C0CED0 /* NSLocking+Extensions.swift in Sources */,
 				BDBAACFA2C2D439700370AAE /* OverrideData.swift in Sources */,
 				BDBAACFA2C2D439700370AAE /* OverrideData.swift in Sources */,
 				DD9ECB682CA99F4500AA7C45 /* TrioRemoteControl.swift in Sources */,
 				DD9ECB682CA99F4500AA7C45 /* TrioRemoteControl.swift in Sources */,
 				38569353270B5E350002C50D /* CGMRootView.swift in Sources */,
 				38569353270B5E350002C50D /* CGMRootView.swift in Sources */,
-				69A31254F2451C20361D172F /* BolusStateModel.swift in Sources */,
+				69A31254F2451C20361D172F /* TreatmentsStateModel.swift in Sources */,
 				1967DFC029D053AC00759F30 /* IconSelection.swift in Sources */,
 				1967DFC029D053AC00759F30 /* IconSelection.swift in Sources */,
 				19D4E4EB29FC6A9F00351451 /* Charts.swift in Sources */,
 				19D4E4EB29FC6A9F00351451 /* Charts.swift in Sources */,
 				FEFFA7A22929FE49007B8193 /* UIDevice+Extensions.swift in Sources */,
 				FEFFA7A22929FE49007B8193 /* UIDevice+Extensions.swift in Sources */,
@@ -3646,7 +3759,7 @@
 				7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */,
 				7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */,
 				38E44534274E411700EC9A94 /* Disk+InternalHelpers.swift in Sources */,
 				38E44534274E411700EC9A94 /* Disk+InternalHelpers.swift in Sources */,
 				38A00B2325FC2B55006BC0B0 /* LRUCache.swift in Sources */,
 				38A00B2325FC2B55006BC0B0 /* LRUCache.swift in Sources */,
-				DDD163122C4C689900CD525A /* OverrideStateModel.swift in Sources */,
+				DDD163122C4C689900CD525A /* AdjustmentsStateModel.swift in Sources */,
 				3083261C4B268E353F36CD0B /* AutotuneConfigDataFlow.swift in Sources */,
 				3083261C4B268E353F36CD0B /* AutotuneConfigDataFlow.swift in Sources */,
 				DD1745132C54169400211FAC /* DevicesView.swift in Sources */,
 				DD1745132C54169400211FAC /* DevicesView.swift in Sources */,
 				891DECF7BC20968D7F566161 /* AutotuneConfigProvider.swift in Sources */,
 				891DECF7BC20968D7F566161 /* AutotuneConfigProvider.swift in Sources */,
@@ -3671,9 +3784,11 @@
 				BD7DA9A52AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift in Sources */,
 				BD7DA9A52AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift in Sources */,
 				6EADD581738D64431902AC0A /* (null) in Sources */,
 				6EADD581738D64431902AC0A /* (null) in Sources */,
 				CE94598729E9E4110047C9C6 /* WatchConfigRootView.swift in Sources */,
 				CE94598729E9E4110047C9C6 /* WatchConfigRootView.swift in Sources */,
+				DD5DC9FB2CF3E1B100AB8703 /* AdjustmentsStateModel+Helpers.swift in Sources */,
 				DDF847E42C5C288F0049BB3B /* LiveActivitySettingsRootView.swift in Sources */,
 				DDF847E42C5C288F0049BB3B /* LiveActivitySettingsRootView.swift in Sources */,
 				DD88C8E22C50420800F2D558 /* DefinitionRow.swift in Sources */,
 				DD88C8E22C50420800F2D558 /* DefinitionRow.swift in Sources */,
 				B7C465E9472624D8A2BE2A6A /* (null) in Sources */,
 				B7C465E9472624D8A2BE2A6A /* (null) in Sources */,
+				71D44AAB2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift in Sources */,
 				320D030F724170A637F06D50 /* (null) in Sources */,
 				320D030F724170A637F06D50 /* (null) in Sources */,
 				CE94598729E9E4110047C9C6 /* WatchConfigRootView.swift in Sources */,
 				CE94598729E9E4110047C9C6 /* WatchConfigRootView.swift in Sources */,
 				19E1F7E829D082D0005C8D20 /* IconConfigDataFlow.swift in Sources */,
 				19E1F7E829D082D0005C8D20 /* IconConfigDataFlow.swift in Sources */,
@@ -3691,9 +3806,8 @@
 				DDE179592C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift in Sources */,
 				DDE179592C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift in Sources */,
 				DDE1795A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift in Sources */,
 				DDE1795A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift in Sources */,
 				DDE1795B2C910127003CDDB7 /* CarbEntryStored+CoreDataProperties.swift in Sources */,
 				DDE1795B2C910127003CDDB7 /* CarbEntryStored+CoreDataProperties.swift in Sources */,
-				DDE1795C2C910127003CDDB7 /* TempTargets+CoreDataClass.swift in Sources */,
-				DDE1795D2C910127003CDDB7 /* TempTargets+CoreDataProperties.swift in Sources */,
 				DDE1795E2C910127003CDDB7 /* PumpEventStored+CoreDataClass.swift in Sources */,
 				DDE1795E2C910127003CDDB7 /* PumpEventStored+CoreDataClass.swift in Sources */,
+				BDDAF9EF2D00554500B34E7A /* SelectionPopoverView.swift in Sources */,
 				DDE1795F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift in Sources */,
 				DDE1795F2C910127003CDDB7 /* PumpEventStored+CoreDataProperties.swift in Sources */,
 				DDE179602C910127003CDDB7 /* StatsData+CoreDataClass.swift in Sources */,
 				DDE179602C910127003CDDB7 /* StatsData+CoreDataClass.swift in Sources */,
 				DDE179612C910127003CDDB7 /* StatsData+CoreDataProperties.swift in Sources */,
 				DDE179612C910127003CDDB7 /* StatsData+CoreDataProperties.swift in Sources */,
@@ -3705,8 +3819,6 @@
 				DDE179672C910127003CDDB7 /* OpenAPS_Battery+CoreDataProperties.swift in Sources */,
 				DDE179672C910127003CDDB7 /* OpenAPS_Battery+CoreDataProperties.swift in Sources */,
 				DDE179682C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift in Sources */,
 				DDE179682C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift in Sources */,
 				DDE179692C910127003CDDB7 /* TempBasalStored+CoreDataProperties.swift in Sources */,
 				DDE179692C910127003CDDB7 /* TempBasalStored+CoreDataProperties.swift in Sources */,
-				DDE1796A2C910127003CDDB7 /* TempTargetsSlider+CoreDataClass.swift in Sources */,
-				DDE1796B2C910127003CDDB7 /* TempTargetsSlider+CoreDataProperties.swift in Sources */,
 				DDE1796C2C910127003CDDB7 /* OverrideRunStored+CoreDataClass.swift in Sources */,
 				DDE1796C2C910127003CDDB7 /* OverrideRunStored+CoreDataClass.swift in Sources */,
 				DDE1796D2C910127003CDDB7 /* OverrideRunStored+CoreDataProperties.swift in Sources */,
 				DDE1796D2C910127003CDDB7 /* OverrideRunStored+CoreDataProperties.swift in Sources */,
 				DDE1796E2C910127003CDDB7 /* OrefDetermination+CoreDataClass.swift in Sources */,
 				DDE1796E2C910127003CDDB7 /* OrefDetermination+CoreDataClass.swift in Sources */,
@@ -3718,6 +3830,7 @@
 				CE7CA3502A064973004BE681 /* CancelTempPresetIntent.swift in Sources */,
 				CE7CA3502A064973004BE681 /* CancelTempPresetIntent.swift in Sources */,
 				6B1F539F9FF75646D1606066 /* SnoozeDataFlow.swift in Sources */,
 				6B1F539F9FF75646D1606066 /* SnoozeDataFlow.swift in Sources */,
 				6FFAE524D1D9C262F2407CAE /* SnoozeProvider.swift in Sources */,
 				6FFAE524D1D9C262F2407CAE /* SnoozeProvider.swift in Sources */,
+				BD4ED4FD2CF9D5E8000EDC9C /* AppState.swift in Sources */,
 				DDF847DD2C5C28720049BB3B /* LiveActivitySettingsDataFlow.swift in Sources */,
 				DDF847DD2C5C28720049BB3B /* LiveActivitySettingsDataFlow.swift in Sources */,
 				8194B80890CDD6A3C13B0FEE /* SnoozeStateModel.swift in Sources */,
 				8194B80890CDD6A3C13B0FEE /* SnoozeStateModel.swift in Sources */,
 				0437CE46C12535A56504EC19 /* SnoozeRootView.swift in Sources */,
 				0437CE46C12535A56504EC19 /* SnoozeRootView.swift in Sources */,

+ 38 - 0
FreeAPS/Resources/Assets.xcassets/Colors/ApnBackground.colorset/Contents.json

@@ -0,0 +1,38 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.929",
+          "green" : "0.926",
+          "red" : "0.918"
+        }
+      },
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "0.950",
+          "blue" : "0.208",
+          "green" : "0.145",
+          "red" : "0.063"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 20 - 0
FreeAPS/Resources/Assets.xcassets/Colors/ApnBackgroundLightDark.colorset/Contents.json

@@ -0,0 +1,20 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "0.950",
+          "blue" : "0.639",
+          "green" : "0.572",
+          "red" : "0.478"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 2 - 2
FreeAPS/Resources/Info.plist

@@ -2,8 +2,6 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <plist version="1.0">
 <dict>
 <dict>
-	<key>TeamID</key>
-	<string>$(DEVELOPER_TEAM)</string>
 	<key>AppGroupID</key>
 	<key>AppGroupID</key>
 	<string>$(APP_GROUP_ID)</string>
 	<string>$(APP_GROUP_ID)</string>
 	<key>BGTaskSchedulerPermittedIdentifiers</key>
 	<key>BGTaskSchedulerPermittedIdentifiers</key>
@@ -88,6 +86,8 @@
 	<string>$(COPYRIGHT_NOTICE)</string>
 	<string>$(COPYRIGHT_NOTICE)</string>
 	<key>NSSupportsLiveActivities</key>
 	<key>NSSupportsLiveActivities</key>
 	<true/>
 	<true/>
+	<key>TeamID</key>
+	<string>$(DEVELOPMENT_TEAM)</string>
 	<key>UIApplicationSceneManifest</key>
 	<key>UIApplicationSceneManifest</key>
 	<dict>
 	<dict>
 		<key>UIApplicationSupportsMultipleScenes</key>
 		<key>UIApplicationSupportsMultipleScenes</key>

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
FreeAPS/Resources/javascript/bundle/autosens.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
FreeAPS/Resources/javascript/bundle/autotune-prep.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
FreeAPS/Resources/javascript/bundle/determine-basal.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
FreeAPS/Resources/javascript/bundle/iob.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
FreeAPS/Resources/javascript/bundle/meal.js


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

@@ -33,14 +33,14 @@
   "useAppleHealth" : false,
   "useAppleHealth" : false,
   "smoothGlucose" : false,
   "smoothGlucose" : false,
   "displayOnWatch" : "BGTarget",
   "displayOnWatch" : "BGTarget",
-  "overrideHbA1cUnit" : false,
+  "hbA1cDisplayUnit" : "percent",
   "high" : 180,
   "high" : 180,
   "low" : 70,
   "low" : 70,
   "hours" : 6,
   "hours" : 6,
   "glucoseColorScheme" : "staticColor",
   "glucoseColorScheme" : "staticColor",
   "xGridLines" : true,
   "xGridLines" : true,
   "yGridLines" : true,
   "yGridLines" : true,
-  "oneDimensionalGraph" : false,
+  "timeInRangeChartStyle" : "vertical",
   "rulerMarks" : true,
   "rulerMarks" : true,
   "forecastDisplayType": "cone",
   "forecastDisplayType": "cone",
   "maxCarbs": 250,
   "maxCarbs": 250,
@@ -52,7 +52,7 @@
   "fattyMeals": false,
   "fattyMeals": false,
   "fattyMealFactor": 0.7,
   "fattyMealFactor": 0.7,
   "sweetMeals": false,
   "sweetMeals": false,
-  "sweetMealFactor": 2,
+  "sweetMealFactor": 1,
   "lockScreenView": "simple",
   "lockScreenView": "simple",
   "useCalendar": false,
   "useCalendar": false,
   "displayCalendarIOBandCOB": false,
   "displayCalendarIOBandCOB": false,

+ 1 - 1
FreeAPS/Resources/json/defaults/preferences.json

@@ -45,7 +45,7 @@
   "enableDynamicCR" : false,
   "enableDynamicCR" : false,
   "useNewFormula" : false,
   "useNewFormula" : false,
   "useWeightedAverage" : false,
   "useWeightedAverage" : false,
-  "weightPercentage" : 0.65,
+  "weightPercentage" : 0.35,
   "tddAdjBasal" : false,
   "tddAdjBasal" : false,
   "enableSMB_high_bg" : false,
   "enableSMB_high_bg" : false,
   "enableSMB_high_bg_target" : 110,
   "enableSMB_high_bg_target" : 110,

+ 1 - 1
FreeAPS/Resources/json/defaults/settings/settings.json

@@ -1,5 +1,5 @@
 {
 {
-    "insulin_action_curve": 6,
+    "insulin_action_curve": 10,
     "maxBolus": 10,
     "maxBolus": 10,
     "maxBasal": 2
     "maxBasal": 2
 }
 }

+ 18 - 19
FreeAPS/Sources/APS/APSManager.swift

@@ -207,7 +207,7 @@ final class BaseAPSManager: APSManager, Injectable {
             backGroundTaskID = await UIApplication.shared.beginBackgroundTask(withName: "Loop starting") {
             backGroundTaskID = await UIApplication.shared.beginBackgroundTask(withName: "Loop starting") {
                 guard let backgroundTask = self.backGroundTaskID else { return }
                 guard let backgroundTask = self.backGroundTaskID else { return }
                 Task {
                 Task {
-                    await UIApplication.shared.endBackgroundTask(backgroundTask)
+                    UIApplication.shared.endBackgroundTask(backgroundTask)
                 }
                 }
                 self.backGroundTaskID = .invalid
                 self.backGroundTaskID = .invalid
             }
             }
@@ -391,7 +391,7 @@ final class BaseAPSManager: APSManager, Injectable {
             let now = Date()
             let now = Date()
 
 
             // Start fetching asynchronously
             // Start fetching asynchronously
-            let (currentTemp, profiles, autosense, dailyAutotune) = try await (
+            let (currentTemp, _, _, _) = try await (
                 fetchCurrentTempBasal(date: now),
                 fetchCurrentTempBasal(date: now),
                 makeProfiles(),
                 makeProfiles(),
                 autosense(),
                 autosense(),
@@ -1043,10 +1043,8 @@ final class BaseAPSManager: APSManager, Injectable {
                 scheduled_basal: 0,
                 scheduled_basal: 0,
                 total_average: 0
                 total_average: 0
             )
             )
-
-            let gs = await glucoseStats
-            let overrideHbA1cUnit = gs.overrideHbA1cUnit
-            let hbA1cUnit = !overrideHbA1cUnit ? (units == .mmolL ? "mmol/mol" : "%") : (units == .mmolL ? "%" : "mmol/mol")
+            let processedGlucoseStats = await glucoseStats
+            let hbA1cDisplayUnit = processedGlucoseStats.hbA1cDisplayUnit
 
 
             let dailystat = await Statistics(
             let dailystat = await Statistics(
                 created_at: Date(),
                 created_at: Date(),
@@ -1064,14 +1062,15 @@ final class BaseAPSManager: APSManager, Injectable {
                 insulinType: insulin_type.rawValue,
                 insulinType: insulin_type.rawValue,
                 peakActivityTime: iPa,
                 peakActivityTime: iPa,
                 Carbs_24h: await carbTotal,
                 Carbs_24h: await carbTotal,
-                GlucoseStorage_Days: Decimal(roundDouble(gs.numberofDays, 1)),
+                GlucoseStorage_Days: Decimal(roundDouble(processedGlucoseStats.numberofDays, 1)),
                 Statistics: Stats(
                 Statistics: Stats(
-                    Distribution: gs.TimeInRange,
-                    Glucose: gs.avg,
-                    HbA1c: gs.hbs, Units: Units(Glucose: units.rawValue, HbA1c: hbA1cUnit),
+                    Distribution: processedGlucoseStats.TimeInRange,
+                    Glucose: processedGlucoseStats.avg,
+                    HbA1c: processedGlucoseStats.hbs,
+                    Units: Units(Glucose: units.rawValue, HbA1c: hbA1cDisplayUnit.rawValue),
                     LoopCycles: loopStats,
                     LoopCycles: loopStats,
                     Insulin: insulin,
                     Insulin: insulin,
-                    Variance: gs.variance
+                    Variance: processedGlucoseStats.variance
                 )
                 )
             )
             )
             storage.save(dailystat, as: file)
             storage.save(dailystat, as: file)
@@ -1234,7 +1233,7 @@ final class BaseAPSManager: APSManager, Injectable {
                 cv: Double,
                 cv: Double,
                 readings: Double
                 readings: Double
             ),
             ),
-            overrideHbA1cUnit: Bool,
+            hbA1cDisplayUnit: HbA1cDisplayUnit,
             numberofDays: Double,
             numberofDays: Double,
             TimeInRange: TIRs,
             TimeInRange: TIRs,
             avg: Averages,
             avg: Averages,
@@ -1270,7 +1269,7 @@ final class BaseAPSManager: APSManager, Injectable {
                 cv: Double,
                 cv: Double,
                 readings: Double
                 readings: Double
             ),
             ),
-            overrideHbA1cUnit: Bool,
+            hbA1cDisplayUnit: HbA1cDisplayUnit,
             numberofDays: Double,
             numberofDays: Double,
             TimeInRange: TIRs,
             TimeInRange: TIRs,
             avg: Averages,
             avg: Averages,
@@ -1301,18 +1300,18 @@ final class BaseAPSManager: APSManager, Injectable {
                 total: self.roundDecimal(Decimal(totalDaysGlucose.median), 1)
                 total: self.roundDecimal(Decimal(totalDaysGlucose.median), 1)
             )
             )
 
 
-            let overrideHbA1cUnit = self.settingsManager.settings.overrideHbA1cUnit
+            let hbA1cDisplayUnit = self.settingsManager.settings.hbA1cDisplayUnit
 
 
             let hbs = Durations(
             let hbs = Durations(
-                day: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
+                day: ((units == .mmolL && hbA1cDisplayUnit == .mmolMol) || (units == .mgdL && hbA1cDisplayUnit == .percent)) ?
                     self.roundDecimal(Decimal(oneDayGlucose.ifcc), 1) : self.roundDecimal(Decimal(oneDayGlucose.ngsp), 1),
                     self.roundDecimal(Decimal(oneDayGlucose.ifcc), 1) : self.roundDecimal(Decimal(oneDayGlucose.ngsp), 1),
-                week: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
+                week: ((units == .mmolL && hbA1cDisplayUnit == .mmolMol) || (units == .mgdL && hbA1cDisplayUnit == .percent)) ?
                     self.roundDecimal(Decimal(sevenDaysGlucose.ifcc), 1) : self
                     self.roundDecimal(Decimal(sevenDaysGlucose.ifcc), 1) : self
                     .roundDecimal(Decimal(sevenDaysGlucose.ngsp), 1),
                     .roundDecimal(Decimal(sevenDaysGlucose.ngsp), 1),
-                month: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
+                month: ((units == .mmolL && hbA1cDisplayUnit == .mmolMol) || (units == .mgdL && hbA1cDisplayUnit == .percent)) ?
                     self.roundDecimal(Decimal(thirtyDaysGlucose.ifcc), 1) : self
                     self.roundDecimal(Decimal(thirtyDaysGlucose.ifcc), 1) : self
                     .roundDecimal(Decimal(thirtyDaysGlucose.ngsp), 1),
                     .roundDecimal(Decimal(thirtyDaysGlucose.ngsp), 1),
-                total: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
+                total: ((units == .mmolL && hbA1cDisplayUnit == .mmolMol) || (units == .mgdL && hbA1cDisplayUnit == .percent)) ?
                     self.roundDecimal(Decimal(totalDaysGlucose.ifcc), 1) : self.roundDecimal(Decimal(totalDaysGlucose.ngsp), 1)
                     self.roundDecimal(Decimal(totalDaysGlucose.ifcc), 1) : self.roundDecimal(Decimal(totalDaysGlucose.ngsp), 1)
             )
             )
 
 
@@ -1386,7 +1385,7 @@ final class BaseAPSManager: APSManager, Injectable {
             )
             )
             let variance = Variance(SD: standardDeviations, CV: cvs)
             let variance = Variance(SD: standardDeviations, CV: cvs)
 
 
-            result = (oneDayGlucose, overrideHbA1cUnit, numberOfDays, TimeInRange, avg, hbs, variance)
+            result = (oneDayGlucose, hbA1cDisplayUnit, numberOfDays, TimeInRange, avg, hbs, variance)
         }
         }
 
 
         return result!
         return result!

+ 0 - 10
FreeAPS/Sources/APS/DeviceDataManager.swift

@@ -648,15 +648,6 @@ extension BaseDeviceDataManager: AlertObserver {
     }
     }
 
 
     private func ackAlert(alert: AlertEntry) {
     private func ackAlert(alert: AlertEntry) {
-        let typeMessage: MessageType
-        let alertUp = alert.alertIdentifier.uppercased()
-        if alertUp.contains("FAULT") || alertUp.contains("ERROR") {
-            typeMessage = .errorPump
-        } else {
-            typeMessage = .warning
-        }
-
-        let messageCont = MessageContent(content: alert.contentBody ?? "Unknown", type: typeMessage)
         let alertIssueDate = alert.issuedDate
         let alertIssueDate = alert.issuedDate
 
 
         processQueue.async {
         processQueue.async {
@@ -676,7 +667,6 @@ extension BaseDeviceDataManager: AlertObserver {
             }
             }
 
 
             self.pumpManager?.acknowledgeAlert(alertIdentifier: alert.alertIdentifier) { error in
             self.pumpManager?.acknowledgeAlert(alertIdentifier: alert.alertIdentifier) { error in
-                self.router.alertMessage.send(messageCont)
                 if let error = error {
                 if let error = error {
                     self.alertHistoryStorage.ackAlert(alertIssueDate, error.localizedDescription)
                     self.alertHistoryStorage.ackAlert(alertIssueDate, error.localizedDescription)
                     debug(.deviceManager, "acknowledge not succeeded with error \(error.localizedDescription)")
                     debug(.deviceManager, "acknowledge not succeeded with error \(error.localizedDescription)")

+ 1 - 1
FreeAPS/Sources/APS/FetchGlucoseManager.swift

@@ -198,7 +198,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
             key: "date",
             key: "date",
             ascending: false,
             ascending: false,
             fetchLimit: 6
             fetchLimit: 6
-        )
+        ) as? [GlucoseStored]
     }
     }
 
 
     private func processGlucose() -> [BloodGlucose] {
     private func processGlucose() -> [BloodGlucose] {

+ 36 - 3
FreeAPS/Sources/APS/FetchTreatmentsManager.swift

@@ -1,4 +1,5 @@
 import Combine
 import Combine
+import CoreData
 import Foundation
 import Foundation
 import SwiftDate
 import SwiftDate
 import Swinject
 import Swinject
@@ -13,6 +14,7 @@ final class BaseFetchTreatmentsManager: FetchTreatmentsManager, Injectable {
 
 
     private var lifetime = Lifetime()
     private var lifetime = Lifetime()
     private let timer = DispatchTimer(timeInterval: 1.minutes.timeInterval)
     private let timer = DispatchTimer(timeInterval: 1.minutes.timeInterval)
+    private var backgroundContext = CoreDataStack.shared.newTaskContext()
 
 
     init(resolver: Resolver) {
     init(resolver: Resolver) {
         injectServices(resolver)
         injectServices(resolver)
@@ -28,17 +30,48 @@ final class BaseFetchTreatmentsManager: FetchTreatmentsManager, Injectable {
                 debug(.nightscout, "Start fetching carbs and temptargets")
                 debug(.nightscout, "Start fetching carbs and temptargets")
 
 
                 Task {
                 Task {
+                    // Fetch carbs and temp targets concurrently
                     async let carbs = self.nightscoutManager.fetchCarbs()
                     async let carbs = self.nightscoutManager.fetchCarbs()
                     async let tempTargets = self.nightscoutManager.fetchTempTargets()
                     async let tempTargets = self.nightscoutManager.fetchTempTargets()
 
 
-                    let filteredCarbs = await carbs.filter { !($0.enteredBy?.contains(CarbsEntry.manual) ?? false) }
+                    // Filter and store if not from "Trio"
+                    let filteredCarbs = await carbs.filter { $0.enteredBy != CarbsEntry.local }
                     if filteredCarbs.isNotEmpty {
                     if filteredCarbs.isNotEmpty {
                         await self.carbsStorage.storeCarbs(filteredCarbs, areFetchedFromRemote: true)
                         await self.carbsStorage.storeCarbs(filteredCarbs, areFetchedFromRemote: true)
                     }
                     }
 
 
-                    let filteredTargets = await tempTargets.filter { !($0.enteredBy?.contains(TempTarget.manual) ?? false) }
+                    // Filter and store if not from Trio
+                    let filteredTargets = await tempTargets.filter { $0.enteredBy != TempTarget.local }
                     if filteredTargets.isNotEmpty {
                     if filteredTargets.isNotEmpty {
-                        self.tempTargetsStorage.storeTempTargets(filteredTargets)
+                        // Sort temp targets by creation date
+                        let sortedTargets = filteredTargets.sorted { $0.createdAt < $1.createdAt }
+
+                        // Iterate and store each temp target
+                        for (index, tempTarget) in sortedTargets.enumerated() {
+                            // Skip saving if a Temp Target with the same date already exists or it's a cancel target
+                            guard await !self.tempTargetsStorage.existsTempTarget(with: tempTarget.createdAt),
+                                  tempTarget.reason != TempTarget.cancel
+                            else {
+                                debug(
+                                    .nightscout,
+                                    "Skipping temp target with date: \(tempTarget.date ?? Date.distantPast)"
+                                )
+                                continue
+                            }
+
+                            // Create a mutable copy and set enabled for the last temp target
+                            var mutableTempTarget = tempTarget
+                            mutableTempTarget.enabled = (index == sortedTargets.count - 1)
+
+                            // Save to Core Data
+                            await self.tempTargetsStorage.storeTempTarget(tempTarget: mutableTempTarget)
+                        }
+
+                        // Save the temp targets to JSON so that they get used by oref
+                        self.tempTargetsStorage.saveTempTargetsToStorage(sortedTargets)
+
+                        // Update Adjustments View
+                        Foundation.NotificationCenter.default.post(name: .didUpdateTempTargetConfiguration, object: nil)
                     }
                     }
                 }
                 }
             }
             }

+ 112 - 192
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -357,194 +357,66 @@ final class OpenAPS {
 
 
     func oref2() async -> RawJSON {
     func oref2() async -> RawJSON {
         await context.perform {
         await context.perform {
-            let preferences = self.storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self)
-            var hbt_ = preferences?.halfBasalExerciseTarget ?? 160
-            let wp = preferences?.weightPercentage ?? 1
-            let smbMinutes = (preferences?.maxSMBBasalMinutes ?? 30) as NSDecimalNumber
-            let uamMinutes = (preferences?.maxUAMSMBBasalMinutes ?? 30) as NSDecimalNumber
+            // Retrieve user preferences
+            let userPreferences = self.storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self)
+            let weightPercentage = userPreferences?.weightPercentage ?? 1.0
+            let maxSMBBasalMinutes = userPreferences?.maxSMBBasalMinutes ?? 30
+            let maxUAMBasalMinutes = userPreferences?.maxUAMSMBBasalMinutes ?? 30
 
 
+            // Fetch historical events for Total Daily Dose (TDD) calculation
             let tenDaysAgo = Date().addingTimeInterval(-10.days.timeInterval)
             let tenDaysAgo = Date().addingTimeInterval(-10.days.timeInterval)
             let twoHoursAgo = Date().addingTimeInterval(-2.hours.timeInterval)
             let twoHoursAgo = Date().addingTimeInterval(-2.hours.timeInterval)
-
-            var uniqueEvents = [[String: Any]]()
-            let requestTDD = OrefDetermination.fetchRequest() as NSFetchRequest<NSFetchRequestResult>
-            requestTDD.predicate = NSPredicate(format: "timestamp > %@ AND totalDailyDose > 0", tenDaysAgo as NSDate)
-            requestTDD.propertiesToFetch = ["timestamp", "totalDailyDose"]
-            let sortTDD = NSSortDescriptor(key: "timestamp", ascending: true)
-            requestTDD.sortDescriptors = [sortTDD]
-            requestTDD.resultType = .dictionaryResultType
-
-            do {
-                if let fetchedResults = try self.context.fetch(requestTDD) as? [[String: Any]] {
-                    uniqueEvents = fetchedResults
-                }
-            } catch {
-                debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch TDD Data")
-            }
-
-            var sliderArray = [TempTargetsSlider]()
-            let requestIsEnbled = TempTargetsSlider.fetchRequest() as NSFetchRequest<TempTargetsSlider>
-            let sortIsEnabled = NSSortDescriptor(key: "date", ascending: false)
-            requestIsEnbled.sortDescriptors = [sortIsEnabled]
-            // requestIsEnbled.fetchLimit = 1
-            try? sliderArray = self.context.fetch(requestIsEnbled)
-
-            /// Get the last active Override as only this information is apparently used in oref2
-            var overrideArray = [OverrideStored]()
-            let requestOverrides = OverrideStored.fetchRequest() as NSFetchRequest<OverrideStored>
-            let sortOverride = NSSortDescriptor(key: "date", ascending: false)
-            requestOverrides.sortDescriptors = [sortOverride]
-            requestOverrides.predicate = NSPredicate.lastActiveOverride
-            requestOverrides.fetchLimit = 1
-            try? overrideArray = self.context.fetch(requestOverrides)
-
-            var tempTargetsArray = [TempTargets]()
-            let requestTempTargets = TempTargets.fetchRequest() as NSFetchRequest<TempTargets>
-            let sortTT = NSSortDescriptor(key: "date", ascending: false)
-            requestTempTargets.sortDescriptors = [sortTT]
-            requestTempTargets.fetchLimit = 1
-            try? tempTargetsArray = self.context.fetch(requestTempTargets)
-
-            let total = uniqueEvents.compactMap({ ($0["totalDailyDose"] as? NSDecimalNumber)?.decimalValue ?? 0 }).reduce(0, +)
-            var indeces = uniqueEvents.count
-            // Only fetch once. Use same (previous) fetch
-            let twoHoursArray = uniqueEvents.filter({ ($0["timestamp"] as? Date ?? Date()) >= twoHoursAgo })
-            var nrOfIndeces = twoHoursArray.count
-            let totalAmount = twoHoursArray.compactMap({ ($0["totalDailyDose"] as? NSDecimalNumber)?.decimalValue ?? 0 })
+            let historicalTDDData = self.fetchHistoricalTDDData(from: tenDaysAgo)
+
+            // Fetch the last active Override
+            let activeOverrides = self.fetchActiveOverrides()
+            let isOverrideActive = activeOverrides.first?.enabled ?? false
+            let overridePercentage = Decimal(activeOverrides.first?.percentage ?? 100)
+            let isOverrideIndefinite = activeOverrides.first?.indefinite ?? true
+            let disableSMBs = activeOverrides.first?.smbIsOff ?? false
+            let overrideTargetBG = activeOverrides.first?.target?.decimalValue ?? 0
+
+            // Calculate averages for Total Daily Dose (TDD)
+            let totalTDD = historicalTDDData.compactMap { ($0["totalDailyDose"] as? NSDecimalNumber)?.decimalValue }.reduce(0, +)
+            let totalDaysCount = max(historicalTDDData.count, 1)
+
+            // Fetch recent TDD data for the past two hours
+            let recentTDDData = historicalTDDData.filter { ($0["timestamp"] as? Date ?? Date()) >= twoHoursAgo }
+            let recentDataCount = max(recentTDDData.count, 1)
+            let recentTotalTDD = recentTDDData.compactMap { ($0["totalDailyDose"] as? NSDecimalNumber)?.decimalValue }
                 .reduce(0, +)
                 .reduce(0, +)
 
 
-            var temptargetActive = tempTargetsArray.first?.active ?? false
-            let isPercentageEnabled = sliderArray.first?.enabled ?? false
-
-            var useOverride = overrideArray.first?.enabled ?? false
-            var overridePercentage = Decimal(overrideArray.first?.percentage ?? 100)
-            var unlimited = overrideArray.first?.indefinite ?? true
-            var disableSMBs = overrideArray.first?.smbIsOff ?? false
-
-            let currentTDD = (uniqueEvents.last?["totalDailyDose"] as? NSDecimalNumber)?.decimalValue ?? 0
-
-            if indeces == 0 {
-                indeces = 1
-            }
-            if nrOfIndeces == 0 {
-                nrOfIndeces = 1
-            }
-
-            let average2hours = totalAmount / Decimal(nrOfIndeces)
-            let average14 = total / Decimal(indeces)
-
-            let weight = wp
-            let weighted_average = weight * average2hours + (1 - weight) * average14
-
-            var duration: Decimal = 0
-            var overrideTarget: Decimal = 0
-
-            if useOverride {
-                duration = (overrideArray.first?.duration ?? 0) as Decimal
-                overrideTarget = (overrideArray.first?.target ?? 0) as Decimal
-                let advancedSettings = overrideArray.first?.advancedSettings ?? false
-                let addedMinutes = Int(duration)
-                let date = overrideArray.first?.date ?? Date()
-                if date.addingTimeInterval(addedMinutes.minutes.timeInterval) < Date(),
-                   !unlimited
-                {
-                    useOverride = false
-                    let saveToCoreData = OverrideStored(context: self.context)
-                    saveToCoreData.enabled = false
-                    saveToCoreData.date = Date()
-                    saveToCoreData.duration = 0
-                    saveToCoreData.indefinite = false
-                    saveToCoreData.percentage = 100
-                    do {
-                        guard self.context.hasChanges else { return "{}" }
-                        try self.context.save()
-                    } catch {
-                        print(error.localizedDescription)
-                    }
-                }
-            }
-
-            if !useOverride {
-                unlimited = true
-                overridePercentage = 100
-                duration = 0
-                overrideTarget = 0
-                disableSMBs = false
-            }
-
-            if temptargetActive {
-                var duration_ = 0
-                var hbt = Double(hbt_)
-                var dd = 0.0
-
-                if temptargetActive {
-                    duration_ = Int(truncating: tempTargetsArray.first?.duration ?? 0)
-                    hbt = tempTargetsArray.first?.hbt ?? Double(hbt_)
-                    let startDate = tempTargetsArray.first?.startDate ?? Date()
-                    let durationPlusStart = startDate.addingTimeInterval(duration_.minutes.timeInterval)
-                    dd = durationPlusStart.timeIntervalSinceNow.minutes
-
-                    if dd > 0.1 {
-                        hbt_ = Decimal(hbt)
-                        temptargetActive = true
-                    } else {
-                        temptargetActive = false
-                    }
-                }
-            }
-
-            if currentTDD > 0 {
-                let averages = Oref2_variables(
-                    average_total_data: average14,
-                    weightedAverage: weighted_average,
-                    past2hoursAverage: average2hours,
-                    date: Date(),
-                    isEnabled: temptargetActive,
-                    presetActive: isPercentageEnabled,
-                    overridePercentage: overridePercentage,
-                    useOverride: useOverride,
-                    duration: duration,
-                    unlimited: unlimited,
-                    hbt: hbt_,
-                    overrideTarget: overrideTarget,
-                    smbIsOff: disableSMBs,
-                    advancedSettings: overrideArray.first?.advancedSettings ?? false,
-                    isfAndCr: overrideArray.first?.isfAndCr ?? false,
-                    isf: overrideArray.first?.isf ?? false,
-                    cr: overrideArray.first?.cr ?? false,
-                    smbMinutes: (overrideArray.first?.smbMinutes ?? smbMinutes) as Decimal,
-                    uamMinutes: (overrideArray.first?.uamMinutes ?? uamMinutes) as Decimal
-                )
-
-                self.storage.save(averages, as: OpenAPS.Monitor.oref2_variables)
-
-                return self.loadFileFromStorage(name: Monitor.oref2_variables)
+            let currentTDD = historicalTDDData.last?["totalDailyDose"] as? Decimal ?? 0
+            let averageTDDLastTwoHours = recentTotalTDD / Decimal(recentDataCount)
+            let averageTDDLastTenDays = totalTDD / Decimal(totalDaysCount)
+            let weightedTDD = weightPercentage * averageTDDLastTwoHours + (1 - weightPercentage) * averageTDDLastTenDays
+
+            // Prepare Oref2 variables
+            let oref2Data = Oref2_variables(
+                average_total_data: currentTDD > 0 ? averageTDDLastTenDays : 0,
+                weightedAverage: currentTDD > 0 ? weightedTDD : 1,
+                past2hoursAverage: currentTDD > 0 ? averageTDDLastTwoHours : 0,
+                date: Date(),
+                overridePercentage: overridePercentage,
+                useOverride: isOverrideActive,
+                duration: activeOverrides.first?.duration?.decimalValue ?? 0,
+                unlimited: isOverrideIndefinite,
+                overrideTarget: overrideTargetBG,
+                smbIsOff: disableSMBs,
+                advancedSettings: activeOverrides.first?.advancedSettings ?? false,
+                isfAndCr: activeOverrides.first?.isfAndCr ?? false,
+                isf: activeOverrides.first?.isf ?? false,
+                cr: activeOverrides.first?.cr ?? false,
+                smbIsScheduledOff: activeOverrides.first?.smbIsScheduledOff ?? false,
+                start: (activeOverrides.first?.start ?? 0) as Decimal,
+                end: (activeOverrides.first?.end ?? 0) as Decimal,
+                smbMinutes: activeOverrides.first?.smbMinutes?.decimalValue ?? maxSMBBasalMinutes,
+                uamMinutes: activeOverrides.first?.uamMinutes?.decimalValue ?? maxUAMBasalMinutes
+            )
 
 
-            } else {
-                let averages = Oref2_variables(
-                    average_total_data: 0,
-                    weightedAverage: 1,
-                    past2hoursAverage: 0,
-                    date: Date(),
-                    isEnabled: temptargetActive,
-                    presetActive: isPercentageEnabled,
-                    overridePercentage: overridePercentage,
-                    useOverride: useOverride,
-                    duration: duration,
-                    unlimited: unlimited,
-                    hbt: hbt_,
-                    overrideTarget: overrideTarget,
-                    smbIsOff: disableSMBs,
-                    advancedSettings: overrideArray.first?.advancedSettings ?? false,
-                    isfAndCr: overrideArray.first?.isfAndCr ?? false,
-                    isf: overrideArray.first?.isf ?? false,
-                    cr: overrideArray.first?.cr ?? false,
-                    smbMinutes: (overrideArray.first?.smbMinutes ?? smbMinutes) as Decimal,
-                    uamMinutes: (overrideArray.first?.uamMinutes ?? uamMinutes) as Decimal
-                )
-                self.storage.save(averages, as: OpenAPS.Monitor.oref2_variables)
-                return self.loadFileFromStorage(name: Monitor.oref2_variables)
-            }
+            // Save and return the Oref2 variables
+            self.storage.save(oref2Data, as: OpenAPS.Monitor.oref2_variables)
+            return self.loadFileFromStorage(name: Monitor.oref2_variables)
         }
         }
     }
     }
 
 
@@ -651,7 +523,7 @@ final class OpenAPS {
     func makeProfiles(useAutotune _: Bool) async -> Autotune? {
     func makeProfiles(useAutotune _: Bool) async -> Autotune? {
         debug(.openAPS, "Start makeProfiles")
         debug(.openAPS, "Start makeProfiles")
 
 
-        async let getPreferences = loadFileFromStorageAsync(name: Settings.preferences)
+        // Load required settings and profiles asynchronously
         async let getPumpSettings = loadFileFromStorageAsync(name: Settings.settings)
         async let getPumpSettings = loadFileFromStorageAsync(name: Settings.settings)
         async let getBGTargets = loadFileFromStorageAsync(name: Settings.bgTargets)
         async let getBGTargets = loadFileFromStorageAsync(name: Settings.bgTargets)
         async let getBasalProfile = loadFileFromStorageAsync(name: Settings.basalProfile)
         async let getBasalProfile = loadFileFromStorageAsync(name: Settings.basalProfile)
@@ -662,8 +534,7 @@ final class OpenAPS {
         async let getAutotune = loadFileFromStorageAsync(name: Settings.autotune)
         async let getAutotune = loadFileFromStorageAsync(name: Settings.autotune)
         async let getFreeAPS = loadFileFromStorageAsync(name: FreeAPS.settings)
         async let getFreeAPS = loadFileFromStorageAsync(name: FreeAPS.settings)
 
 
-        let (preferences, pumpSettings, bgTargets, basalProfile, isf, cr, tempTargets, model, autotune, freeaps) = await (
-            getPreferences,
+        let (pumpSettings, bgTargets, basalProfile, isf, cr, tempTargets, model, autotune, freeaps) = await (
             getPumpSettings,
             getPumpSettings,
             getBGTargets,
             getBGTargets,
             getBasalProfile,
             getBasalProfile,
@@ -675,13 +546,26 @@ final class OpenAPS {
             getFreeAPS
             getFreeAPS
         )
         )
 
 
+        // Retrieve user preferences, or set defaults if not available
+        let preferences = storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self) ?? Preferences()
+        let defaultHalfBasalTarget = preferences.halfBasalExerciseTarget
         var adjustedPreferences = preferences
         var adjustedPreferences = preferences
-        if adjustedPreferences.isEmpty {
-            adjustedPreferences = Preferences().rawJSON
+
+        // Check for active Temp Targets and adjust HBT if necessary
+        await context.perform {
+            // Check if a Temp Target is active and if its HBT differs from user preferences
+            if let activeTempTarget = self.fetchActiveTempTargets().first,
+               activeTempTarget.enabled,
+               let activeHBT = activeTempTarget.halfBasalTarget?.decimalValue,
+               activeHBT != defaultHalfBasalTarget
+            {
+                // Overwrite the HBT in preferences
+                adjustedPreferences.halfBasalExerciseTarget = activeHBT
+                debug(.openAPS, "Updated halfBasalExerciseTarget to active Temp Target value: \(activeHBT)")
+            }
         }
         }
 
 
         do {
         do {
-            // Pump Profile
             let pumpProfile = try await makeProfile(
             let pumpProfile = try await makeProfile(
                 preferences: adjustedPreferences,
                 preferences: adjustedPreferences,
                 pumpSettings: pumpSettings,
                 pumpSettings: pumpSettings,
@@ -695,7 +579,6 @@ final class OpenAPS {
                 freeaps: freeaps
                 freeaps: freeaps
             )
             )
 
 
-            // Profile
             let profile = try await makeProfile(
             let profile = try await makeProfile(
                 preferences: adjustedPreferences,
                 preferences: adjustedPreferences,
                 pumpSettings: pumpSettings,
                 pumpSettings: pumpSettings,
@@ -709,25 +592,26 @@ final class OpenAPS {
                 freeaps: freeaps
                 freeaps: freeaps
             )
             )
 
 
+            // Save the profiles
             await storage.saveAsync(pumpProfile, as: Settings.pumpProfile)
             await storage.saveAsync(pumpProfile, as: Settings.pumpProfile)
             await storage.saveAsync(profile, as: Settings.profile)
             await storage.saveAsync(profile, as: Settings.profile)
 
 
+            // Return the Autotune object, if available
             if let tunedProfile = Autotune(from: profile) {
             if let tunedProfile = Autotune(from: profile) {
                 return tunedProfile
                 return tunedProfile
             } else {
             } else {
                 return nil
                 return nil
             }
             }
         } catch {
         } catch {
+            // Handle errors and log failure
             debug(
             debug(
                 .apsManager,
                 .apsManager,
-                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to execute makeProfiles() to return Autoune results"
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to execute makeProfiles()"
             )
             )
             return nil
             return nil
         }
         }
     }
     }
 
 
-    // MARK: - Private
-
     private func iob(pumphistory: JSON, profile: JSON, clock: JSON, autosens: JSON) async throws -> RawJSON {
     private func iob(pumphistory: JSON, profile: JSON, clock: JSON, autosens: JSON) async throws -> RawJSON {
         await withCheckedContinuation { continuation in
         await withCheckedContinuation { continuation in
             jsWorker.inCommonContext { worker in
             jsWorker.inCommonContext { worker in
@@ -1020,3 +904,39 @@ final class OpenAPS {
         }
         }
     }
     }
 }
 }
+
+// Non-Async fetch methods for oref2
+extension OpenAPS {
+    func fetchActiveTempTargets() -> [TempTargetStored] {
+        CoreDataStack.shared.fetchEntities(
+            ofType: TempTargetStored.self,
+            onContext: context,
+            predicate: NSPredicate.lastActiveTempTarget,
+            key: "date",
+            ascending: false,
+            fetchLimit: 1
+        ) as? [TempTargetStored] ?? []
+    }
+
+    func fetchActiveOverrides() -> [OverrideStored] {
+        CoreDataStack.shared.fetchEntities(
+            ofType: OverrideStored.self,
+            onContext: context,
+            predicate: NSPredicate.lastActiveOverride,
+            key: "date",
+            ascending: false,
+            fetchLimit: 1
+        ) as? [OverrideStored] ?? []
+    }
+
+    func fetchHistoricalTDDData(from date: Date) -> [[String: Any]] {
+        CoreDataStack.shared.fetchEntities(
+            ofType: OrefDetermination.self,
+            onContext: context,
+            predicate: NSPredicate(format: "timestamp > %@ AND totalDailyDose > 0", date as NSDate),
+            key: "timestamp",
+            ascending: true,
+            propertiesToFetch: ["timestamp", "totalDailyDose"]
+        ) as? [[String: Any]] ?? []
+    }
+}

+ 6 - 5
FreeAPS/Sources/APS/Storage/CarbsStorage.swift

@@ -165,7 +165,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                 fat: 0,
                 fat: 0,
                 protein: 0,
                 protein: 0,
                 note: nil,
                 note: nil,
-                enteredBy: CarbsEntry.manual,
+                enteredBy: CarbsEntry.local,
                 isFPU: true,
                 isFPU: true,
                 fpuID: fpuID
                 fpuID: fpuID
             )
             )
@@ -365,7 +365,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                     rate: nil,
                     rate: nil,
                     eventType: .nsCarbCorrection,
                     eventType: .nsCarbCorrection,
                     createdAt: result.date,
                     createdAt: result.date,
-                    enteredBy: CarbsEntry.manual,
+                    enteredBy: CarbsEntry.local,
                     bolus: nil,
                     bolus: nil,
                     insulin: nil,
                     insulin: nil,
                     notes: result.note,
                     notes: result.note,
@@ -402,9 +402,10 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                     rate: nil,
                     rate: nil,
                     eventType: .nsCarbCorrection,
                     eventType: .nsCarbCorrection,
                     createdAt: result.date,
                     createdAt: result.date,
-                    enteredBy: CarbsEntry.manual,
+                    enteredBy: CarbsEntry.local,
                     bolus: nil,
                     bolus: nil,
                     insulin: nil,
                     insulin: nil,
+                    notes: result.note,
                     carbs: Decimal(result.carbs),
                     carbs: Decimal(result.carbs),
                     fat: Decimal(result.fat),
                     fat: Decimal(result.fat),
                     protein: Decimal(result.protein),
                     protein: Decimal(result.protein),
@@ -440,7 +441,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                     fat: Decimal(result.fat),
                     fat: Decimal(result.fat),
                     protein: Decimal(result.protein),
                     protein: Decimal(result.protein),
                     note: result.note,
                     note: result.note,
-                    enteredBy: CarbsEntry.manual,
+                    enteredBy: CarbsEntry.local,
                     isFPU: result.isFPU,
                     isFPU: result.isFPU,
                     fpuID: result.fpuID?.uuidString
                     fpuID: result.fpuID?.uuidString
                 )
                 )
@@ -471,7 +472,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                     fat: nil,
                     fat: nil,
                     protein: nil,
                     protein: nil,
                     note: result.note,
                     note: result.note,
-                    enteredBy: CarbsEntry.manual,
+                    enteredBy: CarbsEntry.local,
                     isFPU: nil,
                     isFPU: nil,
                     fpuID: nil
                     fpuID: nil
                 )
                 )

+ 29 - 3
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -10,6 +10,7 @@ import Swinject
 protocol GlucoseStorage {
 protocol GlucoseStorage {
     var updatePublisher: AnyPublisher<Void, Never> { get }
     var updatePublisher: AnyPublisher<Void, Never> { get }
     func storeGlucose(_ glucose: [BloodGlucose])
     func storeGlucose(_ glucose: [BloodGlucose])
+    func addManualGlucose(glucose: Int)
     func isGlucoseDataFresh(_ glucoseDate: Date?) -> Bool
     func isGlucoseDataFresh(_ glucoseDate: Date?) -> Bool
     func syncDate() -> Date
     func syncDate() -> Date
     func filterTooFrequentGlucose(_ glucose: [BloodGlucose], at: Date) -> [BloodGlucose]
     func filterTooFrequentGlucose(_ glucose: [BloodGlucose], at: Date) -> [BloodGlucose]
@@ -173,6 +174,31 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         }
         }
     }
     }
 
 
+    func addManualGlucose(glucose: Int) {
+        coredataContext.perform {
+            let newItem = GlucoseStored(context: self.coredataContext)
+            newItem.id = UUID()
+            newItem.date = Date()
+            newItem.glucose = Int16(glucose)
+            newItem.isManual = true
+            newItem.isUploadedToNS = false
+            newItem.isUploadedToHealth = false
+            newItem.isUploadedToTidepool = false
+
+            do {
+                guard self.coredataContext.hasChanges else { return }
+                try self.coredataContext.save()
+
+                // Glucose subscribers already listen to the update publisher, so call here to update glucose-related data.
+                self.updateSubject.send(())
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save manual glucose to Core Data with error: \(error)"
+                )
+            }
+        }
+    }
+
     func isGlucoseDataFresh(_ glucoseDate: Date?) -> Bool {
     func isGlucoseDataFresh(_ glucoseDate: Date?) -> Bool {
         guard let glucoseDate = glucoseDate else { return false }
         guard let glucoseDate = glucoseDate else { return false }
         return glucoseDate > Date().addingTimeInterval(-6 * 60)
         return glucoseDate > Date().addingTimeInterval(-6 * 60)
@@ -238,14 +264,14 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
 
     func fetchLatestGlucose() -> GlucoseStored? {
     func fetchLatestGlucose() -> GlucoseStored? {
         let predicate = NSPredicate.predicateFor20MinAgo
         let predicate = NSPredicate.predicateFor20MinAgo
-        return CoreDataStack.shared.fetchEntities(
+        return (CoreDataStack.shared.fetchEntities(
             ofType: GlucoseStored.self,
             ofType: GlucoseStored.self,
             onContext: coredataContext,
             onContext: coredataContext,
             predicate: predicate,
             predicate: predicate,
             key: "date",
             key: "date",
             ascending: false,
             ascending: false,
             fetchLimit: 1
             fetchLimit: 1
-        ).first
+        ) as? [GlucoseStored] ?? []).first
     }
     }
 
 
     // Fetch glucose that is not uploaded to Nightscout yet
     // Fetch glucose that is not uploaded to Nightscout yet
@@ -303,7 +329,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
                     rate: nil,
                     rate: nil,
                     eventType: .capillaryGlucose,
                     eventType: .capillaryGlucose,
                     createdAt: result.date,
                     createdAt: result.date,
-                    enteredBy: CarbsEntry.manual,
+                    enteredBy: CarbsEntry.local,
                     bolus: nil,
                     bolus: nil,
                     insulin: nil,
                     insulin: nil,
                     notes: "Trio User",
                     notes: "Trio User",

+ 15 - 22
FreeAPS/Sources/APS/Storage/OverrideStorage.swift

@@ -15,7 +15,7 @@ protocol OverrideStorage {
     func getPresetOverridesForNightscout() async -> [NightscoutPresetOverride]
     func getPresetOverridesForNightscout() async -> [NightscoutPresetOverride]
 }
 }
 
 
-final class BaseOverrideStorage: OverrideStorage, Injectable {
+final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var settingsManager: SettingsManager!
 
 
     private let viewContext = CoreDataStack.shared.persistentContainer.viewContext
     private let viewContext = CoreDataStack.shared.persistentContainer.viewContext
@@ -89,7 +89,7 @@ final class BaseOverrideStorage: OverrideStorage, Injectable {
 
 
     @MainActor func calculateTarget(override: OverrideStored) -> Decimal {
     @MainActor func calculateTarget(override: OverrideStored) -> Decimal {
         guard let overrideTarget = override.target, overrideTarget != 0 else {
         guard let overrideTarget = override.target, overrideTarget != 0 else {
-            return 100 // default
+            return 0
         }
         }
         return overrideTarget.decimalValue
         return overrideTarget.decimalValue
     }
     }
@@ -125,38 +125,31 @@ final class BaseOverrideStorage: OverrideStorage, Injectable {
             newOverride.duration = override.duration as NSDecimalNumber
             newOverride.duration = override.duration as NSDecimalNumber
             newOverride.indefinite = override.indefinite
             newOverride.indefinite = override.indefinite
             newOverride.percentage = override.percentage
             newOverride.percentage = override.percentage
+            newOverride.isfAndCr = override.isfAndCr
+            newOverride.isf = override.isf
+            newOverride.cr = override.cr
             newOverride.enabled = override.enabled
             newOverride.enabled = override.enabled
             newOverride.smbIsOff = override.smbIsOff
             newOverride.smbIsOff = override.smbIsOff
             if override.overrideTarget {
             if override.overrideTarget {
-                newOverride.target = (
-                    self.settingsManager.settings.units == .mmolL ? override.target.asMgdL : override.target
-                ) as NSDecimalNumber
+                newOverride.target = override.target as NSDecimalNumber
             } else {
             } else {
                 newOverride.target = 0
                 newOverride.target = 0
             }
             }
             if override.advancedSettings {
             if override.advancedSettings {
                 newOverride.advancedSettings = true
                 newOverride.advancedSettings = true
 
 
-                if !override.isfAndCr {
-                    newOverride.isfAndCr = false
-                    newOverride.isf = override.isf
-                    newOverride.cr = override.cr
-                } else {
-                    newOverride.isfAndCr = true
-                }
-
-                if override.smbIsAlwaysOff {
-                    newOverride.smbIsAlwaysOff = true
-                    newOverride.start = override.start as NSDecimalNumber
-                    newOverride.end = override.end as NSDecimalNumber
-                } else {
-                    newOverride.smbIsAlwaysOff = false
-                }
-
                 newOverride.smbMinutes = override.smbMinutes as NSDecimalNumber
                 newOverride.smbMinutes = override.smbMinutes as NSDecimalNumber
                 newOverride.uamMinutes = override.uamMinutes as NSDecimalNumber
                 newOverride.uamMinutes = override.uamMinutes as NSDecimalNumber
             }
             }
 
 
+            if override.smbIsScheduledOff {
+                newOverride.smbIsScheduledOff = true
+                newOverride.start = override.start as NSDecimalNumber
+                newOverride.end = override.end as NSDecimalNumber
+            } else {
+                newOverride.smbIsScheduledOff = false
+            }
+
             do {
             do {
                 guard self.backgroundContext.hasChanges else { return }
                 guard self.backgroundContext.hasChanges else { return }
                 try self.backgroundContext.save()
                 try self.backgroundContext.save()
@@ -185,7 +178,7 @@ final class BaseOverrideStorage: OverrideStorage, Injectable {
         newOverride.isfAndCr = override.isfAndCr
         newOverride.isfAndCr = override.isfAndCr
         newOverride.isf = override.isf
         newOverride.isf = override.isf
         newOverride.cr = override.cr
         newOverride.cr = override.cr
-        newOverride.smbIsAlwaysOff = override.smbIsAlwaysOff
+        newOverride.smbIsScheduledOff = override.smbIsScheduledOff
         newOverride.start = override.start
         newOverride.start = override.start
         newOverride.end = override.end
         newOverride.end = override.end
         newOverride.smbMinutes = override.smbMinutes
         newOverride.smbMinutes = override.smbMinutes

+ 1 - 1
FreeAPS/Sources/APS/Storage/PumpHistoryStorage.swift

@@ -60,7 +60,7 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
                         key: "timestamp",
                         key: "timestamp",
                         ascending: false,
                         ascending: false,
                         batchSize: 50
                         batchSize: 50
-                    )
+                    ) as? [PumpEventStored] ?? []
 
 
                     switch event.type {
                     switch event.type {
                     case .bolus:
                     case .bolus:

+ 266 - 50
FreeAPS/Sources/APS/Storage/TempTargetsStorage.swift

@@ -1,3 +1,4 @@
+import CoreData
 import Foundation
 import Foundation
 import SwiftDate
 import SwiftDate
 import Swinject
 import Swinject
@@ -7,59 +8,218 @@ protocol TempTargetsObserver {
 }
 }
 
 
 protocol TempTargetsStorage {
 protocol TempTargetsStorage {
-    func storeTempTargets(_ targets: [TempTarget])
+    func storeTempTarget(tempTarget: TempTarget) async
+    func saveTempTargetsToStorage(_ targets: [TempTarget])
+    func fetchForTempTargetPresets() async -> [NSManagedObjectID]
+    func fetchScheduledTempTargets() async -> [NSManagedObjectID]
+    func fetchScheduledTempTarget(for targetDate: Date) async -> [NSManagedObjectID]
+    func copyRunningTempTarget(_ tempTarget: TempTargetStored) async -> NSManagedObjectID
+    func deleteOverridePreset(_ objectID: NSManagedObjectID) async
+    func loadLatestTempTargetConfigurations(fetchLimit: Int) async -> [NSManagedObjectID]
     func syncDate() -> Date
     func syncDate() -> Date
     func recent() -> [TempTarget]
     func recent() -> [TempTarget]
-    func nightscoutTreatmentsNotUploaded() -> [NightscoutTreatment]
-    func storePresets(_ targets: [TempTarget])
+    func getTempTargetsNotYetUploadedToNightscout() async -> [NightscoutTreatment]
+    func getTempTargetRunsNotYetUploadedToNightscout() async -> [NightscoutTreatment]
     func presets() -> [TempTarget]
     func presets() -> [TempTarget]
     func current() -> TempTarget?
     func current() -> TempTarget?
+    func existsTempTarget(with date: Date) async -> Bool
 }
 }
 
 
 final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
 final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
     private let processQueue = DispatchQueue(label: "BaseTempTargetsStorage.processQueue")
     private let processQueue = DispatchQueue(label: "BaseTempTargetsStorage.processQueue")
     @Injected() private var storage: FileStorage!
     @Injected() private var storage: FileStorage!
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var broadcaster: Broadcaster!
+    @Injected() private var settingsManager: SettingsManager!
+
+    private let backgroundContext = CoreDataStack.shared.newTaskContext()
+    private let viewContext = CoreDataStack.shared.persistentContainer.viewContext
 
 
     init(resolver: Resolver) {
     init(resolver: Resolver) {
         injectServices(resolver)
         injectServices(resolver)
     }
     }
 
 
-    func storeTempTargets(_ targets: [TempTarget]) {
-        storeTempTargets(targets, isPresets: false)
+    func loadLatestTempTargetConfigurations(fetchLimit: Int) async -> [NSManagedObjectID] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: TempTargetStored.self,
+            onContext: backgroundContext,
+            predicate: NSPredicate.lastActiveTempTarget,
+            key: "orderPosition",
+            ascending: true,
+            fetchLimit: fetchLimit
+        )
+
+        guard let fetchedResults = results as? [TempTargetStored] else { return [] }
+
+        return await backgroundContext.perform {
+            return fetchedResults.map(\.objectID)
+        }
+    }
+
+    /// Returns the NSManagedObjectID of the Temp Target Presets
+    func fetchForTempTargetPresets() async -> [NSManagedObjectID] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: TempTargetStored.self,
+            onContext: backgroundContext,
+            predicate: NSPredicate.allTempTargetPresets,
+            key: "orderPosition",
+            ascending: true
+        )
+
+        guard let fetchedResults = results as? [TempTargetStored] else { return [] }
+
+        return await backgroundContext.perform {
+            return fetchedResults.map(\.objectID)
+        }
+    }
+
+    func fetchScheduledTempTargets() async -> [NSManagedObjectID] {
+        let scheduledTempTargets = NSPredicate(format: "date > %@", Date() as NSDate)
+
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: TempTargetStored.self,
+            onContext: backgroundContext,
+            predicate: scheduledTempTargets,
+            key: "date",
+            ascending: false
+        )
+
+        guard let fetchedResults = results as? [TempTargetStored] else { return [] }
+
+        return await backgroundContext.perform {
+            return fetchedResults.map(\.objectID)
+        }
     }
     }
 
 
-    private func storeTempTargets(_ targets: [TempTarget], isPresets: Bool) {
-        processQueue.sync {
-            var targets = targets
-            if !isPresets {
-                if current() != nil, let newActive = targets.last(where: {
-                    $0.createdAt.addingTimeInterval(Int($0.duration).minutes.timeInterval) > Date()
-                        && $0.createdAt <= Date()
-                }) {
-                    // cancel current
-                    targets += [TempTarget.cancel(at: newActive.createdAt.addingTimeInterval(-1))]
-                }
+    func fetchScheduledTempTarget(for targetDate: Date) async -> [NSManagedObjectID] {
+        let predicate = NSPredicate(format: "date == %@", targetDate as NSDate)
+
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: TempTargetStored.self,
+            onContext: backgroundContext,
+            predicate: predicate,
+            key: "date",
+            ascending: false,
+            fetchLimit: 1
+        )
+
+        guard let fetchedResults = results as? [TempTargetStored] else { return [] }
+
+        return await backgroundContext.perform {
+            fetchedResults.map(\.objectID)
+        }
+    }
+
+    func storeTempTarget(tempTarget: TempTarget) async {
+        var presetCount = -1
+        if tempTarget.isPreset == true {
+            let presets = await fetchForTempTargetPresets()
+            presetCount = presets.count
+        }
+
+        await backgroundContext.perform {
+            let newTempTarget = TempTargetStored(context: self.backgroundContext)
+            newTempTarget.date = tempTarget.createdAt
+            newTempTarget.id = UUID()
+            newTempTarget.enabled = tempTarget.enabled ?? false
+            newTempTarget.duration = tempTarget.duration as NSDecimalNumber
+            newTempTarget.isUploadedToNS = false
+            newTempTarget.name = tempTarget.name
+            newTempTarget.target = NSDecimalNumber(decimal: tempTarget.targetTop ?? 0)
+            newTempTarget.isPreset = tempTarget.isPreset ?? false
+
+            // Nullify half basal target to ensure the latest HBT is used via OpenAPS Manager when sending TT data to oref
+            newTempTarget.halfBasalTarget = nil
+
+            if let halfBasalTarget = tempTarget.halfBasalTarget,
+               halfBasalTarget != self.settingsManager.preferences.halfBasalExerciseTarget
+            {
+                newTempTarget.halfBasalTarget = NSDecimalNumber(decimal: halfBasalTarget)
+            }
+
+            if tempTarget.isPreset == true, presetCount > -1 {
+                newTempTarget.orderPosition = Int16(presetCount + 1)
+            }
+
+            do {
+                guard self.backgroundContext.hasChanges else { return }
+                try self.backgroundContext.save()
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Temp Target to Core Data with error: \(error.userInfo)"
+                )
             }
             }
+        }
+    }
 
 
-            let file = isPresets ? OpenAPS.FreeAPS.tempTargetsPresets : OpenAPS.Settings.tempTargets
+    func saveTempTargetsToStorage(_ targets: [TempTarget]) {
+        processQueue.async {
+            let file = OpenAPS.Settings.tempTargets
             var uniqEvents: [TempTarget] = []
             var uniqEvents: [TempTarget] = []
             self.storage.transaction { storage in
             self.storage.transaction { storage in
                 storage.append(targets, to: file, uniqBy: \.createdAt)
                 storage.append(targets, to: file, uniqBy: \.createdAt)
-                uniqEvents = storage.retrieve(file, as: [TempTarget].self)?
-                    .filter {
-                        guard !isPresets else { return true }
-                        return $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date()
-                    }
-                    .sorted { $0.createdAt > $1.createdAt } ?? []
-                storage.save(Array(uniqEvents), as: file)
+
+                let retrievedTargets = storage.retrieve(file, as: [TempTarget].self) ?? []
+                uniqEvents = retrievedTargets
+                    .filter { $0.isWithinLastDay }
+                    .sorted(by: { $0.createdAt > $1.createdAt })
+
+                storage.save(uniqEvents, as: file)
             }
             }
-            broadcaster.notify(TempTargetsObserver.self, on: processQueue) {
+
+            self.broadcaster.notify(TempTargetsObserver.self, on: self.processQueue) {
                 $0.tempTargetsDidUpdate(uniqEvents)
                 $0.tempTargetsDidUpdate(uniqEvents)
             }
             }
         }
         }
     }
     }
 
 
+    func existsTempTarget(with date: Date) async -> Bool {
+        await backgroundContext.perform {
+            // Fetch all Temp Targets with the given date
+            let fetchRequest: NSFetchRequest<TempTargetStored> = TempTargetStored.fetchRequest()
+            fetchRequest.predicate = NSPredicate(format: "date == %@", date as NSDate)
+
+            do {
+                let results = try self.backgroundContext.fetch(fetchRequest)
+                return !results.isEmpty
+            } catch let error as NSError {
+                debugPrint("\(DebuggingIdentifiers.failed) Failed to check for existing Temp Target: \(error)")
+                return false
+            }
+        }
+    }
+
+    // Copy the current Temp Target if it is a RUNNING Preset
+    /// otherwise we would edit the Preset
+    @MainActor func copyRunningTempTarget(_ tempTarget: TempTargetStored) async -> NSManagedObjectID {
+        let newTempTarget = TempTargetStored(context: viewContext)
+        newTempTarget.date = tempTarget.date
+        newTempTarget.id = tempTarget.id
+        newTempTarget.enabled = tempTarget.enabled
+        newTempTarget.duration = tempTarget.duration
+        newTempTarget.isUploadedToNS = true // to avoid getting duplicates on NS
+        newTempTarget.name = tempTarget.name
+        newTempTarget.target = tempTarget.target
+        newTempTarget.isPreset = false // no Preset
+        newTempTarget.halfBasalTarget = tempTarget.halfBasalTarget != 160 ? tempTarget.halfBasalTarget : nil
+
+        await viewContext.perform {
+            do {
+                guard self.viewContext.hasChanges else { return }
+                try self.viewContext.save()
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to copy Temp Target with error: \(error.userInfo)"
+                )
+            }
+        }
+
+        return newTempTarget.objectID
+    }
+
+    @MainActor func deleteOverridePreset(_ objectID: NSManagedObjectID) async {
+        await CoreDataStack.shared.deleteObject(identifiedBy: objectID)
+    }
+
     func syncDate() -> Date {
     func syncDate() -> Date {
         Date().addingTimeInterval(-1.days.timeInterval)
         Date().addingTimeInterval(-1.days.timeInterval)
     }
     }
@@ -82,38 +242,94 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
         return last
         return last
     }
     }
 
 
-    func nightscoutTreatmentsNotUploaded() -> [NightscoutTreatment] {
-        let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedTempTargets, as: [NightscoutTreatment].self) ?? []
-
-        let eventsManual = recent().filter { $0.enteredBy == TempTarget.manual }
-        let treatments = eventsManual.map {
-            NightscoutTreatment(
-                duration: Int($0.duration),
-                rawDuration: nil,
-                rawRate: nil,
-                absolute: nil,
-                rate: nil,
-                eventType: .nsTempTarget,
-                createdAt: $0.createdAt,
-                enteredBy: TempTarget.manual,
-                bolus: nil,
-                insulin: nil,
-                notes: nil,
-                carbs: nil,
-                targetTop: $0.targetTop,
-                targetBottom: $0.targetBottom
-            )
+    func getTempTargetsNotYetUploadedToNightscout() async -> [NightscoutTreatment] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: TempTargetStored.self,
+            onContext: backgroundContext,
+            predicate: NSPredicate.lastActiveOverrideNotYetUploadedToNightscout, // TODO: create adjustment predicate (OR+TT)
+            key: "date",
+            ascending: false
+        )
+
+        return await backgroundContext.perform {
+            guard let fetchedTempTargets = results as? [TempTargetStored] else { return [] }
+
+            return fetchedTempTargets.map { tempTarget in
+                NightscoutTreatment(
+                    duration: Int(truncating: tempTarget.duration ?? 60),
+                    rawDuration: nil,
+                    rawRate: nil,
+                    absolute: nil,
+                    rate: nil,
+                    eventType: .nsTempTarget,
+                    createdAt: tempTarget.date ?? Date(),
+                    enteredBy: TempTarget.local,
+                    bolus: nil,
+                    insulin: nil,
+                    notes: tempTarget.name ?? TempTarget.custom,
+                    carbs: nil,
+                    targetTop: tempTarget
+                        .target as Decimal? ?? (self.settingsManager.settings.units == .mgdL ? 100.0 : 100.asMmolL),
+                    targetBottom: tempTarget
+                        .target as Decimal? ?? (self.settingsManager.settings.units == .mgdL ? 100.0 : 100.asMmolL)
+                )
+            }
         }
         }
-        return Array(Set(treatments).subtracting(Set(uploaded)))
     }
     }
 
 
-    func storePresets(_ targets: [TempTarget]) {
-        storage.remove(OpenAPS.FreeAPS.tempTargetsPresets)
+    func getTempTargetRunsNotYetUploadedToNightscout() async -> [NightscoutTreatment] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: TempTargetRunStored.self,
+            onContext: backgroundContext,
+            predicate: NSPredicate(
+                format: "startDate >= %@ AND isUploadedToNS == %@",
+                Date.oneDayAgo as NSDate,
+                false as NSNumber
+            ),
+            key: "startDate",
+            ascending: false
+        )
+
+        return await backgroundContext.perform {
+            guard let fetchedTempTargetRuns = results as? [TempTargetRunStored] else { return [] }
 
 
-        storeTempTargets(targets, isPresets: true)
+            return fetchedTempTargetRuns.map { tempTargetRun in
+                var durationInMinutes = (tempTargetRun.endDate?.timeIntervalSince(tempTargetRun.startDate ?? Date()) ?? 1) / 60
+                durationInMinutes = durationInMinutes < 1 ? 1 : durationInMinutes
+                return NightscoutTreatment(
+                    duration: Int(durationInMinutes),
+                    rawDuration: nil,
+                    rawRate: nil,
+                    absolute: nil,
+                    rate: nil,
+                    eventType: .nsTempTarget,
+                    createdAt: (tempTargetRun.startDate ?? tempTargetRun.tempTarget?.date) ?? Date(),
+                    enteredBy: TempTarget.local,
+                    bolus: nil,
+                    insulin: nil,
+                    notes: tempTargetRun.tempTarget?.name ?? TempTarget.custom,
+                    carbs: nil,
+                    targetTop: tempTargetRun
+                        .target as Decimal? ?? (self.settingsManager.settings.units == .mgdL ? 100.0 : 100.asMmolL),
+                    targetBottom: tempTargetRun
+                        .target as Decimal? ?? (self.settingsManager.settings.units == .mgdL ? 100.0 : 100.asMmolL)
+                )
+            }
+        }
     }
     }
 
 
     func presets() -> [TempTarget] {
     func presets() -> [TempTarget] {
         storage.retrieve(OpenAPS.FreeAPS.tempTargetsPresets, as: [TempTarget].self)?.reversed() ?? []
         storage.retrieve(OpenAPS.FreeAPS.tempTargetsPresets, as: [TempTarget].self)?.reversed() ?? []
     }
     }
 }
 }
+
+private extension TempTarget {
+    var isActive: Bool {
+        let expirationTime = createdAt.addingTimeInterval(Int(duration).minutes.timeInterval)
+        return expirationTime > Date() && createdAt <= Date()
+    }
+
+    var isWithinLastDay: Bool {
+        createdAt.addingTimeInterval(1.days.timeInterval) > Date()
+    }
+}

+ 0 - 1
FreeAPS/Sources/Application/AppDelegate.swift

@@ -7,7 +7,6 @@ class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject, UNUserNoti
         _ application: UIApplication,
         _ application: UIApplication,
         didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?
         didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?
     ) -> Bool {
     ) -> Bool {
-        UNUserNotificationCenter.current().delegate = self
         application.registerForRemoteNotifications()
         application.registerForRemoteNotifications()
         return true
         return true
     }
     }

+ 20 - 0
FreeAPS/Sources/Application/AppState.swift

@@ -0,0 +1,20 @@
+import Foundation
+import Observation
+import SwiftUICore
+import UIKit
+
+@Observable class AppState {
+    func trioBackgroundColor(for colorScheme: ColorScheme) -> LinearGradient {
+        colorScheme == .dark
+            ? LinearGradient(
+                gradient: Gradient(colors: [Color.bgDarkBlue, Color.bgDarkerDarkBlue]),
+                startPoint: .top,
+                endPoint: .bottom
+            )
+            : LinearGradient(
+                gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
+                startPoint: .top,
+                endPoint: .bottom
+            )
+    }
+}

+ 22 - 3
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -15,6 +15,8 @@ import Swinject
 
 
     let coreDataStack = CoreDataStack.shared
     let coreDataStack = CoreDataStack.shared
 
 
+    @State private var appState = AppState()
+
     // Dependencies Assembler
     // Dependencies Assembler
     // contain all dependencies Assemblies
     // contain all dependencies Assemblies
     // TODO: Remove static key after update "Use Dependencies" logic
     // TODO: Remove static key after update "Use Dependencies" logic
@@ -51,6 +53,7 @@ import Swinject
         _ = resolver.resolve(HealthKitManager.self)!
         _ = resolver.resolve(HealthKitManager.self)!
         _ = resolver.resolve(BluetoothStateManager.self)!
         _ = resolver.resolve(BluetoothStateManager.self)!
         _ = resolver.resolve(PluginManager.self)!
         _ = resolver.resolve(PluginManager.self)!
+        _ = resolver.resolve(AlertPermissionsChecker.self)!
         if #available(iOS 16.2, *) {
         if #available(iOS 16.2, *) {
             _ = resolver.resolve(LiveActivityBridge.self)!
             _ = resolver.resolve(LiveActivityBridge.self)!
         }
         }
@@ -59,10 +62,15 @@ import Swinject
     init() {
     init() {
         debug(
         debug(
             .default,
             .default,
-            "Trio Started: v\(Bundle.main.releaseVersionNumber ?? "")(\(Bundle.main.buildVersionNumber ?? "")) [buildDate: \(BuildDetails.default.buildDate())] [buildExpires: \(BuildDetails.default.calculateExpirationDate())]"
+            "Trio Started: v\(Bundle.main.releaseVersionNumber ?? "")(\(Bundle.main.buildVersionNumber ?? "")) [buildDate: \(String(describing: BuildDetails.default.buildDate()))] [buildExpires: \(String(describing: BuildDetails.default.calculateExpirationDate()))]"
         )
         )
+
+        // Load services
         loadServices()
         loadServices()
 
 
+        // Fix bug in iOS 18 related to the translucent tab bar
+        configureTabBarAppearance()
+
         // Clear the persistentHistory and the NSManagedObjects that are older than 90 days every time the app starts
         // Clear the persistentHistory and the NSManagedObjects that are older than 90 days every time the app starts
         cleanupOldData()
         cleanupOldData()
     }
     }
@@ -70,12 +78,13 @@ import Swinject
     var body: some Scene {
     var body: some Scene {
         WindowGroup {
         WindowGroup {
             Main.RootView(resolver: resolver)
             Main.RootView(resolver: resolver)
-                .preferredColorScheme(colorScheme(for: colorSchemePreference ?? .systemDefault) ?? nil)
+                .preferredColorScheme(colorScheme(for: colorSchemePreference) ?? nil)
                 .environment(\.managedObjectContext, coreDataStack.persistentContainer.viewContext)
                 .environment(\.managedObjectContext, coreDataStack.persistentContainer.viewContext)
+                .environment(appState)
                 .environmentObject(Icons())
                 .environmentObject(Icons())
                 .onOpenURL(perform: handleURL)
                 .onOpenURL(perform: handleURL)
         }
         }
-        .onChange(of: scenePhase) { newScenePhase in
+        .onChange(of: scenePhase) { _, newScenePhase in
             debug(.default, "APPLICATION PHASE: \(newScenePhase)")
             debug(.default, "APPLICATION PHASE: \(newScenePhase)")
 
 
             /// If the App goes to the background we should ensure that all the changes are saved from the viewContext to the Persistent Container
             /// If the App goes to the background we should ensure that all the changes are saved from the viewContext to the Persistent Container
@@ -89,6 +98,16 @@ import Swinject
         }
         }
     }
     }
 
 
+    func configureTabBarAppearance() {
+        let appearance = UITabBarAppearance()
+        appearance.configureWithDefaultBackground()
+        appearance.backgroundEffect = UIBlurEffect(style: .systemChromeMaterial)
+        appearance.backgroundColor = UIColor.clear
+
+        UITabBar.appearance().standardAppearance = appearance
+        UITabBar.appearance().scrollEdgeAppearance = appearance
+    }
+
     private func colorScheme(for colorScheme: ColorSchemeOption) -> ColorScheme? {
     private func colorScheme(for colorScheme: ColorSchemeOption) -> ColorScheme? {
         switch colorScheme {
         switch colorScheme {
         case .systemDefault:
         case .systemDefault:

+ 1 - 1
FreeAPS/Sources/Assemblies/ServiceAssembly.swift

@@ -20,7 +20,7 @@ final class ServiceAssembly: Assembly {
         container.register(UserNotificationsManager.self) { r in BaseUserNotificationsManager(resolver: r) }
         container.register(UserNotificationsManager.self) { r in BaseUserNotificationsManager(resolver: r) }
         container.register(WatchManager.self) { r in BaseWatchManager(resolver: r) }
         container.register(WatchManager.self) { r in BaseWatchManager(resolver: r) }
         container.register(GarminManager.self) { r in BaseGarminManager(resolver: r) }
         container.register(GarminManager.self) { r in BaseGarminManager(resolver: r) }
-
+        container.register(AlertPermissionsChecker.self) { r in AlertPermissionsChecker(resolver: r) }
         if #available(iOS 16.2, *) {
         if #available(iOS 16.2, *) {
             container.register(LiveActivityBridge.self) { r in
             container.register(LiveActivityBridge.self) { r in
                 LiveActivityBridge(resolver: r)
                 LiveActivityBridge(resolver: r)

+ 4 - 0
FreeAPS/Sources/Helpers/ConstantValues.swift

@@ -0,0 +1,4 @@
+import SwiftUI
+
+// Global constant for list section separation
+let sectionSpacing: CGFloat = 15

+ 6 - 0
FreeAPS/Sources/Helpers/Decimal+Extensions.swift

@@ -13,6 +13,12 @@ extension Int {
     }
     }
 }
 }
 
 
+extension Int16 {
+    var minutes: TimeInterval {
+        TimeInterval(self) * 60
+    }
+}
+
 extension CGFloat {
 extension CGFloat {
     init(_ decimal: Decimal) {
     init(_ decimal: Decimal) {
         self.init(Double(decimal))
         self.init(Double(decimal))

+ 51 - 0
FreeAPS/Sources/Helpers/Formatters.swift

@@ -28,6 +28,57 @@ extension Formatter {
         formatter.formatOptions = [.withInternetDateTime]
         formatter.formatOptions = [.withInternetDateTime]
         return formatter
         return formatter
     }()
     }()
+
+    static let decimalFormatterWithTwoFractionDigits: NumberFormatter = {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 2
+        return formatter
+    }()
+
+    static let dateFormatter: DateFormatter = {
+        let dateFormatter = DateFormatter()
+        dateFormatter.timeStyle = .short
+        return dateFormatter
+    }()
+
+    static let decimalFormatterWithOneFractionDigit: NumberFormatter = {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 1
+        return formatter
+    }()
+
+    static let integerFormatter: NumberFormatter = {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        return formatter
+    }()
+
+    static func glucoseFormatter(for units: GlucoseUnits) -> NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.roundingMode = .halfUp
+
+        switch units {
+        case .mmolL:
+            formatter.maximumFractionDigits = 1
+        case .mgdL:
+            formatter.maximumFractionDigits = 0
+        }
+
+        return formatter
+    }
+
+    static let bolusFormatter: NumberFormatter = {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.minimumIntegerDigits = 0
+        formatter.maximumFractionDigits = 2
+        formatter.decimalSeparator = "."
+        return formatter
+    }()
 }
 }
 
 
 extension JSONDecoder.DateDecodingStrategy {
 extension JSONDecoder.DateDecodingStrategy {

+ 138 - 28
FreeAPS/Sources/Helpers/MainChartHelper.swift

@@ -1,5 +1,7 @@
+import Charts
 import CoreData
 import CoreData
 import Foundation
 import Foundation
+import SwiftUICore
 
 
 enum MainChartHelper {
 enum MainChartHelper {
     // Calculates the glucose value thats the nearest to parameter 'time'
     // Calculates the glucose value thats the nearest to parameter 'time'
@@ -47,53 +49,161 @@ enum MainChartHelper {
         static let minGlucose = 45
         static let minGlucose = 45
     }
     }
 
 
-    static var bolusFormatter: NumberFormatter {
-        let formatter = NumberFormatter()
-        formatter.numberStyle = .decimal
-        formatter.minimumIntegerDigits = 0
-        formatter.maximumFractionDigits = 2
-        formatter.decimalSeparator = "."
-        return formatter
-    }
-
-    static var carbsFormatter: NumberFormatter {
-        let formatter = NumberFormatter()
-        formatter.numberStyle = .decimal
-        formatter.maximumFractionDigits = 0
-        return formatter
-    }
-
     static func bolusOffset(units: GlucoseUnits) -> Decimal {
     static func bolusOffset(units: GlucoseUnits) -> Decimal {
         units == .mgdL ? 30 : 1.66
         units == .mgdL ? 30 : 1.66
     }
     }
 
 
-    static func calculateDuration(objectID: NSManagedObjectID, context: NSManagedObjectContext) -> TimeInterval? {
+    static func calculateDuration(
+        objectID: NSManagedObjectID,
+        attribute: String,
+        context: NSManagedObjectContext
+    ) -> TimeInterval? {
         do {
         do {
-            if let override = try context.existingObject(with: objectID) as? OverrideStored,
-               let overrideDuration = override.duration as? Double, overrideDuration != 0
-            {
-                return TimeInterval(overrideDuration * 60) // return seconds
+            let object = try context.existingObject(with: objectID)
+            if let attributeValue = object.value(forKey: attribute) as? NSDecimalNumber {
+                let doubleValue = attributeValue.doubleValue
+                if doubleValue != 0 {
+                    return TimeInterval(doubleValue * 60) // return seconds
+                }
+            } else {
+                debugPrint("Attribute \(attribute) not found or not of type NSDecimalNumber")
             }
             }
         } catch {
         } catch {
             debugPrint(
             debugPrint(
-                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to calculate Override Target with error: \(error.localizedDescription)"
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to calculate duration for object with error: \(error.localizedDescription)"
             )
             )
         }
         }
+
         return nil
         return nil
     }
     }
 
 
-    static func calculateTarget(objectID: NSManagedObjectID, context: NSManagedObjectContext) -> Decimal? {
+    static func calculateTarget(objectID: NSManagedObjectID, attribute: String, context: NSManagedObjectContext) -> Decimal? {
         do {
         do {
-            if let override = try context.existingObject(with: objectID) as? OverrideStored,
-               let overrideTarget = override.target, overrideTarget != 0
-            {
-                return overrideTarget.decimalValue
+            let object = try context.existingObject(with: objectID)
+            if let attributeValue = object.value(forKey: attribute) as? NSDecimalNumber, attributeValue != 0 {
+                return attributeValue.decimalValue
             }
             }
         } catch {
         } catch {
             debugPrint(
             debugPrint(
-                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to calculate Override Target with error: \(error.localizedDescription)"
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to calculate target for object with error: \(error.localizedDescription)"
             )
             )
         }
         }
         return nil
         return nil
     }
     }
 }
 }
+
+// MARK: - Rule Marks and Charts configurations
+
+extension MainChartView {
+    func drawCurrentTimeMarker() -> some ChartContent {
+        RuleMark(
+            x: .value(
+                "",
+                Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970)),
+                unit: .second
+            )
+        ).lineStyle(.init(lineWidth: 2, dash: [3])).foregroundStyle(Color(.systemGray2))
+    }
+
+    func drawStartRuleMark() -> some ChartContent {
+        RuleMark(
+            x: .value(
+                "",
+                startMarker,
+                unit: .second
+            )
+        ).foregroundStyle(Color.clear)
+    }
+
+    func drawEndRuleMark() -> some ChartContent {
+        RuleMark(
+            x: .value(
+                "",
+                endMarker,
+                unit: .second
+            )
+        ).foregroundStyle(Color.clear)
+    }
+
+    func basalChartPlotStyle(_ plotContent: ChartPlotContent) -> some View {
+        plotContent
+            .rotationEffect(.degrees(180))
+            .scaleEffect(x: -1, y: 1)
+    }
+
+    var mainChartXAxis: some AxisContent {
+        AxisMarks(values: .stride(by: .hour, count: screenHours > 6 ? (screenHours > 12 ? 4 : 2) : 1)) { _ in
+            if displayXgridLines {
+                AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+            } else {
+                AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+            }
+        }
+    }
+
+    var basalChartXAxis: some AxisContent {
+        AxisMarks(values: .stride(by: .hour, count: screenHours > 6 ? (screenHours > 12 ? 4 : 2) : 1)) { _ in
+            if displayXgridLines {
+                AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+            } else {
+                AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+            }
+            AxisValueLabel(format: .dateTime.hour(.defaultDigits(amPM: .narrow)), anchor: .top)
+                .font(.footnote).foregroundStyle(Color.primary)
+        }
+    }
+
+    var mainChartYAxis: some AxisContent {
+        AxisMarks(position: .trailing) { value in
+
+            if displayYgridLines {
+                AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+            } else {
+                AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+            }
+
+            if let glucoseValue = value.as(Double.self), glucoseValue > 0 {
+                /// fix offset between the two charts...
+                if units == .mmolL {
+                    AxisTick(length: 7, stroke: .init(lineWidth: 7)).foregroundStyle(Color.clear)
+                }
+                AxisValueLabel().font(.footnote).foregroundStyle(Color.primary)
+            }
+        }
+    }
+
+    var cobIobChartYAxis: some AxisContent {
+        AxisMarks(position: .trailing) { _ in
+            if displayYgridLines {
+                AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+            } else {
+                AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+            }
+        }
+    }
+}
+
+// MARK: - Calculations and formatting
+
+extension MainChartView {
+    func fullWidth(viewWidth: CGFloat) -> CGFloat {
+        viewWidth * CGFloat(hours) / CGFloat(min(max(screenHours, 2), 24))
+    }
+
+    // Update start and  end marker to fix scroll update problem with x axis
+    func updateStartEndMarkers() {
+        startMarker = Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970 - 86400))
+
+        let threeHourSinceNow = Date(timeIntervalSinceNow: TimeInterval(hours: 3))
+
+        // min is 1.5h -> (1.5*1h = 1.5*(5*12*60))
+        let dynamicFutureDateForCone = Date(timeIntervalSinceNow: TimeInterval(
+            Int(1.5) * 5 * state
+                .minCount * 60
+        ))
+
+        endMarker = state
+            .forecastDisplayType == .lines ? threeHourSinceNow : dynamicFutureDateForCone <= threeHourSinceNow ?
+            dynamicFutureDateForCone.addingTimeInterval(TimeInterval(minutes: 30)) : threeHourSinceNow
+    }
+}

+ 1 - 1
FreeAPS/Sources/Helpers/ProgressBar.swift

@@ -17,7 +17,7 @@ struct ProgressBar: View {
                         height: geometry.size.height
                         height: geometry.size.height
                     )
                     )
                     .foregroundColor(.accentColor)
                     .foregroundColor(.accentColor)
-                    .animation(.linear)
+                    .animation(.linear, value: value)
             }
             }
         }
         }
         .frame(height: 20)
         .frame(height: 20)

Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings


+ 1 - 1
FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings

@@ -110,7 +110,7 @@
 "Duration" = "Duration";
 "Duration" = "Duration";
 
 
 /*  */
 /*  */
-"Enact Temp Target" = "Enact Temp Target";
+"Start Temp Target" = "Start Temp Target";
 
 
 /* */
 /* */
 "Target" = "Target";
 "Target" = "Target";

Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings


+ 1 - 1
FreeAPS/Sources/Models/AlertEntry.swift

@@ -15,7 +15,7 @@ struct AlertEntry: JSON, Codable, Hashable {
     let contentBody: String?
     let contentBody: String?
     var errorMessage: String?
     var errorMessage: String?
 
 
-    static let manual = "Trio"
+    static let local = "Trio"
 
 
     static func == (lhs: AlertEntry, rhs: AlertEntry) -> Bool {
     static func == (lhs: AlertEntry, rhs: AlertEntry) -> Bool {
         lhs.issuedDate == rhs.issuedDate
         lhs.issuedDate == rhs.issuedDate

+ 2 - 0
FreeAPS/Sources/Models/Battery.swift

@@ -10,4 +10,6 @@ struct Battery: JSON {
 enum BatteryState: String, JSON {
 enum BatteryState: String, JSON {
     case normal
     case normal
     case low
     case low
+    case unknown
+    case error
 }
 }

+ 2 - 2
FreeAPS/Sources/Models/CarbsEntry.swift

@@ -13,7 +13,7 @@ struct CarbsEntry: JSON, Equatable, Hashable, Identifiable {
     let isFPU: Bool?
     let isFPU: Bool?
     let fpuID: String?
     let fpuID: String?
 
 
-    static let manual = "Trio"
+    static let local = "Trio"
     static let appleHealth = "applehealth"
     static let appleHealth = "applehealth"
 
 
     static func == (lhs: CarbsEntry, rhs: CarbsEntry) -> Bool {
     static func == (lhs: CarbsEntry, rhs: CarbsEntry) -> Bool {
@@ -33,7 +33,7 @@ extension CarbsEntry {
         case carbs
         case carbs
         case fat
         case fat
         case protein
         case protein
-        case note
+        case note = "notes"
         case enteredBy
         case enteredBy
         case isFPU
         case isFPU
         case fpuID
         case fpuID

+ 9 - 9
FreeAPS/Sources/Models/DecimalPickerSettings.swift

@@ -49,8 +49,8 @@ struct DecimalPickerSettings {
     var maxFat = PickerSetting(value: 250, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
     var maxFat = PickerSetting(value: 250, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
     var maxProtein = PickerSetting(value: 250, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
     var maxProtein = PickerSetting(value: 250, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
     var overrideFactor = PickerSetting(value: 0.8, step: 0.05, min: 0.5, max: 1.5, type: PickerSetting.PickerSettingType.factor)
     var overrideFactor = PickerSetting(value: 0.8, step: 0.05, min: 0.5, max: 1.5, type: PickerSetting.PickerSettingType.factor)
-    var fattyMealFactor = PickerSetting(value: 0.7, step: 0.05, min: 0.5, max: 2, type: PickerSetting.PickerSettingType.factor)
-    var sweetMealFactor = PickerSetting(value: 2, step: 0.05, min: 1, max: 3, type: PickerSetting.PickerSettingType.factor)
+    var fattyMealFactor = PickerSetting(value: 0.7, step: 0.05, min: 0.05, max: 1, type: PickerSetting.PickerSettingType.factor)
+    var sweetMealFactor = PickerSetting(value: 1, step: 0.05, min: 0.05, max: 2, type: PickerSetting.PickerSettingType.factor)
     var maxIOB = PickerSetting(value: 0, step: 1, min: 0, max: 20, type: PickerSetting.PickerSettingType.insulinUnit)
     var maxIOB = PickerSetting(value: 0, step: 1, min: 0, max: 20, type: PickerSetting.PickerSettingType.insulinUnit)
     var maxDailySafetyMultiplier = PickerSetting(
     var maxDailySafetyMultiplier = PickerSetting(
         value: 3,
         value: 3,
@@ -71,9 +71,9 @@ struct DecimalPickerSettings {
     var smbDeliveryRatio = PickerSetting(value: 0.5, step: 0.05, min: 0.3, max: 0.7, type: PickerSetting.PickerSettingType.factor)
     var smbDeliveryRatio = PickerSetting(value: 0.5, step: 0.05, min: 0.3, max: 0.7, type: PickerSetting.PickerSettingType.factor)
     var halfBasalExerciseTarget = PickerSetting(
     var halfBasalExerciseTarget = PickerSetting(
         value: 160,
         value: 160,
-        step: 1,
+        step: 5,
         min: 100,
         min: 100,
-        max: 200,
+        max: 300,
         type: PickerSetting.PickerSettingType.glucose
         type: PickerSetting.PickerSettingType.glucose
     )
     )
     var maxCOB = PickerSetting(value: 120, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
     var maxCOB = PickerSetting(value: 120, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
@@ -119,15 +119,15 @@ struct DecimalPickerSettings {
         max: 0.4,
         max: 0.4,
         type: PickerSetting.PickerSettingType.factor
         type: PickerSetting.PickerSettingType.factor
     )
     )
-    var adjustmentFactor = PickerSetting(value: 0.8, step: 0.1, min: 0.5, max: 1.5, type: PickerSetting.PickerSettingType.factor)
+    var adjustmentFactor = PickerSetting(value: 0.8, step: 0.05, min: 0.3, max: 1.5, type: PickerSetting.PickerSettingType.factor)
     var adjustmentFactorSigmoid = PickerSetting(
     var adjustmentFactorSigmoid = PickerSetting(
         value: 0.5,
         value: 0.5,
-        step: 0.1,
-        min: 0.5,
+        step: 0.05,
+        min: 0.1,
         max: 2,
         max: 2,
         type: PickerSetting.PickerSettingType.factor
         type: PickerSetting.PickerSettingType.factor
     )
     )
-    var weightPercentage = PickerSetting(value: 0.65, step: 0.1, min: 0.1, max: 1, type: PickerSetting.PickerSettingType.factor)
+    var weightPercentage = PickerSetting(value: 0.35, step: 0.05, min: 0.05, max: 1, type: PickerSetting.PickerSettingType.factor)
     var enableSMB_high_bg_target = PickerSetting(
     var enableSMB_high_bg_target = PickerSetting(
         value: 110,
         value: 110,
         step: 1,
         step: 1,
@@ -141,7 +141,7 @@ struct DecimalPickerSettings {
     var minuteInterval = PickerSetting(value: 20, step: 5, min: 5, max: 60, type: PickerSetting.PickerSettingType.minute)
     var minuteInterval = PickerSetting(value: 20, step: 5, min: 5, max: 60, type: PickerSetting.PickerSettingType.minute)
     var timeCap = PickerSetting(value: 8, step: 1, min: 5, max: 12, type: PickerSetting.PickerSettingType.hour)
     var timeCap = PickerSetting(value: 8, step: 1, min: 5, max: 12, type: PickerSetting.PickerSettingType.hour)
     var hours = PickerSetting(value: 6, step: 0.5, min: 2, max: 24, type: PickerSetting.PickerSettingType.hour)
     var hours = PickerSetting(value: 6, step: 0.5, min: 2, max: 24, type: PickerSetting.PickerSettingType.hour)
-    var dia = PickerSetting(value: 6, step: 0.5, min: 4, max: 10, type: PickerSetting.PickerSettingType.hour)
+    var dia = PickerSetting(value: 10, step: 0.5, min: 5, max: 10, type: PickerSetting.PickerSettingType.hour)
     var maxBolus = PickerSetting(value: 10, step: 0.5, min: 1, max: 30, type: PickerSetting.PickerSettingType.insulinUnit)
     var maxBolus = PickerSetting(value: 10, step: 0.5, min: 1, max: 30, type: PickerSetting.PickerSettingType.insulinUnit)
     var maxBasal = PickerSetting(value: 10, step: 0.5, min: 1, max: 30, type: PickerSetting.PickerSettingType.insulinUnit)
     var maxBasal = PickerSetting(value: 10, step: 0.5, min: 1, max: 30, type: PickerSetting.PickerSettingType.insulinUnit)
 }
 }

+ 27 - 7
FreeAPS/Sources/Models/FreeAPSSettings.swift

@@ -33,6 +33,10 @@ struct FreeAPSSettings: JSON, Equatable {
     var displayCalendarIOBandCOB: Bool = false
     var displayCalendarIOBandCOB: Bool = false
     var displayCalendarEmojis: Bool = false
     var displayCalendarEmojis: Bool = false
     var glucoseBadge: Bool = false
     var glucoseBadge: Bool = false
+    var notificationsPump: Bool = true
+    var notificationsCgm: Bool = true
+    var notificationsCarb: Bool = true
+    var notificationsAlgorithm: Bool = true
     var glucoseNotificationsAlways: Bool = false
     var glucoseNotificationsAlways: Bool = false
     var useAlarmSound: Bool = false
     var useAlarmSound: Bool = false
     var addSourceInfoToGlucoseNotifications: Bool = false
     var addSourceInfoToGlucoseNotifications: Bool = false
@@ -49,14 +53,14 @@ struct FreeAPSSettings: JSON, Equatable {
     var useAppleHealth: Bool = false
     var useAppleHealth: Bool = false
     var smoothGlucose: Bool = false
     var smoothGlucose: Bool = false
     var displayOnWatch: AwConfig = .BGTarget
     var displayOnWatch: AwConfig = .BGTarget
-    var overrideHbA1cUnit: Bool = false
+    var hbA1cDisplayUnit: HbA1cDisplayUnit = .percent
     var high: Decimal = 180
     var high: Decimal = 180
     var low: Decimal = 70
     var low: Decimal = 70
     var hours: Int = 6
     var hours: Int = 6
     var glucoseColorScheme: GlucoseColorScheme = .staticColor
     var glucoseColorScheme: GlucoseColorScheme = .staticColor
     var xGridLines: Bool = true
     var xGridLines: Bool = true
     var yGridLines: Bool = true
     var yGridLines: Bool = true
-    var oneDimensionalGraph: Bool = false
+    var timeInRangeChartStyle: TimeInRangeChartStyle = .vertical
     var rulerMarks: Bool = true
     var rulerMarks: Bool = true
     var forecastDisplayType: ForecastDisplayType = .cone
     var forecastDisplayType: ForecastDisplayType = .cone
     var maxCarbs: Decimal = 250
     var maxCarbs: Decimal = 250
@@ -69,7 +73,7 @@ struct FreeAPSSettings: JSON, Equatable {
     var fattyMeals: Bool = false
     var fattyMeals: Bool = false
     var fattyMealFactor: Decimal = 0.7
     var fattyMealFactor: Decimal = 0.7
     var sweetMeals: Bool = false
     var sweetMeals: Bool = false
-    var sweetMealFactor: Decimal = 2
+    var sweetMealFactor: Decimal = 1
     var displayPresets: Bool = true
     var displayPresets: Bool = true
     var useLiveActivity: Bool = false
     var useLiveActivity: Bool = false
     var lockScreenView: LockScreenView = .simple
     var lockScreenView: LockScreenView = .simple
@@ -204,6 +208,22 @@ extension FreeAPSSettings: Decodable {
             settings.delay = delay
             settings.delay = delay
         }
         }
 
 
+        if let notificationsPump = try? container.decode(Bool.self, forKey: .notificationsPump) {
+            settings.notificationsPump = notificationsPump
+        }
+
+        if let notificationsCgm = try? container.decode(Bool.self, forKey: .notificationsCgm) {
+            settings.notificationsCgm = notificationsCgm
+        }
+
+        if let notificationsCarb = try? container.decode(Bool.self, forKey: .notificationsCarb) {
+            settings.notificationsCarb = notificationsCarb
+        }
+
+        if let notificationsAlgorithm = try? container.decode(Bool.self, forKey: .notificationsAlgorithm) {
+            settings.notificationsAlgorithm = notificationsAlgorithm
+        }
+
         if let glucoseNotificationsAlways = try? container.decode(Bool.self, forKey: .glucoseNotificationsAlways) {
         if let glucoseNotificationsAlways = try? container.decode(Bool.self, forKey: .glucoseNotificationsAlways) {
             settings.glucoseNotificationsAlways = glucoseNotificationsAlways
             settings.glucoseNotificationsAlways = glucoseNotificationsAlways
         }
         }
@@ -263,8 +283,8 @@ extension FreeAPSSettings: Decodable {
             settings.yGridLines = yGridLines
             settings.yGridLines = yGridLines
         }
         }
 
 
-        if let oneDimensionalGraph = try? container.decode(Bool.self, forKey: .oneDimensionalGraph) {
-            settings.oneDimensionalGraph = oneDimensionalGraph
+        if let timeInRangeChartStyle = try? container.decode(TimeInRangeChartStyle.self, forKey: .timeInRangeChartStyle) {
+            settings.timeInRangeChartStyle = timeInRangeChartStyle
         }
         }
 
 
         if let rulerMarks = try? container.decode(Bool.self, forKey: .rulerMarks) {
         if let rulerMarks = try? container.decode(Bool.self, forKey: .rulerMarks) {
@@ -275,8 +295,8 @@ extension FreeAPSSettings: Decodable {
             settings.forecastDisplayType = forecastDisplayType
             settings.forecastDisplayType = forecastDisplayType
         }
         }
 
 
-        if let overrideHbA1cUnit = try? container.decode(Bool.self, forKey: .overrideHbA1cUnit) {
-            settings.overrideHbA1cUnit = overrideHbA1cUnit
+        if let hbA1cDisplayUnit = try? container.decode(HbA1cDisplayUnit.self, forKey: .hbA1cDisplayUnit) {
+            settings.hbA1cDisplayUnit = hbA1cDisplayUnit
         }
         }
 
 
         if let maxCarbs = try? container.decode(Decimal.self, forKey: .maxCarbs) {
         if let maxCarbs = try? container.decode(Decimal.self, forKey: .maxCarbs) {

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

@@ -0,0 +1,16 @@
+import Foundation
+
+enum HbA1cDisplayUnit: String, JSON, CaseIterable, Identifiable, Codable, Hashable {
+    var id: String { rawValue }
+    case percent
+    case mmolMol
+
+    var displayName: String {
+        switch self {
+        case .percent:
+            return NSLocalizedString("Percent", comment: "")
+        case .mmolMol:
+            return NSLocalizedString("mmol/mol", comment: "")
+        }
+    }
+}

+ 1 - 0
FreeAPS/Sources/Models/NightscoutStatus.swift

@@ -24,6 +24,7 @@ struct NSPumpStatus: JSON {
 struct Uploader: JSON {
 struct Uploader: JSON {
     let batteryVoltage: Decimal?
     let batteryVoltage: Decimal?
     let battery: Int
     let battery: Int
+    let isCharging: Bool?
 }
 }
 
 
 struct NightscoutTimevalue: JSON {
 struct NightscoutTimevalue: JSON {

+ 12 - 12
FreeAPS/Sources/Models/Oref2_variables.swift

@@ -5,19 +5,19 @@ struct Oref2_variables: JSON, Equatable {
     var weightedAverage: Decimal
     var weightedAverage: Decimal
     var past2hoursAverage: Decimal
     var past2hoursAverage: Decimal
     var date: Date
     var date: Date
-    var isEnabled: Bool
-    var presetActive: Bool
     var overridePercentage: Decimal
     var overridePercentage: Decimal
     var useOverride: Bool
     var useOverride: Bool
     var duration: Decimal
     var duration: Decimal
     var unlimited: Bool
     var unlimited: Bool
-    var hbt: Decimal
     var overrideTarget: Decimal
     var overrideTarget: Decimal
     var smbIsOff: Bool
     var smbIsOff: Bool
     var advancedSettings: Bool
     var advancedSettings: Bool
     var isfAndCr: Bool
     var isfAndCr: Bool
     var isf: Bool
     var isf: Bool
     var cr: Bool
     var cr: Bool
+    var smbIsScheduledOff: Bool
+    var start: Decimal
+    var end: Decimal
     var smbMinutes: Decimal
     var smbMinutes: Decimal
     var uamMinutes: Decimal
     var uamMinutes: Decimal
 
 
@@ -26,19 +26,19 @@ struct Oref2_variables: JSON, Equatable {
         weightedAverage: Decimal,
         weightedAverage: Decimal,
         past2hoursAverage: Decimal,
         past2hoursAverage: Decimal,
         date: Date,
         date: Date,
-        isEnabled: Bool,
-        presetActive: Bool,
         overridePercentage: Decimal,
         overridePercentage: Decimal,
         useOverride: Bool,
         useOverride: Bool,
         duration: Decimal,
         duration: Decimal,
         unlimited: Bool,
         unlimited: Bool,
-        hbt: Decimal,
         overrideTarget: Decimal,
         overrideTarget: Decimal,
         smbIsOff: Bool,
         smbIsOff: Bool,
         advancedSettings: Bool,
         advancedSettings: Bool,
         isfAndCr: Bool,
         isfAndCr: Bool,
         isf: Bool,
         isf: Bool,
         cr: Bool,
         cr: Bool,
+        smbIsScheduledOff: Bool,
+        start: Decimal,
+        end: Decimal,
         smbMinutes: Decimal,
         smbMinutes: Decimal,
         uamMinutes: Decimal
         uamMinutes: Decimal
     ) {
     ) {
@@ -46,19 +46,19 @@ struct Oref2_variables: JSON, Equatable {
         self.weightedAverage = weightedAverage
         self.weightedAverage = weightedAverage
         self.past2hoursAverage = past2hoursAverage
         self.past2hoursAverage = past2hoursAverage
         self.date = date
         self.date = date
-        self.isEnabled = isEnabled
-        self.presetActive = presetActive
         self.overridePercentage = overridePercentage
         self.overridePercentage = overridePercentage
         self.useOverride = useOverride
         self.useOverride = useOverride
         self.duration = duration
         self.duration = duration
         self.unlimited = unlimited
         self.unlimited = unlimited
-        self.hbt = hbt
         self.overrideTarget = overrideTarget
         self.overrideTarget = overrideTarget
         self.smbIsOff = smbIsOff
         self.smbIsOff = smbIsOff
         self.advancedSettings = advancedSettings
         self.advancedSettings = advancedSettings
         self.isfAndCr = isfAndCr
         self.isfAndCr = isfAndCr
         self.isf = isf
         self.isf = isf
         self.cr = cr
         self.cr = cr
+        self.smbIsScheduledOff = smbIsScheduledOff
+        self.start = start
+        self.end = end
         self.smbMinutes = smbMinutes
         self.smbMinutes = smbMinutes
         self.uamMinutes = uamMinutes
         self.uamMinutes = uamMinutes
     }
     }
@@ -70,19 +70,19 @@ extension Oref2_variables {
         case weightedAverage
         case weightedAverage
         case past2hoursAverage
         case past2hoursAverage
         case date
         case date
-        case isEnabled
-        case presetActive
         case overridePercentage
         case overridePercentage
         case useOverride
         case useOverride
         case duration
         case duration
         case unlimited
         case unlimited
-        case hbt
         case overrideTarget
         case overrideTarget
         case smbIsOff
         case smbIsOff
         case advancedSettings
         case advancedSettings
         case isfAndCr
         case isfAndCr
         case isf
         case isf
         case cr
         case cr
+        case smbIsScheduledOff
+        case start
+        case end
         case smbMinutes
         case smbMinutes
         case uamMinutes
         case uamMinutes
     }
     }

+ 1 - 1
FreeAPS/Sources/Models/Override.swift

@@ -16,7 +16,7 @@ struct Override {
     let isfAndCr: Bool
     let isfAndCr: Bool
     let isf: Bool
     let isf: Bool
     let cr: Bool
     let cr: Bool
-    let smbIsAlwaysOff: Bool
+    let smbIsScheduledOff: Bool
     let start: Decimal
     let start: Decimal
     let end: Decimal
     let end: Decimal
     let smbMinutes: Decimal
     let smbMinutes: Decimal

+ 219 - 2
FreeAPS/Sources/Models/Preferences.swift

@@ -1,6 +1,6 @@
 import Foundation
 import Foundation
 
 
-struct Preferences: JSON {
+struct Preferences: JSON, Equatable {
     var maxIOB: Decimal = 0
     var maxIOB: Decimal = 0
     var maxDailySafetyMultiplier: Decimal = 3
     var maxDailySafetyMultiplier: Decimal = 3
     var currentBasalSafetyMultiplier: Decimal = 4
     var currentBasalSafetyMultiplier: Decimal = 4
@@ -48,7 +48,7 @@ struct Preferences: JSON {
     var enableDynamicCR: Bool = false
     var enableDynamicCR: Bool = false
     var useNewFormula: Bool = false
     var useNewFormula: Bool = false
     var useWeightedAverage: Bool = false
     var useWeightedAverage: Bool = false
-    var weightPercentage: Decimal = 0.65
+    var weightPercentage: Decimal = 0.35
     var tddAdjBasal: Bool = false
     var tddAdjBasal: Bool = false
     var enableSMB_high_bg: Bool = false
     var enableSMB_high_bg: Bool = false
     var enableSMB_high_bg_target: Decimal = 110
     var enableSMB_high_bg_target: Decimal = 110
@@ -120,3 +120,220 @@ enum InsulinCurve: String, JSON, Identifiable, CaseIterable {
 
 
     var id: InsulinCurve { self }
     var id: InsulinCurve { self }
 }
 }
+
+extension Preferences: Decodable {
+    init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        var preferences = Preferences()
+
+        if let maxIOB = try? container.decode(Decimal.self, forKey: .maxIOB) {
+            preferences.maxIOB = maxIOB
+        }
+
+        if let maxDailySafetyMultiplier = try? container.decode(Decimal.self, forKey: .maxDailySafetyMultiplier) {
+            preferences.maxDailySafetyMultiplier = maxDailySafetyMultiplier
+        }
+
+        if let currentBasalSafetyMultiplier = try? container.decode(Decimal.self, forKey: .currentBasalSafetyMultiplier) {
+            preferences.currentBasalSafetyMultiplier = currentBasalSafetyMultiplier
+        }
+
+        if let autosensMax = try? container.decode(Decimal.self, forKey: .autosensMax) {
+            preferences.autosensMax = autosensMax
+        }
+
+        if let autosensMin = try? container.decode(Decimal.self, forKey: .autosensMin) {
+            preferences.autosensMin = autosensMin
+        }
+
+        if let smbDeliveryRatio = try? container.decode(Decimal.self, forKey: .smbDeliveryRatio) {
+            preferences.smbDeliveryRatio = smbDeliveryRatio
+        }
+
+        if let rewindResetsAutosens = try? container.decode(Bool.self, forKey: .rewindResetsAutosens) {
+            preferences.rewindResetsAutosens = rewindResetsAutosens
+        }
+
+        if let highTemptargetRaisesSensitivity = try? container.decode(Bool.self, forKey: .highTemptargetRaisesSensitivity) {
+            preferences.highTemptargetRaisesSensitivity = highTemptargetRaisesSensitivity
+        }
+
+        if let lowTemptargetLowersSensitivity = try? container.decode(Bool.self, forKey: .lowTemptargetLowersSensitivity) {
+            preferences.lowTemptargetLowersSensitivity = lowTemptargetLowersSensitivity
+        }
+
+        if let sensitivityRaisesTarget = try? container.decode(Bool.self, forKey: .sensitivityRaisesTarget) {
+            preferences.sensitivityRaisesTarget = sensitivityRaisesTarget
+        }
+
+        if let resistanceLowersTarget = try? container.decode(Bool.self, forKey: .resistanceLowersTarget) {
+            preferences.resistanceLowersTarget = resistanceLowersTarget
+        }
+
+        if let advTargetAdjustments = try? container.decode(Bool.self, forKey: .advTargetAdjustments) {
+            preferences.advTargetAdjustments = advTargetAdjustments
+        }
+
+        if let exerciseMode = try? container.decode(Bool.self, forKey: .exerciseMode) {
+            preferences.exerciseMode = exerciseMode
+        }
+
+        if let halfBasalExerciseTarget = try? container.decode(Decimal.self, forKey: .halfBasalExerciseTarget) {
+            preferences.halfBasalExerciseTarget = halfBasalExerciseTarget
+        }
+
+        if let maxCOB = try? container.decode(Decimal.self, forKey: .maxCOB) {
+            preferences.maxCOB = maxCOB
+        }
+
+        if let wideBGTargetRange = try? container.decode(Bool.self, forKey: .wideBGTargetRange) {
+            preferences.wideBGTargetRange = wideBGTargetRange
+        }
+
+        if let skipNeutralTemps = try? container.decode(Bool.self, forKey: .skipNeutralTemps) {
+            preferences.skipNeutralTemps = skipNeutralTemps
+        }
+
+        if let unsuspendIfNoTemp = try? container.decode(Bool.self, forKey: .unsuspendIfNoTemp) {
+            preferences.unsuspendIfNoTemp = unsuspendIfNoTemp
+        }
+
+        if let min5mCarbimpact = try? container.decode(Decimal.self, forKey: .min5mCarbimpact) {
+            preferences.min5mCarbimpact = min5mCarbimpact
+        }
+
+        if let autotuneISFAdjustmentFraction = try? container.decode(Decimal.self, forKey: .autotuneISFAdjustmentFraction) {
+            preferences.autotuneISFAdjustmentFraction = autotuneISFAdjustmentFraction
+        }
+
+        if let remainingCarbsFraction = try? container.decode(Decimal.self, forKey: .remainingCarbsFraction) {
+            preferences.remainingCarbsFraction = remainingCarbsFraction
+        }
+
+        if let remainingCarbsCap = try? container.decode(Decimal.self, forKey: .remainingCarbsCap) {
+            preferences.remainingCarbsCap = remainingCarbsCap
+        }
+
+        if let enableUAM = try? container.decode(Bool.self, forKey: .enableUAM) {
+            preferences.enableUAM = enableUAM
+        }
+
+        if let a52RiskEnable = try? container.decode(Bool.self, forKey: .a52RiskEnable) {
+            preferences.a52RiskEnable = a52RiskEnable
+        }
+
+        if let enableSMBWithCOB = try? container.decode(Bool.self, forKey: .enableSMBWithCOB) {
+            preferences.enableSMBWithCOB = enableSMBWithCOB
+        }
+
+        if let enableSMBWithTemptarget = try? container.decode(Bool.self, forKey: .enableSMBWithTemptarget) {
+            preferences.enableSMBWithTemptarget = enableSMBWithTemptarget
+        }
+
+        if let enableSMBAlways = try? container.decode(Bool.self, forKey: .enableSMBAlways) {
+            preferences.enableSMBAlways = enableSMBAlways
+        }
+
+        if let enableSMBAfterCarbs = try? container.decode(Bool.self, forKey: .enableSMBAfterCarbs) {
+            preferences.enableSMBAfterCarbs = enableSMBAfterCarbs
+        }
+
+        if let allowSMBWithHighTemptarget = try? container.decode(Bool.self, forKey: .allowSMBWithHighTemptarget) {
+            preferences.allowSMBWithHighTemptarget = allowSMBWithHighTemptarget
+        }
+
+        if let maxSMBBasalMinutes = try? container.decode(Decimal.self, forKey: .maxSMBBasalMinutes) {
+            preferences.maxSMBBasalMinutes = maxSMBBasalMinutes
+        }
+
+        if let maxUAMSMBBasalMinutes = try? container.decode(Decimal.self, forKey: .maxUAMSMBBasalMinutes) {
+            preferences.maxUAMSMBBasalMinutes = maxUAMSMBBasalMinutes
+        }
+
+        if let smbInterval = try? container.decode(Decimal.self, forKey: .smbInterval) {
+            preferences.smbInterval = smbInterval
+        }
+
+        if let bolusIncrement = try? container.decode(Decimal.self, forKey: .bolusIncrement) {
+            preferences.bolusIncrement = bolusIncrement
+        }
+
+        if let curve = try? container.decode(InsulinCurve.self, forKey: .curve) {
+            preferences.curve = curve
+        }
+
+        if let useCustomPeakTime = try? container.decode(Bool.self, forKey: .useCustomPeakTime) {
+            preferences.useCustomPeakTime = useCustomPeakTime
+        }
+
+        if let insulinPeakTime = try? container.decode(Decimal.self, forKey: .insulinPeakTime) {
+            preferences.insulinPeakTime = insulinPeakTime
+        }
+
+        if let carbsReqThreshold = try? container.decode(Decimal.self, forKey: .carbsReqThreshold) {
+            preferences.carbsReqThreshold = carbsReqThreshold
+        }
+
+        if let noisyCGMTargetMultiplier = try? container.decode(Decimal.self, forKey: .noisyCGMTargetMultiplier) {
+            preferences.noisyCGMTargetMultiplier = noisyCGMTargetMultiplier
+        }
+
+        if let suspendZerosIOB = try? container.decode(Bool.self, forKey: .suspendZerosIOB) {
+            preferences.suspendZerosIOB = suspendZerosIOB
+        }
+
+        if let maxDeltaBGthreshold = try? container.decode(Decimal.self, forKey: .maxDeltaBGthreshold) {
+            preferences.maxDeltaBGthreshold = maxDeltaBGthreshold
+        }
+
+        if let adjustmentFactor = try? container.decode(Decimal.self, forKey: .adjustmentFactor) {
+            preferences.adjustmentFactor = adjustmentFactor
+        }
+
+        if let adjustmentFactorSigmoid = try? container.decode(Decimal.self, forKey: .adjustmentFactorSigmoid) {
+            preferences.adjustmentFactorSigmoid = adjustmentFactorSigmoid
+        }
+
+        if let sigmoid = try? container.decode(Bool.self, forKey: .sigmoid) {
+            preferences.sigmoid = sigmoid
+        }
+
+        if let enableDynamicCR = try? container.decode(Bool.self, forKey: .enableDynamicCR) {
+            preferences.enableDynamicCR = enableDynamicCR
+        }
+
+        if let useNewFormula = try? container.decode(Bool.self, forKey: .useNewFormula) {
+            preferences.useNewFormula = useNewFormula
+        }
+
+        if let useWeightedAverage = try? container.decode(Bool.self, forKey: .useWeightedAverage) {
+            preferences.useWeightedAverage = useWeightedAverage
+        }
+
+        if let weightPercentage = try? container.decode(Decimal.self, forKey: .weightPercentage) {
+            preferences.weightPercentage = weightPercentage
+        }
+
+        if let tddAdjBasal = try? container.decode(Bool.self, forKey: .tddAdjBasal) {
+            preferences.tddAdjBasal = tddAdjBasal
+        }
+
+        if let enableSMB_high_bg = try? container.decode(Bool.self, forKey: .enableSMB_high_bg) {
+            preferences.enableSMB_high_bg = enableSMB_high_bg
+        }
+
+        if let enableSMB_high_bg_target = try? container.decode(Decimal.self, forKey: .enableSMB_high_bg_target) {
+            preferences.enableSMB_high_bg_target = enableSMB_high_bg_target
+        }
+
+        if let threshold_setting = try? container.decode(Decimal.self, forKey: .threshold_setting) {
+            preferences.threshold_setting = threshold_setting
+        }
+
+        if let updateInterval = try? container.decode(Decimal.self, forKey: .updateInterval) {
+            preferences.updateInterval = updateInterval
+        }
+
+        self = preferences
+    }
+}

+ 13 - 4
FreeAPS/Sources/Models/TempTarget.swift

@@ -9,9 +9,12 @@ struct TempTarget: JSON, Identifiable, Equatable, Hashable {
     let duration: Decimal
     let duration: Decimal
     let enteredBy: String?
     let enteredBy: String?
     let reason: String?
     let reason: String?
+    let isPreset: Bool?
+    var enabled: Bool?
+    let halfBasalTarget: Decimal?
 
 
-    static let manual = "Trio"
-    static let custom = "Temp target"
+    static let local = "Trio"
+    static let custom = "Temp Target"
     static let cancel = "Cancel"
     static let cancel = "Cancel"
 
 
     var displayName: String {
     var displayName: String {
@@ -33,8 +36,11 @@ struct TempTarget: JSON, Identifiable, Equatable, Hashable {
             targetTop: 0,
             targetTop: 0,
             targetBottom: 0,
             targetBottom: 0,
             duration: 0,
             duration: 0,
-            enteredBy: TempTarget.manual,
-            reason: TempTarget.cancel
+            enteredBy: TempTarget.local,
+            reason: TempTarget.cancel,
+            isPreset: nil,
+            enabled: nil,
+            halfBasalTarget: 160
         )
         )
     }
     }
 }
 }
@@ -49,5 +55,8 @@ extension TempTarget {
         case duration
         case duration
         case enteredBy
         case enteredBy
         case reason
         case reason
+        case isPreset
+        case enabled
+        case halfBasalTarget
     }
     }
 }
 }

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

@@ -0,0 +1,16 @@
+import Foundation
+
+enum TimeInRangeChartStyle: String, JSON, CaseIterable, Identifiable, Codable, Hashable {
+    var id: String { rawValue }
+    case vertical
+    case horizontal
+
+    var displayName: String {
+        switch self {
+        case .vertical:
+            return NSLocalizedString("Vertical", comment: "")
+        case .horizontal:
+            return NSLocalizedString("Horizontal", comment: "")
+        }
+    }
+}

+ 2 - 2
FreeAPS/Sources/Models/TotalInsulinDisplayType.swift

@@ -14,9 +14,9 @@ enum TotalInsulinDisplayType: String, JSON, CaseIterable, Identifiable, Codable,
     var displayName: String {
     var displayName: String {
         switch self {
         switch self {
         case .totalDailyDose:
         case .totalDailyDose:
-            return NSLocalizedString("Total Daily Dose", comment: "")
+            return NSLocalizedString("TDD", comment: "")
         case .totalInsulinInScope:
         case .totalInsulinInScope:
-            return NSLocalizedString("Total Insulin in Scope", comment: "")
+            return NSLocalizedString("TINS", comment: "")
         }
         }
     }
     }
 }
 }

+ 2 - 2
FreeAPS/Sources/Modules/OverrideConfig/OverrideDataFlow.swift

@@ -1,7 +1,7 @@
 import Foundation
 import Foundation
 import SwiftUI
 import SwiftUI
 
 
-enum OverrideConfig {
+enum Adjustments {
     enum Config {}
     enum Config {}
 
 
     enum Tab: String, Hashable, Identifiable, CaseIterable {
     enum Tab: String, Hashable, Identifiable, CaseIterable {
@@ -24,4 +24,4 @@ enum OverrideConfig {
     }
     }
 }
 }
 
 
-protocol OverrideProvider: Provider {}
+protocol AdjustmentsProvider: Provider {}

+ 9 - 0
FreeAPS/Sources/Modules/Adjustments/AdjustmentsProvider.swift

@@ -0,0 +1,9 @@
+extension Adjustments {
+    final class Provider: BaseProvider, AdjustmentsProvider {
+        func getBGTarget() async -> BGTargets {
+            await storage.retrieveAsync(OpenAPS.Settings.bgTargets, as: BGTargets.self)
+                ?? BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
+                ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])
+        }
+    }
+}

+ 93 - 0
FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+Helpers.swift

@@ -0,0 +1,93 @@
+import SwiftUI
+
+extension Adjustments.StateModel {
+    /// Returns a description of how insulin doses are adjusted based on percentage.
+    func percentageDescription(_ percent: Double) -> Text? {
+        if percent.isNaN || percent == 100 { return nil }
+
+        var description: String = "Insulin doses will be "
+
+        if percent < 100 {
+            description += "decreased by "
+        } else {
+            description += "increased by "
+        }
+
+        let deviationFrom100 = abs(percent - 100)
+        description += String(format: "%.0f% %.", deviationFrom100)
+
+        return Text(description)
+    }
+
+    /// Checks if the device is using a 24-hour time format.
+    func is24HourFormat() -> Bool {
+        let formatter = DateFormatter()
+        formatter.locale = Locale.current
+        formatter.dateStyle = .none
+        formatter.timeStyle = .short
+        let dateString = formatter.string(from: Date())
+
+        return !dateString.contains("AM") && !dateString.contains("PM")
+    }
+
+    /// Converts a given hour to a 12-hour AM/PM format string.
+    func convertTo12HourFormat(_ hour: Int) -> String {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "h a"
+
+        let calendar = Calendar.current
+        let components = DateComponents(hour: hour)
+        let date = calendar.date(from: components) ?? Date()
+
+        return formatter.string(from: date)
+    }
+
+    /// Formats a given 24-hour time number as a two-digit string.
+    func format24Hour(_ hour: Int) -> String {
+        String(format: "%02d", hour)
+    }
+
+    /// Converts a duration in minutes to a formatted string (e.g., "1 hr 30 min").
+    func formatHrMin(_ durationInMinutes: Int) -> String {
+        let hours = durationInMinutes / 60
+        let minutes = durationInMinutes % 60
+
+        switch (hours, minutes) {
+        case let (0, m):
+            return "\(m) min"
+        case let (h, 0):
+            return "\(h) hr"
+        default:
+            return "\(hours) hr \(minutes) min"
+        }
+    }
+
+    /// Converts hours and minutes to total minutes as a `Decimal`.
+    func convertToMinutes(_ hours: Int, _ minutes: Int) -> Decimal {
+        let totalMinutes = (hours * 60) + minutes
+        return Decimal(max(0, totalMinutes))
+    }
+}
+
+extension PickerSettingsProvider {
+    /// Generates picker values based on a setting, optionally rounding minimum to the nearest step.
+    func generatePickerValues(from setting: PickerSetting, units: GlucoseUnits, roundMinToStep: Bool) -> [Decimal] {
+        if !roundMinToStep {
+            return generatePickerValues(from: setting, units: units)
+        }
+
+        // Adjust min to be divisible by step
+        var newSetting = setting
+        var min = Double(newSetting.min)
+        let step = Double(newSetting.step)
+        let remainder = min.truncatingRemainder(dividingBy: step)
+        if remainder != 0 {
+            // Move min up to the next value divisible by targetStep
+            min += (step - remainder)
+        }
+
+        newSetting.min = Decimal(min)
+
+        return generatePickerValues(from: newSetting, units: units)
+    }
+}

+ 338 - 0
FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+Overrides.swift

@@ -0,0 +1,338 @@
+import Combine
+import CoreData
+import Foundation
+
+extension Adjustments.StateModel {
+    // MARK: - Enact Overrides
+
+    /// Enacts an Override Preset by enabling it and disabling others.
+    @MainActor func enactOverridePreset(withID id: NSManagedObjectID) async {
+        do {
+            let overrideToEnact = try viewContext.existingObject(with: id) as? OverrideStored
+            overrideToEnact?.enabled = true
+            overrideToEnact?.date = Date()
+            overrideToEnact?.isUploadedToNS = false
+            isEnabled = true
+
+            await disableAllActiveOverrides(except: id, createOverrideRunEntry: currentActiveOverride != nil)
+            await resetStateVariables()
+
+            guard viewContext.hasChanges else { return }
+            try viewContext.save()
+
+            updateLatestOverrideConfiguration()
+        } catch {
+            debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to enact Override Preset")
+        }
+    }
+
+    // MARK: - Disable Overrides
+
+    /// Disables all active Overrides, optionally creating a run entry.
+    @MainActor func disableAllActiveOverrides(except overrideID: NSManagedObjectID? = nil, createOverrideRunEntry: Bool) async {
+        // Get ALL NSManagedObject IDs of ALL active Override to cancel every single Override
+        let ids = await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0)
+
+        await viewContext.perform {
+            do {
+                // Fetch the existing OverrideStored objects from the context
+                let results = try ids.compactMap { id in
+                    try self.viewContext.existingObject(with: id) as? OverrideStored
+                }
+                guard !results.isEmpty else { return }
+
+                // Check if we also need to create a corresponding OverrideRunStored entry, i.e. when the User uses the Cancel Button in Override View
+                if createOverrideRunEntry {
+                    // Use the first override to create a new OverrideRunStored entry
+                    if let canceledOverride = results.first {
+                        let newOverrideRunStored = OverrideRunStored(context: self.viewContext)
+                        newOverrideRunStored.id = UUID()
+                        newOverrideRunStored.name = canceledOverride.name
+                        newOverrideRunStored.startDate = canceledOverride.date ?? .distantPast
+                        newOverrideRunStored.endDate = Date()
+                        newOverrideRunStored
+                            .target = NSDecimalNumber(decimal: self.overrideStorage.calculateTarget(override: canceledOverride))
+                        newOverrideRunStored.override = canceledOverride
+                        newOverrideRunStored.isUploadedToNS = false
+                    }
+                }
+
+                // Disable all overrides except the one with overrideID
+                for overrideToCancel in results where overrideToCancel.objectID != overrideID {
+                    overrideToCancel.enabled = false
+                }
+
+                if self.viewContext.hasChanges {
+                    // Save changes and update the View
+                    try self.viewContext.save()
+                    self.updateLatestOverrideConfiguration()
+                }
+            } catch {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Overrides: \(error.localizedDescription)"
+                )
+            }
+        }
+    }
+
+    // MARK: - Save Overrides
+
+    /// Saves a custom Override and activates it.
+    func saveCustomOverride() async {
+        let override = Override(
+            name: overrideName,
+            enabled: true,
+            date: Date(),
+            duration: overrideDuration,
+            indefinite: indefinite,
+            percentage: overridePercentage,
+            smbIsOff: smbIsOff,
+            isPreset: isPreset,
+            id: id,
+            overrideTarget: shouldOverrideTarget,
+            target: target,
+            advancedSettings: advancedSettings,
+            isfAndCr: isfAndCr,
+            isf: isf,
+            cr: cr,
+            smbIsScheduledOff: smbIsScheduledOff,
+            start: start,
+            end: end,
+            smbMinutes: smbMinutes,
+            uamMinutes: uamMinutes
+        )
+
+        // First disable all Overrides
+        await disableAllActiveOverrides(createOverrideRunEntry: true)
+
+        // Then save and activate a new custom Override
+        await overrideStorage.storeOverride(override: override)
+
+        // Reset State variables
+        await resetStateVariables()
+
+        // Update View
+        updateLatestOverrideConfiguration()
+    }
+
+    /// Saves an Override Preset without activating it.
+    /// `enabled` has to be false
+    /// `isPreset` has to be true
+    func saveOverridePreset() async {
+        let preset = Override(
+            name: overrideName,
+            enabled: false,
+            date: Date(),
+            duration: overrideDuration,
+            indefinite: indefinite,
+            percentage: overridePercentage,
+            smbIsOff: smbIsOff,
+            isPreset: true,
+            id: id,
+            overrideTarget: shouldOverrideTarget,
+            target: target,
+            advancedSettings: advancedSettings,
+            isfAndCr: isfAndCr,
+            isf: isf,
+            cr: cr,
+            smbIsScheduledOff: smbIsScheduledOff,
+            start: start,
+            end: end,
+            smbMinutes: smbMinutes,
+            uamMinutes: uamMinutes
+        )
+
+        async let storeOverride: () = overrideStorage.storeOverride(override: preset)
+        async let resetState: () = resetStateVariables()
+        _ = await (storeOverride, resetState)
+        setupOverridePresetsArray()
+        await nightscoutManager.uploadProfiles()
+    }
+
+    // MARK: - Override Preset Management
+
+    /// Sets up the array of Override Presets for UI display.
+    func setupOverridePresetsArray() {
+        Task {
+            let ids = await overrideStorage.fetchForOverridePresets()
+            await updateOverridePresetsArray(with: ids)
+        }
+    }
+
+    /// Updates the array of Override Presets from Core Data.
+    @MainActor private func updateOverridePresetsArray(with IDs: [NSManagedObjectID]) async {
+        do {
+            let overrideObjects = try IDs.compactMap { id in
+                try viewContext.existingObject(with: id) as? OverrideStored
+            }
+            overridePresets = overrideObjects
+        } catch {
+            debugPrint(
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to extract Overrides: \(error.localizedDescription)"
+            )
+        }
+    }
+
+    /// Deletes an Override Preset and updates the view.
+    func invokeOverridePresetDeletion(_ objectID: NSManagedObjectID) async {
+        await overrideStorage.deleteOverridePreset(objectID)
+        setupOverridePresetsArray()
+        await nightscoutManager.uploadProfiles()
+    }
+
+    // MARK: - Update Latest Override Configuration
+
+    /// Updates the latest Override configuration and state.
+    /// First get the latest Overrides corresponding NSManagedObjectID with a background fetch
+    /// Then unpack it on the view context and update the State variables which can be used on in the View for some Logic
+    /// This also needs to be called when we cancel an Override via the Home View to update the State of the Button for this case
+    func updateLatestOverrideConfiguration() {
+        Task {
+            let id = await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 1)
+            async let updateState: () = updateLatestOverrideConfigurationOfState(from: id)
+            async let setOverride: () = setCurrentOverride(from: id)
+            _ = await (updateState, setOverride)
+        }
+    }
+
+    /// Updates state variables with the latest Override configuration.
+    @MainActor func updateLatestOverrideConfigurationOfState(from IDs: [NSManagedObjectID]) async {
+        do {
+            let result = try IDs.compactMap { id in
+                try viewContext.existingObject(with: id) as? OverrideStored
+            }
+            isEnabled = result.first?.enabled ?? false
+            if !isEnabled {
+                await resetStateVariables()
+            }
+        } catch {
+            debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update latest Override configuration")
+        }
+    }
+
+    /// Sets the current active Override for UI purposes.
+    @MainActor func setCurrentOverride(from IDs: [NSManagedObjectID]) async {
+        do {
+            guard let firstID = IDs.first else {
+                activeOverrideName = "Custom Override"
+                currentActiveOverride = nil
+                return
+            }
+
+            if let overrideToEdit = try viewContext.existingObject(with: firstID) as? OverrideStored {
+                currentActiveOverride = overrideToEdit
+                activeOverrideName = overrideToEdit.name ?? "Custom Override"
+            }
+        } catch {
+            debugPrint(
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to set active Override: \(error.localizedDescription)"
+            )
+        }
+    }
+
+    /// Duplicates the active Override Preset and cancels the previous one.
+    @MainActor func duplicateOverridePresetAndCancelPreviousOverride() async {
+        guard let overridePresetToDuplicate = currentActiveOverride, overridePresetToDuplicate.isPreset else { return }
+
+        let duplicateId = await overrideStorage.copyRunningOverride(overridePresetToDuplicate)
+
+        do {
+            try await viewContext.perform {
+                overridePresetToDuplicate.enabled = false
+                guard self.viewContext.hasChanges else { return }
+                try self.viewContext.save()
+            }
+
+            if let overrideToEdit = try viewContext.existingObject(with: duplicateId) as? OverrideStored {
+                currentActiveOverride = overrideToEdit
+                activeOverrideName = overrideToEdit.name ?? "Custom Override"
+            }
+        } catch {
+            debugPrint(
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to cancel previous Override: \(error.localizedDescription)"
+            )
+        }
+    }
+
+    // MARK: - Helper Functions
+
+    /// Resets state variables to default values.
+    @MainActor func resetStateVariables() async {
+        id = ""
+        overrideDuration = 0
+        indefinite = true
+        overridePercentage = 100
+        advancedSettings = false
+        smbIsOff = false
+        overrideName = ""
+        shouldOverrideTarget = false
+        isf = true
+        cr = true
+        isfAndCr = true
+        smbIsScheduledOff = false
+        start = 0
+        end = 0
+        smbMinutes = defaultSmbMinutes
+        uamMinutes = defaultUamMinutes
+        target = currentGlucoseTarget
+    }
+
+    /// Rounds a target value to the nearest step.
+    static func roundTargetToStep(_ target: Decimal, _ step: Decimal) -> Decimal {
+        // Convert target and step to NSDecimalNumber
+        guard let targetValue = NSDecimalNumber(decimal: target).doubleValue as Double?,
+              let stepValue = NSDecimalNumber(decimal: step).doubleValue as Double?
+        else {
+            return target
+        }
+
+        // Perform the remainder check using truncatingRemainder
+        let remainder = Decimal(targetValue.truncatingRemainder(dividingBy: stepValue))
+
+        if remainder != 0 {
+            // Calculate how much to adjust (up or down) based on the remainder
+            let adjustment = step - remainder
+            return target + adjustment
+        }
+
+        // Return the original target if no adjustment is needed
+        return target
+    }
+
+    /// Rounds an Override percentage to the nearest step.
+    static func roundOverridePercentageToStep(_ percentage: Double, _ step: Int) -> Double {
+        let stepDouble = Double(step)
+        // Check if overridePercentage is not divisible by the selected step
+        if percentage.truncatingRemainder(dividingBy: stepDouble) != 0 {
+            let roundedValue: Double
+
+            if percentage > 100 {
+                // Round down to the nearest valid step away from 100
+                let stepCount = (percentage - 100) / stepDouble
+                roundedValue = 100 + floor(stepCount) * stepDouble
+            } else {
+                // Round up to the nearest valid step away from 100
+                let stepCount = (100 - percentage) / stepDouble
+                roundedValue = 100 - floor(stepCount) * stepDouble
+            }
+
+            // Ensure the value stays between 10 and 200
+            return max(10, min(roundedValue, 200))
+        }
+
+        return percentage
+    }
+}
+
+enum IsfAndOrCrOptions: String, CaseIterable {
+    case isfAndCr = "ISF/CR"
+    case isf = "ISF"
+    case cr = "CR"
+    case nothing = "None"
+}
+
+enum DisableSmbOptions: String, CaseIterable {
+    case dontDisable = "Don't Disable"
+    case disable = "Disable"
+    case disableOnSchedule = "Disable on Schedule"
+}

+ 422 - 0
FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+TempTargets.swift

@@ -0,0 +1,422 @@
+import Combine
+import CoreData
+import Foundation
+
+extension Adjustments.StateModel {
+    // MARK: - State Initialization and Updates
+
+    /// Updates the latest Temp Target configuration for UI state and logic.
+    /// First get the latest Temp Target corresponding NSManagedObjectID with a background fetch
+    /// Then unpack it on the view context and update the State variables which can be used on in the View for some Logic
+    /// This also needs to be called when we cancel an Temp Target via the Home View to update the State of the Button for this case
+    func updateLatestTempTargetConfiguration() {
+        Task {
+            let id = await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 1)
+            async let updateState: () = updateLatestTempTargetConfigurationOfState(from: id)
+            async let setTempTarget: () = setCurrentTempTarget(from: id)
+            _ = await (updateState, setTempTarget)
+        }
+    }
+
+    /// Updates state variables with the latest Temp Target configuration.
+    @MainActor func updateLatestTempTargetConfigurationOfState(from IDs: [NSManagedObjectID]) async {
+        do {
+            let result = try IDs.compactMap { id in
+                try viewContext.existingObject(with: id) as? TempTargetStored
+            }
+            isTempTargetEnabled = result.first?.enabled ?? false
+            if !isEnabled {
+                await resetTempTargetState()
+            }
+        } catch {
+            debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update latest temp target configuration")
+        }
+    }
+
+    /// Sets the current Temp Target for UI and logic purposes.
+    @MainActor func setCurrentTempTarget(from IDs: [NSManagedObjectID]) async {
+        do {
+            guard let firstID = IDs.first else {
+                activeTempTargetName = "Custom Temp Target"
+                currentActiveTempTarget = nil
+                return
+            }
+
+            if let tempTargetToEdit = try viewContext.existingObject(with: firstID) as? TempTargetStored {
+                currentActiveTempTarget = tempTargetToEdit
+                activeTempTargetName = tempTargetToEdit.name ?? "Custom Temp Target"
+                tempTargetTarget = tempTargetToEdit.target?.decimalValue ?? 0
+            }
+        } catch {
+            debugPrint(
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to set active preset name with error: \(error.localizedDescription)"
+            )
+        }
+    }
+
+    // MARK: - Temp Target Fetching and Setup
+
+    /// Sets up Temp Targets using fetch and update functions.
+    func setupTempTargets(
+        fetchFunction: @escaping () async -> [NSManagedObjectID],
+        updateFunction: @escaping @MainActor([TempTargetStored]) -> Void
+    ) {
+        Task {
+            let ids = await fetchFunction()
+            let tempTargetObjects = await fetchTempTargetObjects(for: ids)
+            await updateFunction(tempTargetObjects)
+        }
+    }
+
+    /// Fetches Temp Target objects from Core Data.
+    @MainActor private func fetchTempTargetObjects(for IDs: [NSManagedObjectID]) async -> [TempTargetStored] {
+        do {
+            return try IDs.compactMap { id in
+                try viewContext.existingObject(with: id) as? TempTargetStored
+            }
+        } catch {
+            debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to fetch Temp Targets")
+            return []
+        }
+    }
+
+    /// Sets up the Temp Target presets array for the view.
+    func setupTempTargetPresetsArray() {
+        setupTempTargets(
+            fetchFunction: tempTargetStorage.fetchForTempTargetPresets,
+            updateFunction: { tempTargets in
+                self.tempTargetPresets = tempTargets
+            }
+        )
+    }
+
+    /// Sets up the scheduled Temp Targets array for the view.
+    func setupScheduledTempTargetsArray() {
+        setupTempTargets(
+            fetchFunction: tempTargetStorage.fetchScheduledTempTargets,
+            updateFunction: { tempTargets in
+                self.scheduledTempTargets = tempTargets
+            }
+        )
+    }
+
+    // MARK: - Temp Target Creation and Management
+
+    /// Saves a Temp Target to storage.
+    func saveTempTargetToStorage(tempTargets: [TempTarget]) {
+        tempTargetStorage.saveTempTargetsToStorage(tempTargets)
+    }
+
+    /// Saves a Temp Target based on whether it is scheduled or custom.
+    func invokeSaveOfCustomTempTargets() async {
+        if date > Date() {
+            await saveScheduledTempTarget()
+        } else {
+            await saveCustomTempTarget()
+        }
+    }
+
+    /// Saves a scheduled Temp Target and activates it at the specified date.
+    func saveScheduledTempTarget() async {
+        let date = self.date
+        guard date > Date() else { return }
+
+        let tempTarget = TempTarget(
+            name: tempTargetName,
+            createdAt: date,
+            targetTop: tempTargetTarget,
+            targetBottom: tempTargetTarget,
+            duration: tempTargetDuration,
+            enteredBy: TempTarget.local,
+            reason: TempTarget.custom,
+            isPreset: false,
+            enabled: false,
+            halfBasalTarget: halfBasalTarget
+        )
+        await tempTargetStorage.storeTempTarget(tempTarget: tempTarget)
+        setupScheduledTempTargetsArray()
+
+        Task {
+            await waitUntilDate(date)
+            await disableAllActiveTempTargets(createTempTargetRunEntry: true)
+            await enableScheduledTempTarget(for: date)
+            tempTargetStorage.saveTempTargetsToStorage([tempTarget])
+        }
+    }
+
+    /// Enables a scheduled Temp Target for a specific date.
+    func enableScheduledTempTarget(for date: Date) async {
+        let ids = await tempTargetStorage.fetchScheduledTempTarget(for: date)
+        guard let firstID = ids.first else {
+            debugPrint("No Temp Target found for the specified date.")
+            return
+        }
+        await setCurrentTempTarget(from: ids)
+
+        await MainActor.run {
+            do {
+                if let tempTarget = try viewContext.existingObject(with: firstID) as? TempTargetStored {
+                    tempTarget.enabled = true
+                    try viewContext.save()
+                    isTempTargetEnabled = true
+                }
+            } catch {
+                debugPrint("Failed to enable the Temp Target: \(error.localizedDescription)")
+            }
+        }
+        setupScheduledTempTargetsArray()
+    }
+
+    /// Waits until a target date before proceeding.
+    private func waitUntilDate(_ targetDate: Date) async {
+        while Date() < targetDate {
+            let timeInterval = targetDate.timeIntervalSince(Date())
+            let sleepDuration = min(timeInterval, 60.0)
+            try? await Task.sleep(nanoseconds: UInt64(sleepDuration * 1_000_000_000))
+        }
+    }
+
+    /// Saves a custom Temp Target and disables existing ones.
+    func saveCustomTempTarget() async {
+        await disableAllActiveTempTargets(createTempTargetRunEntry: true)
+        let tempTarget = TempTarget(
+            name: tempTargetName,
+            /// We don't need to use the state var date here as we are using a different function for scheduled Temp Targets 'saveScheduledTempTarget()'
+            createdAt: Date(),
+            targetTop: tempTargetTarget,
+            targetBottom: tempTargetTarget,
+            duration: tempTargetDuration,
+            enteredBy: TempTarget.local,
+            reason: TempTarget.custom,
+            isPreset: false,
+            enabled: true,
+            halfBasalTarget: halfBasalTarget
+        )
+        await tempTargetStorage.storeTempTarget(tempTarget: tempTarget)
+        tempTargetStorage.saveTempTargetsToStorage([tempTarget])
+        await resetTempTargetState()
+        isTempTargetEnabled = true
+        updateLatestTempTargetConfiguration()
+    }
+
+    /// Creates a new Temp Target preset.
+    func saveTempTargetPreset() async {
+        let tempTarget = TempTarget(
+            name: tempTargetName,
+            createdAt: Date(),
+            targetTop: tempTargetTarget,
+            targetBottom: tempTargetTarget,
+            duration: tempTargetDuration,
+            enteredBy: TempTarget.local,
+            reason: TempTarget.custom,
+            isPreset: true,
+            enabled: false,
+            halfBasalTarget: halfBasalTarget
+        )
+        await tempTargetStorage.storeTempTarget(tempTarget: tempTarget)
+        await resetTempTargetState()
+        setupTempTargetPresetsArray()
+    }
+
+    /// Enacts a Temp Target preset by enabling it.
+    @MainActor func enactTempTargetPreset(withID id: NSManagedObjectID) async {
+        do {
+            let tempTargetToEnact = try viewContext.existingObject(with: id) as? TempTargetStored
+            tempTargetToEnact?.enabled = true
+            tempTargetToEnact?.date = Date()
+            tempTargetToEnact?.isUploadedToNS = false
+            isTempTargetEnabled = true
+
+            async let disableTempTargets: () = disableAllActiveTempTargets(
+                except: id,
+                createTempTargetRunEntry: currentActiveTempTarget != nil
+            )
+            async let resetState: () = resetTempTargetState()
+            _ = await (disableTempTargets, resetState)
+
+            if viewContext.hasChanges {
+                try viewContext.save()
+            }
+
+            updateLatestTempTargetConfiguration()
+
+            let tempTarget = TempTarget(
+                name: tempTargetToEnact?.name,
+                createdAt: Date(),
+                targetTop: tempTargetToEnact?.target?.decimalValue,
+                targetBottom: tempTargetToEnact?.target?.decimalValue,
+                duration: tempTargetToEnact?.duration?.decimalValue ?? 0,
+                enteredBy: TempTarget.local,
+                reason: TempTarget.custom,
+                isPreset: true,
+                enabled: true,
+                halfBasalTarget: halfBasalTarget
+            )
+            tempTargetStorage.saveTempTargetsToStorage([tempTarget])
+        } catch {
+            debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to enact Override Preset")
+        }
+    }
+
+    /// Disables all active Temp Targets.
+    @MainActor func disableAllActiveTempTargets(except id: NSManagedObjectID? = nil, createTempTargetRunEntry: Bool) async {
+        // Get ALL NSManagedObject IDs of ALL active Temp Targets to cancel every single Temp Target
+        let ids = await tempTargetStorage.loadLatestTempTargetConfigurations(fetchLimit: 0) // 0 = no fetch limit
+
+        await viewContext.perform {
+            do {
+                // Fetch the existing TempTargetStored objects from the context
+                let results = try ids.compactMap { id in
+                    try self.viewContext.existingObject(with: id) as? TempTargetStored
+                }
+
+                // If there are no results, return early
+                guard !results.isEmpty else { return }
+
+                // Check if we also need to create a corresponding TempTargetRunStored entry, i.e. when the User uses the Cancel Button in Temp Target View
+                if createTempTargetRunEntry {
+                    // Use the first temp target to create a new TempTargetRunStored entry
+                    if let canceledTempTarget = results.first {
+                        let newTempTargetRunStored = TempTargetRunStored(context: self.viewContext)
+                        newTempTargetRunStored.id = UUID()
+                        newTempTargetRunStored.name = canceledTempTarget.name
+                        newTempTargetRunStored.startDate = canceledTempTarget.date ?? .distantPast
+                        newTempTargetRunStored.endDate = Date()
+                        newTempTargetRunStored
+                            .target = canceledTempTarget.target ?? 0
+                        newTempTargetRunStored.tempTarget = canceledTempTarget
+                        newTempTargetRunStored.isUploadedToNS = false
+                    }
+                }
+
+                // Disable all temporary targets except the one with given id
+                for tempTargetToCancel in results {
+                    if tempTargetToCancel.objectID != id {
+                        tempTargetToCancel.enabled = false
+                    }
+                }
+
+                // Save the context if there are changes
+                if self.viewContext.hasChanges {
+                    try self.viewContext.save()
+
+                    // Update the storage
+                    self.tempTargetStorage.saveTempTargetsToStorage([TempTarget.cancel(at: Date().addingTimeInterval(-1))])
+                }
+            } catch {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active TempTargets with error: \(error.localizedDescription)"
+                )
+            }
+        }
+    }
+
+    /// Duplicates the current preset and cancels the previous one.
+    @MainActor func duplicateTempTargetPresetAndCancelPreviousTempTarget() async {
+        // We get the current active Preset by using currentActiveTempTarget which can either be a Preset or a custom Override
+        guard let tempTargetPresetToDuplicate = currentActiveTempTarget,
+              tempTargetPresetToDuplicate.isPreset == true else { return }
+
+        // Copy the current TempTarget-Preset to not edit the underlying Preset
+        let duplidateId = await tempTargetStorage.copyRunningTempTarget(tempTargetPresetToDuplicate)
+
+        // Cancel the duplicated Temp Target
+        // As we are on the Main Thread already we don't need to cancel via the objectID in this case
+        do {
+            try await viewContext.perform {
+                tempTargetPresetToDuplicate.enabled = false
+
+                guard self.viewContext.hasChanges else { return }
+                try self.viewContext.save()
+            }
+
+            if let tempTargetToEdit = try viewContext.existingObject(with: duplidateId) as? TempTargetStored
+            {
+                currentActiveTempTarget = tempTargetToEdit
+                activeTempTargetName = tempTargetToEdit.name ?? "Custom Temp Target"
+            }
+        } catch {
+            debugPrint(
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to cancel previous override with error: \(error.localizedDescription)"
+            )
+        }
+    }
+
+    /// Deletes a Temp Target preset.
+    func invokeTempTargetPresetDeletion(_ objectID: NSManagedObjectID) async {
+        await tempTargetStorage.deleteOverridePreset(objectID)
+        setupTempTargetPresetsArray()
+    }
+
+    /// Resets Temp Target state variables.
+    @MainActor func resetTempTargetState() async {
+        tempTargetName = ""
+        tempTargetTarget = 100
+        tempTargetDuration = 0
+        percentage = 100
+        halfBasalTarget = settingHalfBasalTarget
+    }
+
+    // MARK: - Calculations
+
+    /// Computes the half-basal target based on the current settings.
+    func computeHalfBasalTarget(
+        usingTarget initialTarget: Decimal? = nil,
+        usingPercentage initialPercentage: Double? = nil
+    ) -> Double {
+        let adjustmentPercentage = initialPercentage ?? percentage
+        let adjustmentRatio = Decimal(adjustmentPercentage / 100)
+        let tempTargetValue: Decimal = initialTarget ?? tempTargetTarget
+        var halfBasalTargetValue = halfBasalTarget
+        if adjustmentRatio != 1 {
+            halfBasalTargetValue = ((2 * adjustmentRatio * normalTarget) - normalTarget - (adjustmentRatio * tempTargetValue)) /
+                (adjustmentRatio - 1)
+        }
+        return round(Double(halfBasalTargetValue))
+    }
+
+    /// Determines if sensitivity adjustment is enabled based on target.
+    func isAdjustSensEnabled(usingTarget initialTarget: Decimal? = nil) -> Bool {
+        let target = initialTarget ?? tempTargetTarget
+        if target < normalTarget, lowTTlowersSens { return true }
+        if target > normalTarget, highTTraisesSens || isExerciseModeActive { return true }
+        return false
+    }
+
+    /// Computes the low value for the slider based on the target.
+    func computeSliderLow(usingTarget initialTarget: Decimal? = nil) -> Double {
+        let calcTarget = initialTarget ?? tempTargetTarget
+        guard calcTarget != 0 else { return 15 } // oref defined maximum sensitivity
+        let minSens = calcTarget < normalTarget ? 105 : 15
+        return Double(max(0, minSens))
+    }
+
+    /// Computes the high value for the slider based on the target.
+    func computeSliderHigh(usingTarget initialTarget: Decimal? = nil) -> Double {
+        let calcTarget = initialTarget ?? tempTargetTarget
+        guard calcTarget != 0 else { return Double(maxValue * 100) } // oref defined limit for increased insulin delivery
+        let maxSens = calcTarget > normalTarget ? 95 : Double(maxValue * 100)
+        return maxSens
+    }
+
+    /// Computes the adjusted percentage for the slider.
+    func computeAdjustedPercentage(
+        usingHBT initialHalfBasalTarget: Decimal? = nil,
+        usingTarget initialTarget: Decimal? = nil
+    ) -> Double {
+        let halfBasalTargetValue = initialHalfBasalTarget ?? halfBasalTarget
+        let calcTarget = initialTarget ?? tempTargetTarget
+        let deviationFromNormal = halfBasalTargetValue - normalTarget
+
+        let adjustmentFactor = deviationFromNormal + (calcTarget - normalTarget)
+        let adjustmentRatio: Decimal = (deviationFromNormal * adjustmentFactor <= 0) ? maxValue : deviationFromNormal /
+            adjustmentFactor
+
+        return Double(min(adjustmentRatio, maxValue) * 100).rounded()
+    }
+}
+
+enum TempTargetSensitivityAdjustmentType: String, CaseIterable {
+    case standard = "Standard"
+    case slider = "Custom"
+}

+ 272 - 0
FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel.swift

@@ -0,0 +1,272 @@
+import Combine
+import CoreData
+import Observation
+import SwiftUI
+
+extension Adjustments {
+    @Observable final class StateModel: BaseStateModel<Provider> {
+        // MARK: - Injected Dependencies
+
+        @ObservationIgnored @Injected() var broadcaster: Broadcaster!
+        @ObservationIgnored @Injected() var tempTargetStorage: TempTargetsStorage!
+        @ObservationIgnored @Injected() var apsManager: APSManager!
+        @ObservationIgnored @Injected() var overrideStorage: OverrideStorage!
+        @ObservationIgnored @Injected() var nightscoutManager: NightscoutManager!
+
+        // MARK: - Override and Temp Target Properties
+
+        var overridePercentage: Double = 100
+        var isEnabled = false
+        var indefinite = true
+        var overrideDuration: Decimal = 0
+        var target: Decimal = 0
+        var currentGlucoseTarget: Decimal = 100
+        var shouldOverrideTarget: Bool = false
+        var smbIsOff: Bool = false
+        var id = ""
+        var overrideName: String = ""
+        var isPreset: Bool = false
+        var overridePresets: [OverrideStored] = []
+        var advancedSettings: Bool = false
+        var isfAndCr: Bool = true
+        var isf: Bool = true
+        var cr: Bool = true
+        var smbIsScheduledOff: Bool = false
+        var start: Decimal = 0
+        var end: Decimal = 0
+        var smbMinutes: Decimal = 0
+        var uamMinutes: Decimal = 0
+        var defaultSmbMinutes: Decimal = 0
+        var defaultUamMinutes: Decimal = 0
+        var selectedTab: Tab = .overrides
+        var activeOverrideName: String = ""
+        var currentActiveOverride: OverrideStored?
+        var activeTempTargetName: String = ""
+
+        var currentActiveTempTarget: TempTargetStored?
+        var showOverrideEditSheet = false
+        var showTempTargetEditSheet = false
+        var units: GlucoseUnits = .mgdL
+
+        // Temp Target Properties
+        let normalTarget: Decimal = 100
+        var tempTargetDuration: Decimal = 0
+        var tempTargetName: String = ""
+        var tempTargetTarget: Decimal = 100
+        var isTempTargetEnabled: Bool = false
+        var date = Date()
+        var newPresetName = ""
+        var tempTargetPresets: [TempTargetStored] = []
+        var scheduledTempTargets: [TempTargetStored] = []
+        var percentage: Double = 100
+        var maxValue: Decimal = 1.2
+        var halfBasalTarget: Decimal = 160
+        var settingHalfBasalTarget: Decimal = 160
+        var highTTraisesSens: Bool = false
+        var isExerciseModeActive: Bool = false
+        var lowTTlowersSens: Bool = false
+        var didSaveSettings: Bool = false
+
+        // Core Data
+        let coredataContext = CoreDataStack.shared.newTaskContext()
+        let viewContext = CoreDataStack.shared.persistentContainer.viewContext
+
+        // Help Sheet
+        var isHelpSheetPresented: Bool = false
+        var helpSheetDetent = PresentationDetent.large
+
+        // Combine
+        private var cancellables = Set<AnyCancellable>()
+
+        // MARK: - Lifecycle
+
+        /// Subscribes to notifications and initializes settings.
+        override func subscribe() {
+            setupNotification()
+            setupSettings()
+            broadcaster.register(SettingsObserver.self, observer: self)
+            broadcaster.register(PreferencesObserver.self, observer: self)
+
+            Task {
+                await withTaskGroup(of: Void.self) { group in
+                    group.addTask { self.setupOverridePresetsArray() }
+                    group.addTask { self.setupTempTargetPresetsArray() }
+                    group.addTask { self.updateLatestOverrideConfiguration() }
+                    group.addTask { self.updateLatestTempTargetConfiguration() }
+                }
+            }
+        }
+
+        /// Retrieves the current glucose target based on the time of day.
+        func getCurrentGlucoseTarget() async {
+            let now = Date()
+            let calendar = Calendar.current
+            let dateFormatter = DateFormatter()
+            dateFormatter.dateFormat = "HH:mm:ss"
+            dateFormatter.timeZone = TimeZone.current
+
+            let bgTargets = await provider.getBGTarget()
+            let entries: [(start: String, value: Decimal)] = bgTargets.targets.map { ($0.start, $0.low) }
+
+            for (index, entry) in entries.enumerated() {
+                guard let entryTime = dateFormatter.date(from: entry.start) else {
+                    print("Invalid entry start time: \(entry.start)")
+                    continue
+                }
+
+                let entryComponents = calendar.dateComponents([.hour, .minute, .second], from: entryTime)
+                let entryStartTime = calendar.date(
+                    bySettingHour: entryComponents.hour!,
+                    minute: entryComponents.minute!,
+                    second: entryComponents.second!,
+                    of: now
+                )!
+
+                let entryEndTime: Date
+                if index < entries.count - 1,
+                   let nextEntryTime = dateFormatter.date(from: entries[index + 1].start)
+                {
+                    let nextEntryComponents = calendar.dateComponents([.hour, .minute, .second], from: nextEntryTime)
+                    entryEndTime = calendar.date(
+                        bySettingHour: nextEntryComponents.hour!,
+                        minute: nextEntryComponents.minute!,
+                        second: nextEntryComponents.second!,
+                        of: now
+                    )!
+                } else {
+                    entryEndTime = calendar.date(byAdding: .day, value: 1, to: entryStartTime)!
+                }
+
+                if now >= entryStartTime, now < entryEndTime {
+                    await MainActor.run {
+                        currentGlucoseTarget = entry.value
+                        target = currentGlucoseTarget
+                    }
+                    return
+                }
+            }
+        }
+
+        /// Configures various settings from the settings manager.
+        private func setupSettings() {
+            units = settingsManager.settings.units
+            defaultSmbMinutes = settingsManager.preferences.maxSMBBasalMinutes
+            defaultUamMinutes = settingsManager.preferences.maxUAMSMBBasalMinutes
+            maxValue = settingsManager.preferences.autosensMax
+            settingHalfBasalTarget = settingsManager.preferences.halfBasalExerciseTarget
+            halfBasalTarget = settingsManager.preferences.halfBasalExerciseTarget
+            highTTraisesSens = settingsManager.preferences.highTemptargetRaisesSensitivity
+            isExerciseModeActive = settingsManager.preferences.exerciseMode
+            lowTTlowersSens = settingsManager.preferences.lowTemptargetLowersSensitivity
+            percentage = computeAdjustedPercentage()
+            Task {
+                await getCurrentGlucoseTarget()
+            }
+        }
+
+        /// Reorders Override Presets and updates the view.
+        func reorderOverride(from source: IndexSet, to destination: Int) {
+            overridePresets.move(fromOffsets: source, toOffset: destination)
+            for (index, override) in overridePresets.enumerated() {
+                override.orderPosition = Int16(index + 1)
+            }
+            do {
+                guard viewContext.hasChanges else { return }
+                try viewContext.save()
+                setupOverridePresetsArray()
+                Task { await nightscoutManager.uploadProfiles() }
+            } catch {
+                debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Override Presets order")
+            }
+        }
+
+        /// Reorders Temp Target Presets and updates the view.
+        func reorderTempTargets(from source: IndexSet, to destination: Int) {
+            tempTargetPresets.move(fromOffsets: source, toOffset: destination)
+            for (index, tempTarget) in tempTargetPresets.enumerated() {
+                tempTarget.orderPosition = Int16(index + 1)
+            }
+            do {
+                guard viewContext.hasChanges else { return }
+                try viewContext.save()
+                setupTempTargetPresetsArray()
+            } catch {
+                debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Temp Target Presets order")
+            }
+        }
+    }
+}
+
+// MARK: - Notifications Setup
+
+extension Adjustments.StateModel {
+    /// Sets up notification observers for Override and Temp Target updates.
+    func setupNotification() {
+        Foundation.NotificationCenter.default.addObserver(
+            self,
+            selector: #selector(handleOverrideConfigurationUpdate),
+            name: .didUpdateOverrideConfiguration,
+            object: nil
+        )
+
+        // Custom Notification to update View when an Temp Target has been cancelled via Home View
+        Foundation.NotificationCenter.default.addObserver(
+            self,
+            selector: #selector(handleTempTargetConfigurationUpdate),
+            name: .didUpdateTempTargetConfiguration,
+            object: nil
+        )
+
+        // Creates a publisher that updates the Override View when the Custom notification was sent (via shortcut)
+        Foundation.NotificationCenter.default.publisher(for: .willUpdateOverrideConfiguration)
+            .sink { [weak self] _ in
+                guard let self = self else { return }
+                self.updateLatestOverrideConfiguration()
+            }
+            .store(in: &cancellables)
+
+        // Creates a publisher that updates the Temp Target View when the Custom notification was sent (via shortcut)
+        Foundation.NotificationCenter.default.publisher(for: .willUpdateTempTargetConfiguration)
+            .sink { [weak self] _ in
+                guard let self = self else { return }
+                self.updateLatestTempTargetConfiguration()
+            }
+            .store(in: &cancellables)
+    }
+
+    /// Handles Override configuration updates.
+    @objc private func handleOverrideConfigurationUpdate() {
+        updateLatestOverrideConfiguration()
+    }
+
+    /// Handles Temp Target configuration updates.
+    @objc private func handleTempTargetConfigurationUpdate() {
+        updateLatestTempTargetConfiguration()
+    }
+}
+
+extension Adjustments.StateModel: SettingsObserver, PreferencesObserver {
+    /// Updates settings when they change.
+    func settingsDidChange(_: FreeAPSSettings) {
+        units = settingsManager.settings.units
+        Task {
+            await getCurrentGlucoseTarget()
+        }
+    }
+
+    /// Updates preferences when they change.
+    func preferencesDidChange(_: Preferences) {
+        defaultSmbMinutes = settingsManager.preferences.maxSMBBasalMinutes
+        defaultUamMinutes = settingsManager.preferences.maxUAMSMBBasalMinutes
+        maxValue = settingsManager.preferences.autosensMax
+        settingHalfBasalTarget = settingsManager.preferences.halfBasalExerciseTarget
+        halfBasalTarget = settingsManager.preferences.halfBasalExerciseTarget
+        highTTraisesSens = settingsManager.preferences.highTemptargetRaisesSensitivity
+        isExerciseModeActive = settingsManager.preferences.exerciseMode
+        lowTTlowersSens = settingsManager.preferences.lowTemptargetLowersSensitivity
+        percentage = computeAdjustedPercentage()
+        Task {
+            await getCurrentGlucoseTarget()
+        }
+    }
+}

+ 707 - 0
FreeAPS/Sources/Modules/Adjustments/View/AdjustmentsRootView.swift

@@ -0,0 +1,707 @@
+import CoreData
+import SwiftUI
+import Swinject
+
+extension Adjustments {
+    struct RootView: BaseView {
+        let resolver: Resolver
+        @State var state = StateModel()
+        @State private var isEditing = false
+        @State private var showOverrideCreationSheet = false
+        @State private var showTempTargetCreationSheet = false
+        @State private var showingDetail = false
+        @State private var showCheckmark: Bool = false
+        @State private var selectedPresetID: String?
+        @State private var selectedTempTargetPresetID: String?
+        @State private var selectedOverride: OverrideStored?
+        @State private var selectedTempTarget: TempTargetStored?
+        @State private var isConfirmDeletePresented = false
+        @State private var isPromptPresented = false
+        @State private var isRemoveAlertPresented = false
+        @State private var removeAlert: Alert?
+        @State private var isEditingTT = false
+
+        @Environment(\.colorScheme) var colorScheme
+        @Environment(AppState.self) var appState
+
+        private func formattedGlucose(glucose: Decimal) -> String {
+            let formattedValue: String
+            if state.units == .mgdL {
+                formattedValue = Formatter.glucoseFormatter(for: state.units)
+                    .string(from: glucose as NSDecimalNumber) ?? "\(glucose)"
+            } else {
+                formattedValue = glucose.formattedAsMmolL
+            }
+            return "\(formattedValue) \(state.units.rawValue)"
+        }
+
+        var body: some View {
+            ZStack(alignment: .center, content: {
+                VStack {
+                    Picker("Adjustment Tabs", selection: $state.selectedTab) {
+                        ForEach(Adjustments.Tab.allCases.indexed(), id: \.1) { index, item in
+                            Text(item.name).tag(index)
+                        }
+                    }
+                    .pickerStyle(SegmentedPickerStyle())
+                    .padding(.horizontal)
+
+                    List {
+                        switch state.selectedTab {
+                        case .overrides: overrides()
+                        case .tempTargets: tempTargets() }
+                    }
+                    .scrollContentBackground(.hidden)
+                    .background(appState.trioBackgroundColor(for: colorScheme))
+                }
+                .listSectionSpacing(10)
+                .safeAreaInset(edge: .bottom, spacing: 30) { stickyStopButton }
+                .scrollContentBackground(.hidden)
+                .background(appState.trioBackgroundColor(for: colorScheme))
+                .onAppear(perform: configureView)
+                .navigationBarTitle("Adjustments")
+                .navigationBarTitleDisplayMode(.large)
+                .toolbar {
+                    ToolbarItem(placement: .topBarTrailing) {
+                        switch state.selectedTab {
+                        case .overrides:
+                            Button(action: {
+                                showOverrideCreationSheet = true
+                            }, label: {
+                                HStack {
+                                    Text("Add Override")
+                                    Image(systemName: "plus")
+                                }
+                            })
+                        case .tempTargets:
+                            Button(action: {
+                                showTempTargetCreationSheet = true
+                            }, label: {
+                                HStack {
+                                    Text("Add Temp Target")
+                                    Image(systemName: "plus")
+                                }
+                            })
+                        }
+                    }
+                }
+                .sheet(isPresented: $state.showOverrideEditSheet, onDismiss: {
+                    Task {
+                        await state.resetStateVariables()
+                        state.showOverrideEditSheet = false
+                    }
+
+                }) {
+                    if let override = selectedOverride {
+                        EditOverrideForm(overrideToEdit: override, state: state)
+                    }
+                }
+                .sheet(isPresented: $showOverrideCreationSheet, onDismiss: {
+                    Task {
+                        await state.resetStateVariables()
+                        showOverrideCreationSheet = false
+                    }
+                }) {
+                    AddOverrideForm(state: state)
+                }
+                .sheet(isPresented: $showTempTargetCreationSheet, onDismiss: {
+                    Task {
+                        await state.resetTempTargetState()
+                        showTempTargetCreationSheet = false
+                    }
+                }) {
+                    AddTempTargetForm(state: state)
+                }
+                .sheet(isPresented: $state.showTempTargetEditSheet, onDismiss: {
+                    Task {
+                        await state.resetTempTargetState()
+                        state.showTempTargetEditSheet = false
+                    }
+
+                }) {
+                    if let tempTarget = selectedTempTarget {
+                        EditTempTargetForm(tempTargetToEdit: tempTarget, state: state)
+                    }
+                }
+            }).background(appState.trioBackgroundColor(for: colorScheme))
+        }
+
+        @ViewBuilder func overrides() -> some View {
+            if state.isEnabled, state.activeOverrideName.isNotEmpty {
+                currentActiveAdjustment
+            }
+            if state.overridePresets.isNotEmpty {
+                overridePresets
+            } else {
+                defaultText
+            }
+        }
+
+        @ViewBuilder func tempTargets() -> some View {
+            if state.isTempTargetEnabled, state.activeTempTargetName.isNotEmpty {
+                currentActiveAdjustment
+            }
+            if state.scheduledTempTargets.isNotEmpty {
+                scheduledTempTargets
+            }
+            if state.tempTargetPresets.isNotEmpty {
+                tempTargetPresets
+            } else {
+                defaultText
+            }
+        }
+
+        private var defaultText: some View {
+            switch state.selectedTab {
+            case .overrides:
+                Section {} header: {
+                    Text("Add Preset or Override by tapping 'Add Override +' in the top right-hand corner of the screen.")
+                        .textCase(nil)
+                        .foregroundStyle(.secondary)
+                }
+            case .tempTargets:
+                Section {} header: {
+                    Text(
+                        "Add Preset or Temp Target by tapping 'Add Temp Target +' in the top right-hand corner of the screen."
+                    )
+                    .textCase(nil)
+                    .foregroundStyle(.secondary)
+                }
+            }
+        }
+
+        private var overridePresets: some View {
+            Section {
+                ForEach(state.overridePresets) { preset in
+                    overridesView(for: preset)
+                        .swipeActions(edge: .trailing, allowsFullSwipe: true) {
+                            Button(role: .none) {
+                                selectedOverride = preset
+                                isConfirmDeletePresented = true
+                            } label: {
+                                Label("Delete", systemImage: "trash")
+                                    .tint(.red)
+                            }
+                            Button(action: {
+                                // Set the selected Override to the chosen Preset and pass it to the Edit Sheet
+                                selectedOverride = preset
+                                state.showOverrideEditSheet = true
+                            }, label: {
+                                Label("Edit", systemImage: "pencil")
+                                    .tint(.blue)
+                            })
+                        }
+                }
+                .onMove(perform: state.reorderOverride)
+                .confirmationDialog(
+                    "Delete the Override Preset \"\(selectedOverride?.name ?? "")\"?",
+                    isPresented: $isConfirmDeletePresented,
+                    titleVisibility: .visible
+                ) {
+                    if let itemToDelete = selectedOverride {
+                        Button(
+                            state.currentActiveOverride == selectedOverride ? "Stop and Delete" : "Delete",
+                            role: .destructive
+                        ) {
+                            if state.currentActiveOverride == selectedOverride {
+                                Task {
+                                    // Save cancelled Override in OverrideRunStored Entity
+                                    // Cancel ALL active Override
+                                    await state.disableAllActiveOverrides(createOverrideRunEntry: true)
+                                }
+                            }
+                            // Perform the delete action
+                            Task {
+                                await state.invokeOverridePresetDeletion(itemToDelete.objectID)
+                            }
+                            // Reset the selected item after deletion
+                            selectedOverride = nil
+                        }
+                    }
+                    Button("Cancel", role: .cancel) {
+                        // Dismiss the dialog without action
+                        selectedOverride = nil
+                    }
+                } message: {
+                    if state.currentActiveOverride == selectedOverride {
+                        Text(
+                            state
+                                .currentActiveOverride == selectedOverride ?
+                                "This override preset is currently running. Deleting will stop it." : ""
+                        )
+                    }
+                }
+                .listRowBackground(Color.chart)
+            } header: {
+                Text("Override Presets")
+            } footer: {
+                HStack {
+                    Image(systemName: "hand.draw.fill").foregroundStyle(.primary)
+                    Text("Swipe left to edit or delete an override preset. Hold, drag and drop to reorder a preset.")
+                }
+            }
+        }
+
+        private var scheduledTempTargets: some View {
+            Section {
+                ForEach(state.scheduledTempTargets) { tempTarget in
+                    tempTargetView(for: tempTarget)
+                        .swipeActions(edge: .trailing, allowsFullSwipe: true) {
+                            swipeActions(for: tempTarget)
+                        }
+                }
+                .listRowBackground(Color.chart)
+            } header: {
+                Text("Scheduled Temp Targets")
+            }
+        }
+
+        private var tempTargetPresets: some View {
+            Section {
+                ForEach(state.tempTargetPresets) { preset in
+                    tempTargetView(for: preset, showCheckmark: showCheckmark) {
+                        enactTempTargetPreset(preset)
+                    }
+                    .swipeActions(edge: .trailing, allowsFullSwipe: true) {
+                        swipeActions(for: preset)
+                    }
+                }
+                .onMove(perform: state.reorderTempTargets)
+                .confirmationDialog(
+                    deleteConfirmationTitle,
+                    isPresented: $isConfirmDeletePresented,
+                    titleVisibility: .visible
+                ) {
+                    deleteConfirmationButtons()
+                } message: {
+                    deleteConfirmationMessage
+                }
+                .listRowBackground(Color.chart)
+            } header: {
+                Text("Temporary Target Presets")
+            } footer: {
+                HStack {
+                    Image(systemName: "hand.draw.fill").foregroundStyle(.primary)
+                    Text("Swipe left to edit or delete a temporary target preset. Hold, drag and drop to reorder a preset.")
+                }
+            }
+        }
+
+        private func enactTempTargetPreset(_ preset: TempTargetStored) {
+            Task {
+                let objectID = preset.objectID
+                await state.enactTempTargetPreset(withID: objectID)
+                selectedTempTargetPresetID = preset.id?.uuidString
+                showCheckmark.toggle()
+
+                DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
+                    showCheckmark = false
+                }
+            }
+        }
+
+        private func swipeActions(for tempTarget: TempTargetStored) -> some View {
+            Group {
+                Button {
+                    Task {
+                        selectedTempTarget = tempTarget
+                        isConfirmDeletePresented = true
+                    }
+                } label: {
+                    Label("Delete", systemImage: "trash")
+                        .tint(.red)
+                }
+                Button(action: {
+                    selectedTempTarget = tempTarget
+                    state.showTempTargetEditSheet = true
+                }, label: {
+                    Label("Edit", systemImage: "pencil")
+                        .tint(.blue)
+                })
+            }
+        }
+
+        private var deleteConfirmationTitle: String {
+            "Delete the Temp Target Preset \"\(selectedTempTarget?.name ?? "")\"?"
+        }
+
+        private func deleteConfirmationButtons() -> some View {
+            Group {
+                if let itemToDelete = selectedTempTarget {
+                    Button(
+                        state.currentActiveTempTarget == selectedTempTarget ? "Stop and Delete" : "Delete",
+                        role: .destructive
+                    ) {
+                        if state.currentActiveTempTarget == selectedTempTarget {
+                            Task {
+                                await state.disableAllActiveTempTargets(createTempTargetRunEntry: true)
+                            }
+                        }
+                        Task {
+                            await state.invokeTempTargetPresetDeletion(itemToDelete.objectID)
+                        }
+                        selectedTempTarget = nil
+                    }
+                }
+                Button("Cancel", role: .cancel) {
+                    selectedTempTarget = nil
+                }
+            }
+        }
+
+        private var deleteConfirmationMessage: Text? {
+            if state.currentActiveTempTarget == selectedTempTarget {
+                return Text("This Temp Target preset is currently running. Deleting will stop it.")
+            }
+            return nil
+        }
+
+        private var currentActiveAdjustment: some View {
+            switch state.selectedTab {
+            case .overrides:
+                Section {
+                    HStack {
+                        Text("\(state.activeOverrideName) is running")
+
+                        Spacer()
+                        Image(systemName: "square.and.pencil")
+                            .foregroundStyle(Color.primary)
+                    }
+                    .contentShape(Rectangle())
+                    .onTapGesture {
+                        Task {
+                            /// To avoid editing the Preset when a Preset-Override is running we first duplicate the Preset-Override as a non-Preset Override
+                            /// The currentActiveOverride variable in the State will update automatically via MOC notification
+                            await state.duplicateOverridePresetAndCancelPreviousOverride()
+
+                            /// selectedOverride is used for passing the chosen Override to the EditSheet so we have to set the updated currentActiveOverride to be the selectedOverride
+                            selectedOverride = state.currentActiveOverride
+
+                            /// Now we can show the Edit sheet
+                            state.showOverrideEditSheet = true
+                        }
+                    }
+                }
+                .listRowBackground(Color.purple.opacity(0.8))
+            case .tempTargets:
+                Section {
+                    HStack {
+                        Text("\(state.activeTempTargetName) is running")
+
+                        Spacer()
+                        Image(systemName: "square.and.pencil")
+                            .foregroundStyle(Color.primary)
+                    }
+                    .contentShape(Rectangle())
+                    .onTapGesture {
+                        Task {
+                            /// To avoid editing the Preset when a Preset-Override is running we first duplicate the Preset-Override as a non-Preset Override
+                            /// The currentActiveOverride variable in the State will update automatically via MOC notification
+                            await state.duplicateTempTargetPresetAndCancelPreviousTempTarget()
+
+                            /// selectedOverride is used for passing the chosen Override to the EditSheet so we have to set the updated currentActiveOverride to be the selectedOverride
+                            selectedTempTarget = state.currentActiveTempTarget
+
+                            /// Now we can show the Edit sheet
+                            state.showTempTargetEditSheet = true
+                        }
+                    }
+                }
+                .listRowBackground(Color.loopGreen.opacity(0.8))
+            }
+        }
+
+        var stickyStopButton: some View {
+            ZStack {
+                Rectangle()
+                    .frame(width: UIScreen.main.bounds.width, height: 65)
+                    .foregroundStyle(colorScheme == .dark ? Color.bgDarkerDarkBlue : Color.white)
+                    .background(.thinMaterial)
+                    .opacity(0.8)
+                    .clipShape(Rectangle())
+                Group {
+                    switch state.selectedTab {
+                    case .overrides:
+                        Button(action: {
+                            Task {
+                                // Save cancelled Override in OverrideRunStored Entity
+                                // Cancel ALL active Override
+                                await state.disableAllActiveOverrides(createOverrideRunEntry: true)
+                            }
+                        }, label: {
+                            Text("Stop Override")
+                                .padding(10)
+                        })
+                            .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
+                            .disabled(!state.isEnabled)
+                            .background(!state.isEnabled ? Color(.systemGray4) : Color(.systemRed))
+                            .tint(.white)
+                            .clipShape(RoundedRectangle(cornerRadius: 8))
+                    case .tempTargets:
+                        Button(action: {
+                            Task {
+                                // Save cancelled Temp Targets in TempTargetRunStored Entity
+                                // Cancel ALL active Temp Targets
+                                await state.disableAllActiveTempTargets(createTempTargetRunEntry: true)
+                                // Update View
+                                state.updateLatestTempTargetConfiguration()
+                            }
+                        }, label: {
+                            Text("Stop Temp Target")
+                                .padding(10)
+                        })
+                            .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
+                            .disabled(!state.isTempTargetEnabled)
+                            .background(!state.isTempTargetEnabled ? Color(.systemGray4) : Color(.systemRed))
+                            .tint(.white)
+                            .clipShape(RoundedRectangle(cornerRadius: 8))
+                    }
+                }.padding(5)
+            }
+        }
+
+        private var cancelAdjustmentButton: some View {
+            switch state.selectedTab {
+            case .overrides:
+                Button(action: {
+                    Task {
+                        // Save cancelled Override in OverrideRunStored Entity
+                        // Cancel ALL active Override
+                        await state.disableAllActiveOverrides(createOverrideRunEntry: true)
+                    }
+                }, label: {
+                    Text("Stop Override")
+
+                })
+                    .frame(maxWidth: .infinity, alignment: .center)
+                    .disabled(!state.isEnabled)
+                    .listRowBackground(!state.isEnabled ? Color(.systemGray4) : Color(.systemRed))
+                    .tint(.white)
+            case .tempTargets:
+                Button(action: {
+                    Task {
+                        // Save cancelled Temp Targets in TempTargetRunStored Entity
+                        // Cancel ALL active Temp Targets
+                        await state.disableAllActiveTempTargets(createTempTargetRunEntry: true)
+
+                        // Update View
+                        state.updateLatestTempTargetConfiguration()
+                    }
+                }, label: {
+                    Text("Stop Temp Target")
+
+                })
+                    .frame(maxWidth: .infinity, alignment: .center)
+                    .disabled(!state.isTempTargetEnabled)
+                    .listRowBackground(!state.isTempTargetEnabled ? Color(.systemGray4) : Color(.systemRed))
+                    .tint(.white)
+            }
+        }
+
+        private func tempTargetView(
+            for tempTarget: TempTargetStored,
+            showCheckmark: Bool = false,
+            onTap: (() -> Void)? = nil
+        ) -> some View {
+            let target = tempTarget.target ?? 100
+            let tempTargetValue = Decimal(target as! Double.RawValue)
+            let isSelected = tempTarget.id?.uuidString == selectedPresetID
+            let tempTargetHalfBasal = Decimal(
+                tempTarget.halfBasalTarget as? Double
+                    .RawValue ?? Double(state.settingHalfBasalTarget)
+            )
+            let percentage = Int(
+                state.computeAdjustedPercentage(usingHBT: tempTargetHalfBasal, usingTarget: tempTargetValue)
+            )
+            let remainingTime = tempTarget.date?.timeIntervalSinceNow ?? 0
+
+            return ZStack(alignment: .trailing) {
+                HStack {
+                    VStack(alignment: .leading) {
+                        HStack {
+                            Text(tempTarget.name ?? "")
+                            Spacer()
+                            if remainingTime > 0 {
+                                Text("Starts in \(formattedTimeRemaining(remainingTime))")
+                                    .foregroundColor(colorScheme == .dark ? .orange : .accentColor)
+                            }
+                        }
+                        HStack(spacing: 2) {
+                            Text(formattedGlucose(glucose: target as Decimal))
+                                .foregroundColor(.secondary)
+                                .font(.caption)
+                            Text("for")
+                                .foregroundColor(.secondary)
+                                .font(.caption)
+                            Text("\(Formatter.integerFormatter.string(from: (tempTarget.duration ?? 0) as NSNumber)!)")
+                                .foregroundColor(.secondary)
+                                .font(.caption)
+                            Text("min")
+                                .foregroundColor(.secondary)
+                                .font(.caption)
+                            if state.isAdjustSensEnabled(usingTarget: tempTargetValue) {
+                                Text(", \(percentage)%")
+                                    .foregroundColor(.secondary)
+                                    .font(.caption)
+                            }
+                            Spacer()
+                        }
+                        .padding(.top, 2)
+                    }
+                    .contentShape(Rectangle())
+                    .onTapGesture {
+                        onTap?()
+                    }
+                }
+                if showCheckmark && isSelected {
+                    Image(systemName: "checkmark.circle.fill")
+                        .imageScale(.large)
+                        .fontWeight(.bold)
+                        .foregroundStyle(Color.green)
+                } else if onTap != nil {
+                    Image(systemName: "line.3.horizontal")
+                        .imageScale(.medium)
+                        .foregroundStyle(.secondary)
+                }
+            }
+        }
+
+        private func formattedTimeRemaining(_ timeInterval: TimeInterval) -> String {
+            let totalSeconds = Int(timeInterval)
+            let hours = totalSeconds / 3600
+            let minutes = (totalSeconds % 3600) / 60
+            let seconds = totalSeconds % 60
+
+            if hours > 0 {
+                return "\(hours)h \(minutes)m \(seconds)s"
+            } else if minutes > 0 {
+                return "\(minutes)m \(seconds)s"
+            } else {
+                return "<1m"
+            }
+        }
+
+        private var overrideLabelDivider: some View {
+            Divider()
+                .frame(width: 1, height: 20)
+        }
+
+        @ViewBuilder private func overridesView(for preset: OverrideStored) -> some View {
+            let isSelected = preset.id == selectedPresetID
+            let name = preset.name ?? ""
+            let indefinite = preset.indefinite
+            let duration = preset.duration?.decimalValue ?? Decimal(0)
+            let percentage = preset.percentage
+            let smbMinutes = preset.smbMinutes?.decimalValue ?? Decimal(0)
+            let uamMinutes = preset.uamMinutes?.decimalValue ?? Decimal(0)
+
+            let target: String = {
+                guard let targetValue = preset.target, targetValue != 0 else { return "" }
+                return state.units == .mgdL ? targetValue.description : targetValue.decimalValue.formattedAsMmolL
+            }()
+
+            let targetString = target.isEmpty ? "" : "\(target) \(state.units.rawValue)"
+
+            let durationString = indefinite ? "" : "\(state.formatHrMin(Int(duration)))"
+
+            let scheduledSMBString: String = {
+                guard preset.smbIsScheduledOff, preset.start != preset.end else { return "" }
+                return " \(formatTimeRange(start: preset.start?.stringValue, end: preset.end?.stringValue))"
+            }()
+
+            let smbString: String = {
+                guard preset.smbIsOff || preset.smbIsScheduledOff else { return "" }
+                return "SMBs Off\(scheduledSMBString)"
+            }()
+
+            let maxSmbMinsString: String = {
+                guard smbMinutes != 0, preset.advancedSettings, !preset.smbIsOff,
+                      smbMinutes != state.defaultSmbMinutes else { return "" }
+                return "\(smbMinutes.formatted()) min SMB"
+            }()
+
+            let maxUamMinsString: String = {
+                guard uamMinutes != 0, preset.advancedSettings, !preset.smbIsOff,
+                      uamMinutes != state.defaultUamMinutes else { return "" }
+                return "\(uamMinutes.formatted()) min UAM"
+            }()
+
+            let isfAndCrString: String = {
+                switch (preset.isfAndCr, preset.isf, preset.cr) {
+                case (_, true, true),
+                     (true, _, _):
+                    return " ISF/CR"
+                case (false, true, false):
+                    return " ISF"
+                case (false, false, true):
+                    return " CR"
+                default:
+                    return ""
+                }
+            }()
+
+            let percentageString = percentage != 100 ? "\(Int(percentage))%\(isfAndCrString)" : ""
+
+            // Combine all labels into a single array, filtering out empty strings
+            let labels: [String] = [
+                durationString,
+                percentageString,
+                targetString,
+                smbString,
+                maxSmbMinsString,
+                maxUamMinsString
+            ].filter { !$0.isEmpty }
+
+            if !name.isEmpty {
+                ZStack(alignment: .trailing) {
+                    HStack {
+                        VStack {
+                            HStack {
+                                Text(name)
+                                Spacer()
+                            }
+                            HStack(spacing: 5) {
+                                ForEach(labels, id: \.self) { label in
+                                    Text(label)
+                                    if label != labels.last { // Add divider between labels
+                                        overrideLabelDivider
+                                    }
+                                }
+                                Spacer()
+                            }
+                            .padding(.top, 2)
+                            .foregroundColor(.secondary)
+                            .font(.caption)
+                        }
+                        .contentShape(Rectangle())
+                        .onTapGesture {
+                            Task {
+                                let objectID = preset.objectID
+                                await state.enactOverridePreset(withID: objectID)
+                                state.hideModal()
+                                showCheckmark.toggle()
+                                selectedPresetID = preset.id
+
+                                // Deactivate checkmark after 3 seconds
+                                DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
+                                    showCheckmark = false
+                                }
+                            }
+                        }
+                    }
+                    // show checkmark to indicate if the preset was actually pressed
+                    if showCheckmark && isSelected {
+                        Image(systemName: "checkmark.circle.fill")
+                            .imageScale(.large)
+                            .fontWeight(.bold)
+                            .foregroundStyle(Color.green)
+                    } else {
+                        Image(systemName: "line.3.horizontal")
+                            .imageScale(.medium)
+                            .foregroundStyle(.secondary)
+                    }
+                }
+            }
+        }
+    }
+}

+ 474 - 0
FreeAPS/Sources/Modules/Adjustments/View/Overrides/AddOverrideForm.swift

@@ -0,0 +1,474 @@
+import Foundation
+import SwiftUI
+
+struct AddOverrideForm: View {
+    @Environment(\.presentationMode) var presentationMode
+    @Environment(\.colorScheme) var colorScheme
+    @Environment(\.dismiss) var dismiss
+    @Environment(AppState.self) var appState
+    @Bindable var state: Adjustments.StateModel
+    @State private var selectedIsfCrOption: IsfAndOrCrOptions = .isfAndCr
+    @State private var selectedDisableSmbOption: DisableSmbOptions = .dontDisable
+    @State private var percentageStep: Int = 5
+    @State private var displayPickerPercentage: Bool = false
+    @State private var displayPickerDuration: Bool = false
+    @State private var targetStep: Decimal = 5
+    @State private var displayPickerTarget: Bool = false
+    @State private var displayPickerDisableSmbSchedule: Bool = false
+    @State private var displayPickerSmbMinutes: Bool = false
+    @State private var durationHours = 0
+    @State private var durationMinutes = 0
+    @State private var overrideTarget = false
+    @State private var didPressSave = false
+
+    var body: some View {
+        NavigationView {
+            List {
+                addOverride()
+                saveButton
+            }
+            .listSectionSpacing(10)
+            .padding(.top, 30)
+            .ignoresSafeArea(edges: .top)
+            .scrollContentBackground(.hidden)
+            .background(appState.trioBackgroundColor(for: colorScheme))
+            .navigationTitle("Add Override")
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .topBarLeading) {
+                    Button(action: {
+                        presentationMode.wrappedValue.dismiss()
+                    }, label: {
+                        Text("Cancel")
+                    })
+                }
+                ToolbarItem(placement: .topBarTrailing) {
+                    Button(
+                        action: {
+                            state.isHelpSheetPresented.toggle()
+                        },
+                        label: {
+                            Image(systemName: "questionmark.circle")
+                        }
+                    )
+                }
+            }
+            .onAppear { targetStep = state.units == .mgdL ? 5 : 9 }
+            .sheet(isPresented: $state.isHelpSheetPresented) {
+                NavigationStack {
+                    List {
+                        Text("Lorem Ipsum Dolor Sit Amet")
+                    }
+                    .padding(.trailing, 10)
+                    .navigationBarTitle("Help", displayMode: .inline)
+
+                    Button { state.isHelpSheetPresented.toggle() }
+                    label: { Text("Got it!").frame(maxWidth: .infinity, alignment: .center) }
+                        .buttonStyle(.bordered)
+                        .padding(.top)
+                }
+                .padding()
+                .presentationDetents(
+                    [.fraction(0.9), .large],
+                    selection: $state.helpSheetDetent
+                )
+            }
+        }
+    }
+
+    @ViewBuilder private func addOverride() -> some View {
+        Group {
+            Section {
+                HStack {
+                    Text("Name")
+                    Spacer()
+                    TextField("(Optional)", text: $state.overrideName).multilineTextAlignment(.trailing)
+                }
+            }
+            .listRowBackground(Color.chart)
+
+            Section(footer: state.percentageDescription(state.overridePercentage)) {
+                // Percentage Picker
+                HStack {
+                    Text("Change Basal Rate by")
+                    Spacer()
+                    Text("\(state.overridePercentage.formatted(.number)) %")
+                        .foregroundColor(!displayPickerPercentage ? .primary : .accentColor)
+                }
+                .onTapGesture {
+                    displayPickerPercentage = toggleScrollWheel(displayPickerPercentage)
+                }
+
+                if displayPickerPercentage {
+                    HStack {
+                        // Radio buttons and text on the left side
+                        VStack(alignment: .leading) {
+                            // Radio buttons for step iteration
+                            ForEach([1, 5], id: \.self) { step in
+                                RadioButton(isSelected: percentageStep == step, label: "\(step) %") {
+                                    percentageStep = step
+                                    state.overridePercentage = Adjustments.StateModel.roundOverridePercentageToStep(
+                                        state.overridePercentage,
+                                        step
+                                    )
+                                }
+                                .padding(.top, 10)
+                            }
+                        }
+                        .frame(maxWidth: .infinity)
+
+                        Spacer()
+
+                        // Picker on the right side
+                        Picker(
+                            selection: Binding(
+                                get: { Int(truncating: state.overridePercentage as NSNumber) },
+                                set: { state.overridePercentage = Double($0) }
+                            ), label: Text("")
+                        ) {
+                            ForEach(Array(stride(from: 40, through: 150, by: percentageStep)), id: \.self) { percent in
+                                Text("\(percent) %").tag(percent)
+                            }
+                        }
+                        .pickerStyle(WheelPickerStyle())
+                        .frame(maxWidth: .infinity)
+                    }
+                    .frame(maxWidth: .infinity)
+                    .listRowSeparator(.hidden, edges: .top)
+                }
+
+                // Picker for ISF/CR settings
+                Picker("Also Inversely Change", selection: $selectedIsfCrOption) {
+                    ForEach(IsfAndOrCrOptions.allCases, id: \.self) { option in
+                        Text(option.rawValue).tag(option)
+                    }
+                }
+                .pickerStyle(MenuPickerStyle())
+                .onChange(of: selectedIsfCrOption) { _, newValue in
+                    switch newValue {
+                    case .isfAndCr:
+                        state.isfAndCr = true
+                        state.isf = true
+                        state.cr = true
+                    case .isf:
+                        state.isfAndCr = false
+                        state.isf = true
+                        state.cr = false
+                    case .cr:
+                        state.isfAndCr = false
+                        state.isf = false
+                        state.cr = true
+                    case .nothing:
+                        state.isfAndCr = false
+                        state.isf = false
+                        state.cr = false
+                    }
+                }
+            }
+            .listRowBackground(Color.chart)
+
+            Section {
+                Toggle(isOn: $state.shouldOverrideTarget) {
+                    Text("Override Target")
+                }
+
+                if state.shouldOverrideTarget {
+                    let settingsProvider = PickerSettingsProvider.shared
+                    let glucoseSetting = PickerSetting(value: 0, step: targetStep, min: 72, max: 270, type: .glucose)
+                    TargetPicker(
+                        label: "Target Glucose",
+                        selection: Binding(
+                            get: { state.target },
+                            set: { state.target = $0 }
+                        ),
+                        options: settingsProvider.generatePickerValues(
+                            from: glucoseSetting,
+                            units: state.units,
+                            roundMinToStep: true
+                        ),
+                        units: state.units,
+                        targetStep: $targetStep,
+                        displayPickerTarget: $displayPickerTarget,
+                        toggleScrollWheel: toggleScrollWheel
+                    )
+                    .onAppear {
+                        if state.target == 0 {
+                            state.target = 100
+                        }
+                    }
+                }
+            }
+            .listRowBackground(Color.chart)
+
+            Section {
+                // Picker for ISF/CR settings
+                Picker("Disable SMBs", selection: $selectedDisableSmbOption) {
+                    ForEach(DisableSmbOptions.allCases, id: \.self) { option in
+                        Text(option.rawValue).tag(option)
+                    }
+                }
+                .pickerStyle(MenuPickerStyle())
+                .onChange(of: selectedDisableSmbOption) { _, newValue in
+                    switch newValue {
+                    case .dontDisable:
+                        state.smbIsOff = false
+                        state.smbIsScheduledOff = false
+                    case .disable:
+                        state.smbIsOff = true
+                        state.smbIsScheduledOff = false
+                    case .disableOnSchedule:
+                        state.smbIsOff = false
+                        state.smbIsScheduledOff = true
+                    }
+                }
+
+                if state.smbIsScheduledOff {
+                    // First Hour SMBs Are Disabled
+                    HStack {
+                        Text("From")
+                        Spacer()
+                        Text(
+                            state.is24HourFormat() ? state.format24Hour(Int(truncating: state.start as NSNumber)) + ":00" :
+                                state.convertTo12HourFormat(Int(truncating: state.start as NSNumber))
+                        )
+                        .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
+                        Spacer()
+                        Divider().frame(width: 1, height: 20)
+                        Spacer()
+                        Text("To")
+                        Spacer()
+                        Text(
+                            state.is24HourFormat() ? state.format24Hour(Int(truncating: state.end as NSNumber)) + ":00" :
+                                state.convertTo12HourFormat(Int(truncating: state.end as NSNumber))
+                        )
+                        .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
+                        Spacer()
+                    }
+                    .onTapGesture {
+                        displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
+                    }
+
+                    if displayPickerDisableSmbSchedule {
+                        HStack {
+                            // From Picker
+                            Picker(selection: Binding(
+                                get: { Int(truncating: state.start as NSNumber) },
+                                set: { state.start = Decimal($0) }
+                            ), label: Text("")) {
+                                ForEach(0 ..< 24, id: \.self) { hour in
+                                    Text(
+                                        state.is24HourFormat() ? state.format24Hour(hour) + ":00" : state
+                                            .convertTo12HourFormat(hour)
+                                    )
+                                    .tag(hour)
+                                }
+                            }
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
+
+                            // To Picker
+                            Picker(selection: Binding(
+                                get: { Int(truncating: state.end as NSNumber) },
+                                set: { state.end = Decimal($0) }
+                            ), label: Text("")) {
+                                ForEach(0 ..< 24, id: \.self) { hour in
+                                    Text(
+                                        state.is24HourFormat() ? state.format24Hour(hour) + ":00" : state
+                                            .convertTo12HourFormat(hour)
+                                    )
+                                    .tag(hour)
+                                }
+                            }
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
+                        }
+                        .listRowSeparator(.hidden, edges: .top)
+                    }
+                }
+            }
+            .listRowBackground(Color.chart)
+
+            if !state.smbIsOff {
+                Section {
+                    Toggle(isOn: $state.advancedSettings) {
+                        Text("Override Max SMB Minutes")
+                    }
+
+                    if state.advancedSettings {
+                        // SMB Minutes Picker
+                        HStack {
+                            Text("SMB")
+                            Spacer()
+                            Text("\(state.smbMinutes.formatted(.number)) min")
+                                .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
+                            Spacer()
+                            Divider().frame(width: 1, height: 20)
+                            Spacer()
+                            Text("UAM")
+                            Spacer()
+                            Text("\(state.uamMinutes.formatted(.number)) min")
+                                .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
+                        }
+                        .onTapGesture {
+                            displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                        }
+
+                        if displayPickerSmbMinutes {
+                            HStack {
+                                Picker(selection: Binding(
+                                    get: { Int(truncating: state.smbMinutes as NSNumber) },
+                                    set: { state.smbMinutes = Decimal($0) }
+                                ), label: Text("")) {
+                                    ForEach(Array(stride(from: 0, through: 180, by: 5)), id: \.self) { minute in
+                                        Text("\(minute) min").tag(minute)
+                                    }
+                                }
+                                .pickerStyle(WheelPickerStyle())
+                                .frame(maxWidth: .infinity)
+
+                                Picker(selection: Binding(
+                                    get: { Int(truncating: state.uamMinutes as NSNumber) },
+                                    set: { state.uamMinutes = Decimal($0) }
+                                ), label: Text("")) {
+                                    ForEach(Array(stride(from: 0, through: 180, by: 5)), id: \.self) { minute in
+                                        Text("\(minute) min").tag(minute)
+                                    }
+                                }
+                                .pickerStyle(WheelPickerStyle())
+                                .frame(maxWidth: .infinity)
+                            }
+                            .listRowSeparator(.hidden, edges: .top)
+                        }
+                    }
+                }
+                .listRowBackground(Color.chart)
+            }
+
+            Section {
+                Toggle(isOn: $state.indefinite) {
+                    Text("Enable Indefinitely")
+                }
+
+                if !state.indefinite {
+                    HStack {
+                        Text("Duration")
+                        Spacer()
+                        Text(state.formatHrMin(Int(state.overrideDuration)))
+                            .foregroundColor(!displayPickerDuration ? .primary : .accentColor)
+                    }
+                    .onTapGesture {
+                        displayPickerDuration = toggleScrollWheel(displayPickerDuration)
+                    }
+
+                    if displayPickerDuration {
+                        HStack {
+                            Picker("Hours", selection: $durationHours) {
+                                ForEach(0 ..< 24) { hour in
+                                    Text("\(hour) hr").tag(hour)
+                                }
+                            }
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
+                            .onChange(of: durationHours) {
+                                state.overrideDuration = state.convertToMinutes(durationHours, durationMinutes)
+                            }
+
+                            Picker("Minutes", selection: $durationMinutes) {
+                                ForEach(Array(stride(from: 0, through: 55, by: 5)), id: \.self) { minute in
+                                    Text("\(minute) min").tag(minute)
+                                }
+                            }
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
+                            .onChange(of: durationMinutes) {
+                                state.overrideDuration = state.convertToMinutes(durationHours, durationMinutes)
+                            }
+                        }
+                        .listRowSeparator(.hidden, edges: .top)
+                    }
+                }
+            }
+            .listRowBackground(Color.chart)
+        }
+    }
+
+    private var saveButton: some View {
+        let (isInvalid, errorMessage) = isOverrideInvalid()
+
+        return Group {
+            Section(
+                header:
+                HStack {
+                    Spacer()
+                    Text(errorMessage ?? "").textCase(nil)
+                        .foregroundColor(colorScheme == .dark ? .orange : .accentColor)
+                    Spacer()
+                },
+                content: {
+                    Button(action: {
+                        Task {
+                            if state.indefinite { state.overrideDuration = 0 }
+                            state.isEnabled.toggle()
+                            await state.saveCustomOverride()
+                            await state.resetStateVariables()
+                            dismiss()
+                        }
+                    }, label: {
+                        Text("Start Override")
+                    })
+                        .disabled(isInvalid)
+                        .frame(maxWidth: .infinity, alignment: .center)
+                        .tint(.white)
+                }
+            ).listRowBackground(isInvalid ? Color(.systemGray4) : Color(.systemBlue))
+
+            Section {
+                Button(action: {
+                    Task {
+                        await state.saveOverridePreset()
+                        dismiss()
+                    }
+                }, label: {
+                    Text("Save as Preset")
+
+                })
+                    .disabled(isInvalid)
+                    .frame(maxWidth: .infinity, alignment: .center)
+                    .tint(.white)
+            }
+            .listRowBackground(
+                isInvalid ? Color(.systemGray4) : Color.secondary
+            )
+        }
+    }
+
+    private func toggleScrollWheel(_ toggle: Bool) -> Bool {
+        displayPickerDuration = false
+        displayPickerPercentage = false
+        displayPickerTarget = false
+        displayPickerDisableSmbSchedule = false
+        displayPickerSmbMinutes = false
+        return !toggle
+    }
+
+    private func isOverrideInvalid() -> (Bool, String?) {
+        let noDurationSpecified = !state.indefinite && state.overrideDuration == 0
+        let targetZeroWithOverride = state.shouldOverrideTarget && state.target == 0
+        let allSettingsDefault = state.overridePercentage == 100 && !state.shouldOverrideTarget &&
+            !state.advancedSettings && !state.smbIsOff && !state.smbIsScheduledOff
+
+        if noDurationSpecified {
+            return (true, "Enable indefinitely or set a duration.")
+        }
+
+        if targetZeroWithOverride {
+            return (true, "Target glucose is out of range (\(state.units == .mgdL ? "72-270" : "4-14")).")
+        }
+
+        if allSettingsDefault {
+            return (true, "All settings are at default values.")
+        }
+
+        return (false, nil)
+    }
+}

+ 635 - 0
FreeAPS/Sources/Modules/Adjustments/View/Overrides/EditOverrideForm.swift

@@ -0,0 +1,635 @@
+import Foundation
+import SwiftUI
+
+struct EditOverrideForm: View {
+    var override: OverrideStored
+    @Environment(\.presentationMode) var presentationMode
+    @Environment(\.colorScheme) var colorScheme
+    @Environment(AppState.self) var appState
+    @Bindable var state: Adjustments.StateModel
+
+    @State private var name: String
+    @State private var percentage: Double
+    @State private var indefinite: Bool
+    @State private var duration: Decimal
+    @State private var target: Decimal?
+    @State private var advancedSettings: Bool
+    @State private var smbIsOff: Bool
+    @State private var smbIsScheduledOff: Bool
+    @State private var start: Decimal?
+    @State private var end: Decimal?
+    @State private var isfAndCr: Bool
+    @State private var isf: Bool
+    @State private var cr: Bool
+    @State private var smbMinutes: Decimal?
+    @State private var uamMinutes: Decimal?
+    @State private var selectedIsfCrOption: IsfAndOrCrOptions
+    @State private var selectedDisableSmbOption: DisableSmbOptions
+    @State private var hasChanges = false
+    @State private var isEditing = false
+    @State private var target_override = false
+    @State private var percentageStep: Int = 1
+    @State private var displayPickerPercentage: Bool = false
+    @State private var displayPickerDuration: Bool = false
+    @State private var targetStep: Decimal = 1
+    @State private var displayPickerTarget: Bool = false
+    @State private var displayPickerDisableSmbSchedule: Bool = false
+    @State private var displayPickerSmbMinutes: Bool = false
+
+    init(overrideToEdit: OverrideStored, state: Adjustments.StateModel) {
+        override = overrideToEdit
+        _state = Bindable(wrappedValue: state)
+        _name = State(initialValue: overrideToEdit.name ?? "")
+        _percentage = State(initialValue: overrideToEdit.percentage)
+        _indefinite = State(initialValue: overrideToEdit.indefinite)
+        _duration = State(initialValue: overrideToEdit.duration?.decimalValue ?? 0)
+        _target = State(initialValue: overrideToEdit.target?.decimalValue)
+        _target_override = State(initialValue: overrideToEdit.target != nil && overrideToEdit.target?.decimalValue != 0)
+        _advancedSettings = State(initialValue: overrideToEdit.advancedSettings)
+        _smbIsOff = State(initialValue: overrideToEdit.smbIsOff)
+        _smbIsScheduledOff = State(initialValue: overrideToEdit.smbIsScheduledOff)
+        _start = State(initialValue: overrideToEdit.start?.decimalValue)
+        _end = State(initialValue: overrideToEdit.end?.decimalValue)
+        _isfAndCr = State(initialValue: overrideToEdit.isfAndCr)
+        _isf = State(initialValue: overrideToEdit.isf)
+        _cr = State(initialValue: overrideToEdit.cr)
+        _selectedIsfCrOption = State(
+            initialValue: overrideToEdit.isfAndCr ? .isfAndCr
+                : (overrideToEdit.isf ? .isf : (overrideToEdit.cr ? .cr : .nothing))
+        )
+        _selectedDisableSmbOption = State(
+            initialValue: overrideToEdit.smbIsScheduledOff ? .disableOnSchedule
+                : (overrideToEdit.smbIsOff ? .disable : .dontDisable)
+        )
+        _smbMinutes = State(initialValue: overrideToEdit.smbMinutes?.decimalValue)
+        _uamMinutes = State(initialValue: overrideToEdit.uamMinutes?.decimalValue)
+    }
+
+    private var percentageSelection: Binding<Double> {
+        Binding<Double>(
+            get: {
+                let value = floor(percentage / Double(percentageStep)) * Double(percentageStep)
+                return max(10, min(value, 200))
+            },
+            set: {
+                percentage = $0
+                hasChanges = true
+            }
+        )
+    }
+
+    var body: some View {
+        NavigationView {
+            List {
+                editOverride()
+                saveButton
+            }
+            .listSectionSpacing(10)
+            .padding(.top, 30)
+            .ignoresSafeArea(edges: .top)
+            .scrollContentBackground(.hidden)
+            .background(appState.trioBackgroundColor(for: colorScheme))
+            .navigationTitle("Edit Override")
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .topBarLeading) {
+                    Button(action: {
+                        presentationMode.wrappedValue.dismiss()
+                    }, label: {
+                        Text("Cancel")
+                    })
+                }
+                ToolbarItem(placement: .topBarTrailing) {
+                    Button(
+                        action: {
+                            state.isHelpSheetPresented.toggle()
+                        },
+                        label: {
+                            Image(systemName: "questionmark.circle")
+                        }
+                    )
+                }
+            }
+            .onDisappear {
+                if !hasChanges {
+                    // Reset UI changes
+                    resetValues()
+                }
+            }
+            .sheet(isPresented: $state.isHelpSheetPresented) {
+                NavigationStack {
+                    List {
+                        Text("Lorem Ipsum Dolor Sit Amet")
+                    }
+                    .padding(.trailing, 10)
+                    .navigationBarTitle("Help", displayMode: .inline)
+
+                    Button { state.isHelpSheetPresented.toggle() }
+                    label: { Text("Got it!").frame(maxWidth: .infinity, alignment: .center) }
+                        .buttonStyle(.bordered)
+                        .padding(.top)
+                }
+                .padding()
+                .presentationDetents(
+                    [.fraction(0.9), .large],
+                    selection: $state.helpSheetDetent
+                )
+            }
+        }
+    }
+
+    @ViewBuilder private func editOverride() -> some View {
+        Group {
+            if override.name != nil {
+                Section {
+                    HStack {
+                        Text("Name")
+                        Spacer()
+                        TextField("Name", text: $name)
+                            .onChange(of: name) { hasChanges = true }
+                            .multilineTextAlignment(.trailing)
+                    }
+                }
+                .listRowBackground(Color.chart)
+            }
+
+            // Percentage Picker
+            Section(footer: state.percentageDescription(percentage)) {
+                HStack {
+                    Text("Change Basal Rate by")
+                    Spacer()
+                    Text("\(percentage.formatted(.number)) %")
+                        .foregroundColor(!displayPickerPercentage ? .primary : .accentColor)
+                }
+                .onTapGesture {
+                    displayPickerPercentage = toggleScrollWheel(displayPickerPercentage)
+                }
+
+                if displayPickerPercentage {
+                    HStack {
+                        // Radio buttons and text on the left side
+                        VStack(alignment: .leading) {
+                            // Radio buttons for step iteration
+                            ForEach([1, 5], id: \.self) { step in
+                                RadioButton(isSelected: percentageStep == step, label: "\(step) %") {
+                                    percentageStep = step
+                                    percentage = Adjustments.StateModel.roundOverridePercentageToStep(percentage, step)
+                                }
+                                .padding(.top, 10)
+                            }
+                        }
+                        .frame(maxWidth: .infinity)
+
+                        Spacer()
+
+                        // Picker on the right side
+                        Picker(
+                            selection: percentageSelection,
+                            label: Text("")
+                        ) {
+                            ForEach(
+                                Array(stride(from: 40.0, through: 150.0, by: Double(percentageStep))),
+                                id: \.self
+                            ) { percent in
+                                Text("\(Int(percent)) %").tag(percent)
+                            }
+                        }
+                        .pickerStyle(WheelPickerStyle())
+                        .frame(maxWidth: .infinity)
+                    }
+                    .listRowSeparator(.hidden, edges: .top)
+                }
+
+                // Picker for ISF/CR settings
+                Picker("Also Change", selection: $selectedIsfCrOption) {
+                    ForEach(IsfAndOrCrOptions.allCases, id: \.self) { option in
+                        Text(option.rawValue).tag(option)
+                    }
+                }
+                .pickerStyle(MenuPickerStyle())
+                .onChange(of: selectedIsfCrOption) { _, newValue in
+                    switch newValue {
+                    case .isfAndCr:
+                        isfAndCr = true
+                        isf = false
+                        cr = false
+                    case .isf:
+                        isfAndCr = false
+                        isf = true
+                        cr = false
+                    case .cr:
+                        isfAndCr = false
+                        isf = false
+                        cr = true
+                    case .nothing:
+                        isfAndCr = false
+                        isf = false
+                        cr = false
+                    }
+                    hasChanges = true
+                }
+            }
+            .listRowBackground(Color.chart)
+
+            Section {
+                Toggle(isOn: $target_override) {
+                    Text("Override Target")
+                }
+                .onChange(of: target_override) {
+                    hasChanges = true
+                }
+                // Target Glucose Picker
+                if target_override {
+                    let settingsProvider = PickerSettingsProvider.shared
+                    let glucoseSetting = PickerSetting(value: 0, step: targetStep, min: 72, max: 270, type: .glucose)
+
+                    TargetPicker(
+                        label: "Target Glucose",
+                        selection: Binding(
+                            get: { target ?? 100 },
+                            set: { target = $0 }
+                        ),
+                        options: settingsProvider.generatePickerValues(
+                            from: glucoseSetting,
+                            units: state.units,
+                            roundMinToStep: true
+                        ),
+                        units: state.units,
+                        hasChanges: $hasChanges,
+                        targetStep: $targetStep,
+                        displayPickerTarget: $displayPickerTarget,
+                        toggleScrollWheel: toggleScrollWheel
+                    )
+                    .onAppear {
+                        if target == 0 || target == nil {
+                            target = 100
+                        }
+                    }
+                }
+            }
+            .listRowBackground(Color.chart)
+
+            Section {
+                // Picker for Disable SMB settings
+                Picker("Disable SMBs", selection: $selectedDisableSmbOption) {
+                    ForEach(DisableSmbOptions.allCases, id: \.self) { option in
+                        Text(option.rawValue).tag(option)
+                    }
+                }
+                .pickerStyle(MenuPickerStyle())
+                .onChange(of: selectedDisableSmbOption) { _, newValue in
+                    switch newValue {
+                    case .dontDisable:
+                        smbIsOff = false
+                        smbIsScheduledOff = false
+                    case .disable:
+                        smbIsOff = true
+                        smbIsScheduledOff = false
+                    case .disableOnSchedule:
+                        smbIsOff = false
+                        smbIsScheduledOff = true
+                    }
+                    hasChanges = true
+                }
+
+                if smbIsScheduledOff {
+                    // First Hour SMBs Are Disabled
+                    HStack {
+                        Text("From")
+                        Spacer()
+                        Text(
+                            state.is24HourFormat() ? state.format24Hour(Int(truncating: start! as NSNumber)) + ":00" :
+                                state.convertTo12HourFormat(Int(truncating: start! as NSNumber))
+                        )
+                        .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
+
+                        Spacer()
+
+                        Divider().frame(width: 1, height: 20)
+
+                        Spacer()
+
+                        Text("To")
+                        Spacer()
+                        Text(
+                            state.is24HourFormat() ? state.format24Hour(Int(truncating: end! as NSNumber)) + ":00" :
+                                state.convertTo12HourFormat(Int(truncating: end! as NSNumber))
+                        )
+                        .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
+                    }
+                    .onTapGesture {
+                        displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
+                    }
+
+                    if displayPickerDisableSmbSchedule {
+                        HStack {
+                            Picker(selection: Binding(
+                                get: { Int(truncating: start! as NSNumber) },
+                                set: {
+                                    start = Decimal($0)
+                                    hasChanges = true
+                                }
+                            ), label: Text("")) {
+                                if state.is24HourFormat() {
+                                    ForEach(0 ..< 24, id: \.self) { hour in
+                                        Text(state.format24Hour(hour) + ":00").tag(hour)
+                                    }
+                                } else {
+                                    ForEach(0 ..< 24, id: \.self) { hour in
+                                        Text(state.convertTo12HourFormat(hour)).tag(hour)
+                                    }
+                                }
+                            }
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
+
+                            Picker(selection: Binding(
+                                get: { Int(truncating: end! as NSNumber) },
+                                set: {
+                                    end = Decimal($0)
+                                    hasChanges = true
+                                }
+                            ), label: Text("")) {
+                                if state.is24HourFormat() {
+                                    ForEach(0 ..< 24, id: \.self) { hour in
+                                        Text(state.format24Hour(hour) + ":00").tag(hour)
+                                    }
+                                } else {
+                                    ForEach(0 ..< 24, id: \.self) { hour in
+                                        Text(state.convertTo12HourFormat(hour)).tag(hour)
+                                    }
+                                }
+                            }
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
+                        }
+                        .listRowSeparator(.hidden, edges: .top)
+                    }
+                }
+            }
+            .listRowBackground(Color.chart)
+
+            if !smbIsOff {
+                Section {
+                    Toggle(isOn: $advancedSettings) {
+                        Text("Change Max SMB Minutes")
+                    }
+                    .onChange(of: advancedSettings) { hasChanges = true }
+
+                    if advancedSettings {
+                        // SMB Minutes Picker
+                        HStack {
+                            Text("SMB")
+                            Spacer()
+                            Text("\(smbMinutes?.formatted(.number) ?? "\(state.defaultSmbMinutes)") min")
+                                .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
+
+                            Spacer()
+
+                            Divider().frame(width: 1, height: 20)
+
+                            Spacer()
+
+                            Text("UAM")
+                            Spacer()
+                            Text("\(uamMinutes?.formatted(.number) ?? "\(state.defaultUamMinutes)") min")
+                                .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
+                        }
+                        .onTapGesture {
+                            displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                        }
+
+                        if displayPickerSmbMinutes {
+                            HStack {
+                                Picker(
+                                    selection: Binding(
+                                        get: { smbMinutes ?? state.defaultSmbMinutes },
+                                        set: {
+                                            smbMinutes = $0
+                                            hasChanges = true
+                                        }
+                                    ),
+                                    label: Text("")
+                                ) {
+                                    ForEach(Array(stride(from: 0, through: 180, by: 5)), id: \.self) { minute in
+                                        Text("\(minute) min").tag(Decimal(minute))
+                                    }
+                                }
+                                .pickerStyle(WheelPickerStyle())
+                                .frame(maxWidth: .infinity)
+
+                                Picker(
+                                    selection: Binding(
+                                        get: { uamMinutes ?? state.defaultUamMinutes },
+                                        set: {
+                                            uamMinutes = $0
+                                            hasChanges = true
+                                        }
+                                    ),
+                                    label: Text("")
+                                ) {
+                                    ForEach(Array(stride(from: 0, through: 180, by: 5)), id: \.self) { minute in
+                                        Text("\(minute) min").tag(Decimal(minute))
+                                    }
+                                }
+                                .pickerStyle(WheelPickerStyle())
+                                .frame(maxWidth: .infinity)
+                            }
+                            .listRowSeparator(.hidden, edges: .top)
+                        }
+                    }
+                }
+                .listRowBackground(Color.chart)
+            }
+
+            Section {
+                Toggle(isOn: $indefinite) { Text("Enable Indefinitely") }
+                    .onChange(of: indefinite) { hasChanges = true }
+
+                if !indefinite {
+                    HStack {
+                        Text("Duration")
+                        Spacer()
+                        Text(state.formatHrMin(Int(truncating: duration as NSNumber)))
+                            .foregroundColor(!displayPickerDuration ? .primary : .accentColor)
+                    }
+                    .onTapGesture {
+                        displayPickerDuration = toggleScrollWheel(displayPickerDuration)
+                    }
+
+                    if displayPickerDuration {
+                        HStack {
+                            Picker(
+                                selection: Binding(
+                                    get: {
+                                        Int(truncating: duration as NSNumber) / 60
+                                    },
+                                    set: {
+                                        let minutes = Int(truncating: duration as NSNumber) % 60
+                                        let totalMinutes = $0 * 60 + minutes
+                                        duration = Decimal(totalMinutes)
+                                        hasChanges = true
+                                    }
+                                ),
+                                label: Text("")
+                            ) {
+                                ForEach(0 ..< 24) { hour in
+                                    Text("\(hour) hr").tag(hour)
+                                }
+                            }
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
+
+                            Picker(
+                                selection: Binding(
+                                    get: {
+                                        Int(truncating: duration as NSNumber) %
+                                            60 // Convert Decimal to Int for modulus operation
+                                    },
+                                    set: {
+                                        duration = Decimal((Int(truncating: duration as NSNumber) / 60) * 60 + $0)
+                                        hasChanges = true
+                                    }
+                                ),
+                                label: Text("")
+                            ) {
+                                ForEach(Array(stride(from: 0, through: 55, by: 5)), id: \.self) { minute in
+                                    Text("\(minute) min").tag(minute)
+                                }
+                            }
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
+                        }
+                        .listRowSeparator(.hidden, edges: .top)
+                    }
+                }
+            }
+            .listRowBackground(Color.chart)
+        }
+    }
+
+    private var saveButton: some View {
+        let (isInvalid, errorMessage) = isOverrideInvalid()
+
+        return Section(
+            header:
+            HStack {
+                Spacer()
+                Text(errorMessage ?? "").textCase(nil)
+                    .foregroundColor(colorScheme == .dark ? .orange : .accentColor)
+                Spacer()
+            },
+            content: {
+                Button(action: {
+                    saveChanges()
+
+                    do {
+                        guard let moc = override.managedObjectContext else { return }
+                        guard moc.hasChanges else { return }
+                        try moc.save()
+                        Task {
+                            await state.nightscoutManager.uploadProfiles()
+                        }
+                        // Disable previous active Override
+                        if let currentActiveOverride = state.currentActiveOverride {
+                            Task {
+                                await state.disableAllActiveOverrides(
+                                    except: currentActiveOverride.objectID,
+                                    createOverrideRunEntry: false
+                                )
+                                // Update View
+                                state.updateLatestOverrideConfiguration()
+                            }
+                        }
+
+                        hasChanges = false
+                        presentationMode.wrappedValue.dismiss()
+                    } catch {
+                        debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to edit Override")
+                    }
+                }, label: {
+                    Text("Save Override")
+                })
+                    .disabled(isInvalid) // Disable button if changes are invalid
+                    .frame(maxWidth: .infinity, alignment: .center)
+                    .tint(.white)
+            }
+        )
+        .listRowBackground(isInvalid ? Color(.systemGray4) : Color(.systemBlue))
+    }
+
+    private func isOverrideInvalid() -> (Bool, String?) {
+        let noDurationSpecified = !indefinite && duration == 0
+        let targetZeroWithOverride = target_override && (target ?? 0 < 72 || target ?? 0 > 270)
+        let allSettingsDefault = percentage == 100 && !target_override && !advancedSettings &&
+            !smbIsOff && !smbIsScheduledOff
+
+        if noDurationSpecified {
+            return (true, "Enable indefinitely or set a duration.")
+        }
+
+        if targetZeroWithOverride {
+            return (true, "Target glucose is out of range (\(state.units == .mgdL ? "72-270" : "4-14")).")
+        }
+
+        if allSettingsDefault {
+            return (true, "All settings are at default values.")
+        }
+
+        if !hasChanges {
+            return (true, nil)
+        }
+
+        return (false, nil)
+    }
+
+    private func saveChanges() {
+        if !override.isPreset, hasChanges, name == (override.name ?? "") {
+            override.name = "Custom Override"
+        } else {
+            override.name = name
+        }
+        override.percentage = percentage
+        override.indefinite = indefinite
+        override.duration = NSDecimalNumber(decimal: duration)
+        override.target = target_override ? NSDecimalNumber(decimal: target ?? 100) : nil
+        override.advancedSettings = advancedSettings
+        override.smbIsOff = smbIsOff
+        override.smbIsScheduledOff = smbIsScheduledOff
+        override.start = start.map { NSDecimalNumber(decimal: $0) }
+        override.end = end.map { NSDecimalNumber(decimal: $0) }
+        override.isfAndCr = isfAndCr
+        override.isf = isf
+        override.cr = cr
+        override.smbMinutes = smbMinutes.map { NSDecimalNumber(decimal: $0) }
+        override.uamMinutes = uamMinutes.map { NSDecimalNumber(decimal: $0) }
+        override.isUploadedToNS = false
+    }
+
+    private func resetValues() {
+        name = override.name ?? ""
+        percentage = override.percentage
+        indefinite = override.indefinite
+        duration = override.duration?.decimalValue ?? 0
+        target = override.target?.decimalValue
+        advancedSettings = override.advancedSettings
+        smbIsOff = override.smbIsOff
+        smbIsScheduledOff = override.smbIsScheduledOff
+        start = override.start?.decimalValue
+        end = override.end?.decimalValue
+        isfAndCr = override.isfAndCr
+        isf = override.isf
+        cr = override.cr
+        smbMinutes = override.smbMinutes?.decimalValue ?? state.defaultSmbMinutes
+        uamMinutes = override.uamMinutes?.decimalValue ?? state.defaultUamMinutes
+    }
+
+    private func toggleScrollWheel(_ toggle: Bool) -> Bool {
+        displayPickerDuration = false
+        displayPickerPercentage = false
+        displayPickerTarget = false
+        displayPickerDisableSmbSchedule = false
+        displayPickerSmbMinutes = false
+        return !toggle
+    }
+}

Разница между файлами не показана из-за своего большого размера
+ 388 - 0
FreeAPS/Sources/Modules/Adjustments/View/TempTargets/AddTempTargetForm.swift


+ 410 - 0
FreeAPS/Sources/Modules/Adjustments/View/TempTargets/EditTempTargetForm.swift

@@ -0,0 +1,410 @@
+import Foundation
+import SwiftUI
+
+struct EditTempTargetForm: View {
+    @ObservedObject var tempTarget: TempTargetStored
+    @Environment(\.presentationMode) var presentationMode
+    @Environment(\.colorScheme) var colorScheme
+    @Environment(AppState.self) var appState
+    @StateObject var state: Adjustments.StateModel
+    @State private var displayPickerDuration: Bool = false
+    @State private var displayPickerTarget: Bool = false
+    @State private var tempTargetSensitivityAdjustmentType: TempTargetSensitivityAdjustmentType = .standard
+    @State private var durationHours = 0
+    @State private var durationMinutes = 0
+    @State private var targetStep: Decimal = 1
+    @State private var name: String
+    @State private var target: Decimal
+    @State private var duration: Decimal
+    @State private var date: Date
+    @State private var halfBasalTarget: Decimal?
+    @State private var percentage: Double
+
+    @State private var hasChanges = false
+    @State private var showAlert = false
+    @State private var isUsingSlider = false
+    @State private var isPreset = false
+    @State private var isEnabled = false
+
+    init(tempTargetToEdit: TempTargetStored, state: Adjustments.StateModel) {
+        tempTarget = tempTargetToEdit
+        _state = StateObject(wrappedValue: state)
+        _name = State(initialValue: tempTargetToEdit.name ?? "")
+        _target = State(initialValue: tempTargetToEdit.target?.decimalValue ?? 0)
+        _duration = State(initialValue: tempTargetToEdit.duration?.decimalValue ?? 0)
+        _date = State(initialValue: tempTargetToEdit.date ?? Date())
+        _halfBasalTarget = State(initialValue: tempTargetToEdit.halfBasalTarget?.decimalValue ?? state.settingHalfBasalTarget)
+        _isPreset = State(initialValue: tempTargetToEdit.isPreset)
+        _isEnabled = State(initialValue: tempTargetToEdit.enabled)
+
+        let tempTargetHalfBasal: Decimal = (tempTargetToEdit.halfBasalTarget?.decimalValue) ?? state.settingHalfBasalTarget
+
+        let H = tempTargetHalfBasal
+        let T = tempTargetToEdit.target?.decimalValue ?? 100
+        let calcPercentage = state.computeAdjustedPercentage(usingHBT: H, usingTarget: T)
+        _percentage = State(initialValue: calcPercentage)
+    }
+
+    private var dateFormatter: DateFormatter {
+        let f = DateFormatter()
+        f.dateStyle = .short
+        f.timeStyle = .short
+        return f
+    }
+
+    var body: some View {
+        NavigationView {
+            List {
+                editTempTarget()
+                saveButton
+            }
+            .listSectionSpacing(10)
+            .padding(.top, 30)
+            .ignoresSafeArea(edges: .top)
+            .scrollContentBackground(.hidden)
+            .background(appState.trioBackgroundColor(for: colorScheme))
+            .navigationTitle("Edit Temp Target")
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .topBarLeading) {
+                    Button(action: {
+                        presentationMode.wrappedValue.dismiss()
+                    }, label: {
+                        Text("Cancel")
+                    })
+                }
+            }
+            .onAppear {
+                if halfBasalTarget != state.settingHalfBasalTarget { tempTargetSensitivityAdjustmentType = .slider }
+            }
+        }
+    }
+
+    private func calculatedEndDate(from startDate: Date, totalDuration: Decimal) -> Date {
+        let elapsedTime = Date().timeIntervalSince(startDate)
+        let totalDurationSeconds = Int(totalDuration) * 60
+        let remainingTime = max(totalDurationSeconds - Int(elapsedTime), 0)
+        return Date().addingTimeInterval(TimeInterval(remainingTime))
+    }
+
+    private func formattedEndTime(startDate: Date, totalDuration: Decimal) -> String {
+        let endDate = calculatedEndDate(from: startDate, totalDuration: totalDuration)
+        let formatter = DateFormatter()
+
+        if Calendar.current.isDateInToday(endDate) {
+            formatter.dateStyle = .none
+            formatter.timeStyle = .short // show only the time
+        } else {
+            formatter.dateStyle = .short
+            formatter.timeStyle = .short // show Date and time
+        }
+
+        return formatter.string(from: endDate)
+    }
+
+    @ViewBuilder private func editTempTarget() -> some View {
+        Group {
+            Section {
+                HStack {
+                    Text("Name")
+                    Spacer()
+                    TextField("(Optional)", text: $name)
+                        .multilineTextAlignment(.trailing)
+                        .onChange(of: name) {
+                            hasChanges = true
+                        }
+                }
+            }.listRowBackground(Color.chart)
+
+            Section {
+                // Picker on the right side
+                let settingsProvider = PickerSettingsProvider.shared
+                let glucoseSetting = PickerSetting(value: 0, step: targetStep, min: 80, max: 200, type: .glucose)
+                TargetPicker(
+                    label: "Target Glucose",
+                    selection: Binding(
+                        get: { target },
+                        set: { target = $0 }
+                    ),
+                    options: settingsProvider.generatePickerValues(
+                        from: glucoseSetting,
+                        units: state.units,
+                        roundMinToStep: true
+                    ),
+                    units: state.units,
+                    hasChanges: $hasChanges,
+                    targetStep: $targetStep,
+                    displayPickerTarget: $displayPickerTarget,
+                    toggleScrollWheel: toggleScrollWheel
+                )
+                .onChange(of: target) {
+                    percentage = state.computeAdjustedPercentage(usingHBT: halfBasalTarget, usingTarget: target)
+                }
+            }
+            .listRowBackground(Color.chart)
+
+            if target != state.normalTarget {
+                let computedHalfBasalTarget = Decimal(
+                    state
+                        .computeHalfBasalTarget(usingTarget: target, usingPercentage: percentage)
+                )
+
+                if state.isAdjustSensEnabled(usingTarget: target) {
+                    Section(
+                        footer: state.percentageDescription(percentage),
+                        content: {
+                            Picker("Sensitivity Adjustment", selection: $tempTargetSensitivityAdjustmentType) {
+                                ForEach(TempTargetSensitivityAdjustmentType.allCases, id: \.self) { option in
+                                    Text(option.rawValue).tag(option)
+                                }
+                                .pickerStyle(MenuPickerStyle())
+                                .onChange(of: tempTargetSensitivityAdjustmentType) { _, newValue in
+                                    if newValue == .standard {
+                                        halfBasalTarget = nil
+                                        hasChanges = true
+                                        percentage = state.computeAdjustedPercentage(
+                                            usingHBT: halfBasalTarget,
+                                            usingTarget: target
+                                        )
+                                    }
+                                }
+                            }
+
+                            Text("\(formattedPercentage(percentage))% Insulin")
+                                .foregroundColor(isUsingSlider ? .orange : Color.tabBar)
+                                .font(.title3)
+                                .fontWeight(.bold)
+                                .frame(maxWidth: .infinity, alignment: .center)
+
+                            if tempTargetSensitivityAdjustmentType == .slider {
+                                Slider(
+                                    value: Binding(
+                                        get: {
+                                            Double(truncating: percentage as NSNumber)
+                                        },
+                                        set: { newValue in
+                                            percentage = newValue
+                                            hasChanges = true
+                                            halfBasalTarget = Decimal(state.computeHalfBasalTarget(
+                                                usingTarget: target,
+                                                usingPercentage: percentage
+                                            ))
+                                        }
+                                    ),
+                                    in: state.computeSliderLow(usingTarget: target) ... state
+                                        .computeSliderHigh(usingTarget: target),
+                                    step: 5
+                                ) {}
+                                minimumValueLabel: {
+                                    Text("\(state.computeSliderLow(usingTarget: target), specifier: "%.0f")%")
+                                }
+                                maximumValueLabel: {
+                                    Text("\(state.computeSliderHigh(usingTarget: target), specifier: "%.0f")%")
+                                }
+                                .listRowSeparator(.hidden, edges: .top)
+                            }
+                        }
+                    )
+                    .listRowBackground(Color.chart)
+                }
+            }
+
+            Section {
+                DatePicker("Start Time", selection: $date, in: Date.now...)
+                    .onChange(of: date) { hasChanges = true }
+            }.listRowBackground(Color.chart)
+
+            Section {
+                VStack {
+                    HStack {
+                        Text("Duration")
+                        Spacer()
+                        Text(state.formatHrMin(Int(duration)))
+                            .foregroundColor(!displayPickerDuration ? (duration > 0 ? .primary : .secondary) : .accentColor)
+                    }
+                    .onTapGesture {
+                        displayPickerDuration = toggleScrollWheel(displayPickerDuration)
+                    }
+
+                    if displayPickerDuration {
+                        HStack {
+                            Picker(
+                                selection: Binding(
+                                    get: {
+                                        Int(truncating: duration as NSNumber) / 60
+                                    },
+                                    set: {
+                                        let minutes = Int(truncating: duration as NSNumber) % 60
+                                        let totalMinutes = $0 * 60 + minutes
+                                        duration = Decimal(totalMinutes)
+                                        hasChanges = duration > 0 ? true : false // prevents the user from setting 0 min
+                                    }
+                                ),
+                                label: Text("")
+                            ) {
+                                ForEach(0 ..< 24) { hour in
+                                    Text("\(hour) hr").tag(hour)
+                                }
+                            }
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
+
+                            Picker(
+                                selection: Binding(
+                                    get: {
+                                        Int(truncating: duration as NSNumber) %
+                                            60 // Convert Decimal to Int for modulus operation
+                                    },
+                                    set: {
+                                        duration = Decimal((Int(truncating: duration as NSNumber) / 60) * 60 + $0)
+                                        hasChanges = duration > 0 ? true : false
+                                    }
+                                ),
+                                label: Text("")
+                            ) {
+                                ForEach(Array(stride(from: 0, through: 55, by: 5)), id: \.self) { minute in
+                                    Text("\(minute) min").tag(minute)
+                                }
+                            }
+                            .pickerStyle(WheelPickerStyle())
+                            .frame(maxWidth: .infinity)
+                        }
+                        .listRowSeparator(.hidden, edges: .top)
+                    }
+                }
+            }.listRowBackground(Color.chart)
+
+            if isEnabled {
+                Section {
+                    HStack {
+                        Spacer()
+                        Text("Until \(formattedEndTime(startDate: date, totalDuration: duration))").foregroundStyle(.secondary)
+                    }
+                }.listRowBackground(Color.clear)
+            }
+        }
+    }
+
+    private var saveButton: some View {
+        HStack {
+            Spacer()
+            Button(action: {
+                saveChanges()
+                do {
+                    guard let moc = tempTarget.managedObjectContext else { return }
+                    guard moc.hasChanges else { return }
+                    try moc.save()
+
+                    if let currentActiveTempTarget = state.currentActiveTempTarget {
+                        Task {
+                            // TODO: - Creating a Run entry is probably needed for Overrides as well and the reason for "jumping" Overrides?
+                            // Disable previous active Temp Targets
+                            await state.disableAllActiveOverrides(
+                                except: currentActiveTempTarget.objectID,
+                                createOverrideRunEntry: false
+                            )
+
+                            // If the temp target which currently gets edited is enabled, then store it to the Temp Target JSON so that oref uses it
+                            if isEnabled {
+                                let tempTarget = TempTarget(
+                                    name: name,
+                                    createdAt: Date(),
+                                    targetTop: target,
+                                    targetBottom: target,
+                                    duration: duration,
+                                    enteredBy: TempTarget.local,
+                                    reason: TempTarget.custom,
+                                    isPreset: isPreset ? true : false,
+                                    enabled: isEnabled ? true : false,
+                                    halfBasalTarget: halfBasalTarget
+                                )
+
+                                // Store to TempTargetStorage so that oref uses the edited Temp target
+                                state.saveTempTargetToStorage(tempTargets: [tempTarget])
+                            }
+
+                            // Update view
+                            state.updateLatestTempTargetConfiguration()
+                        }
+                    }
+                    hasChanges = false
+                    presentationMode.wrappedValue.dismiss()
+                } catch {
+                    debugPrint("Failed to Edit Temp Target")
+                }
+            }, label: {
+                Text("Save")
+            })
+                .disabled(!hasChanges)
+                .frame(maxWidth: .infinity, alignment: .center)
+                .tint(.white)
+
+            Spacer()
+        }.listRowBackground(hasChanges ? Color(.systemBlue) : Color(.systemGray4))
+    }
+
+    private func saveChanges() {
+        tempTarget.name = name
+        tempTarget.target = NSDecimalNumber(decimal: target)
+        tempTarget.duration = NSDecimalNumber(decimal: duration)
+        tempTarget.date = date
+        tempTarget.isUploadedToNS = false
+        if let halfBasalValue = halfBasalTarget {
+            tempTarget.halfBasalTarget = NSDecimalNumber(decimal: halfBasalValue)
+        } else {
+            tempTarget.halfBasalTarget = nil
+        }
+    }
+
+    private func toggleScrollWheel(_ toggle: Bool) -> Bool {
+        displayPickerDuration = false
+        displayPickerTarget = false
+        return !toggle
+    }
+
+    private func resetValues() {
+        name = tempTarget.name ?? ""
+        target = tempTarget.target?.decimalValue ?? 0
+        duration = tempTarget.duration?.decimalValue ?? 0
+        date = tempTarget.date ?? Date()
+    }
+
+    private func totalDurationInMinutes() -> Int {
+        let durationTotal = (durationHours * 60) + durationMinutes
+        return max(0, durationTotal)
+    }
+
+    private var formatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        return formatter
+    }
+
+    private var glucoseFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        if state.units == .mmolL {
+            formatter.maximumFractionDigits = 1
+        } else {
+            formatter.maximumFractionDigits = 0
+        }
+        formatter.roundingMode = .halfUp
+        return formatter
+    }
+
+    private func formattedPercentage(_ value: Double) -> String {
+        let percentageNumber = NSNumber(value: value)
+        return formatter.string(from: percentageNumber) ?? "\(value)"
+    }
+
+    private func formattedGlucose(glucose: Decimal) -> String {
+        let formattedValue: String
+        if state.units == .mgdL {
+            formattedValue = glucoseFormatter.string(from: glucose as NSDecimalNumber) ?? "\(glucose)"
+        } else {
+            formattedValue = glucose.formattedAsMmolL
+        }
+        return "\(formattedValue) \(state.units.rawValue)"
+    }
+}

+ 19 - 0
FreeAPS/Sources/Modules/Adjustments/View/ViewElements/RadioButton.swift

@@ -0,0 +1,19 @@
+import SwiftUI
+
+struct RadioButton: View {
+    var isSelected: Bool
+    var label: String
+    var action: () -> Void
+
+    var body: some View {
+        Button(action: {
+            action()
+        }) {
+            HStack {
+                Image(systemName: isSelected ? "largecircle.fill.circle" : "circle")
+                Text(label) // Add label inside the button to make it tappable
+            }
+        }
+        .buttonStyle(PlainButtonStyle())
+    }
+}

+ 67 - 0
FreeAPS/Sources/Modules/Adjustments/View/ViewElements/TargetPicker.swift

@@ -0,0 +1,67 @@
+import SwiftUI
+
+struct TargetPicker: View {
+    let label: String
+    @Binding var selection: Decimal
+    let options: [Decimal]
+    let units: GlucoseUnits
+    var hasChanges: Binding<Bool>?
+    @Binding var targetStep: Decimal
+    @Binding var displayPickerTarget: Bool
+    var toggleScrollWheel: (_ picker: Bool) -> Bool
+
+    var body: some View {
+        HStack {
+            Text(label)
+            Spacer()
+            Text(
+                (units == .mgdL ? selection.description : selection.formattedAsMmolL) + " " + units.rawValue
+            )
+            .foregroundColor(!displayPickerTarget ? .primary : .accentColor)
+        }
+        .onTapGesture {
+            displayPickerTarget = toggleScrollWheel(displayPickerTarget)
+        }
+        if displayPickerTarget {
+            HStack {
+                // Radio buttons and text on the left side
+                VStack(alignment: .leading) {
+                    // Radio buttons for step iteration
+                    let stepChoices: [Decimal] = units == .mgdL ? [1, 5] : [1, 9]
+                    ForEach(stepChoices, id: \.self) { step in
+                        let label = (units == .mgdL ? step.description : step.formattedAsMmolL) + " " +
+                            units.rawValue
+                        RadioButton(
+                            isSelected: targetStep == step,
+                            label: label
+                        ) {
+                            targetStep = step
+                            selection = Adjustments.StateModel.roundTargetToStep(selection, step)
+                        }
+                        .padding(.top, 10)
+                    }
+                }
+                .frame(maxWidth: .infinity)
+
+                Spacer()
+
+                // Picker on the right side
+                Picker(selection: Binding(
+                    get: { Adjustments.StateModel.roundTargetToStep(selection, targetStep) },
+                    set: {
+                        selection = $0
+                        hasChanges?.wrappedValue = true // This safely updates if hasChanges is provided
+                    }
+                ), label: Text("")) {
+                    ForEach(options, id: \.self) { option in
+                        Text((units == .mgdL ? option.description : option.formattedAsMmolL) + " " + units.rawValue)
+                            .tag(option)
+                    }
+                }
+                .pickerStyle(WheelPickerStyle())
+                .frame(maxWidth: .infinity)
+            }
+            .listRowSeparator(.hidden, edges: .top)
+        }
+    }
+}

+ 10 - 1
FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsProvider.swift

@@ -12,7 +12,16 @@ extension AlgorithmAdvancedSettings {
         func settings() -> PumpSettings {
         func settings() -> PumpSettings {
             storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self)
             storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self)
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
-                ?? PumpSettings(insulinActionCurve: 6.0, maxBolus: 10, maxBasal: 2)
+                ?? PumpSettings(insulinActionCurve: 10.0, maxBolus: 10, maxBasal: 2)
+        }
+
+        func savePreferences(_ preferences: Preferences) {
+            storage.save(preferences, as: OpenAPS.Settings.preferences)
+            processQueue.async {
+                self.broadcaster.notify(PreferencesObserver.self, on: self.processQueue) {
+                    $0.preferencesDidChange(preferences)
+                }
+            }
         }
         }
 
 
         func save(settings: PumpSettings) -> AnyPublisher<Void, Error> {
         func save(settings: PumpSettings) -> AnyPublisher<Void, Error> {

+ 34 - 69
FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift

@@ -3,31 +3,27 @@ import Observation
 import SwiftUI
 import SwiftUI
 
 
 extension AlgorithmAdvancedSettings {
 extension AlgorithmAdvancedSettings {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var settings: SettingsManager!
-        @ObservationIgnored @Injected() var storage: FileStorage!
-        @ObservationIgnored @Injected() var nightscout: NightscoutManager!
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var settings: SettingsManager!
+        @Injected() var storage: FileStorage!
+        @Injected() var nightscout: NightscoutManager!
 
 
         var units: GlucoseUnits = .mgdL
         var units: GlucoseUnits = .mgdL
 
 
-        var maxDailySafetyMultiplier: Decimal = 3
-        var currentBasalSafetyMultiplier: Decimal = 4
-        var useCustomPeakTime: Bool = false
-        var insulinPeakTime: Decimal = 75
-        var skipNeutralTemps: Bool = false
-        var unsuspendIfNoTemp: Bool = false
-        var suspendZerosIOB: Bool = false
-        var min5mCarbimpact: Decimal = 8
-        var autotuneISFAdjustmentFraction: Decimal = 1.0
-        var remainingCarbsFraction: Decimal = 1.0
-        var remainingCarbsCap: Decimal = 90
-        var noisyCGMTargetMultiplier: Decimal = 1.3
-
-        var insulinActionCurve: Decimal = 6
-
-        var preferences: Preferences {
-            settingsManager.preferences
-        }
+        @Published var maxDailySafetyMultiplier: Decimal = 3
+        @Published var currentBasalSafetyMultiplier: Decimal = 4
+        @Published var useCustomPeakTime: Bool = false
+        @Published var insulinPeakTime: Decimal = 75
+        @Published var skipNeutralTemps: Bool = false
+        @Published var unsuspendIfNoTemp: Bool = false
+        @Published var suspendZerosIOB: Bool = false
+        @Published var min5mCarbimpact: Decimal = 8
+        @Published var autotuneISFAdjustmentFraction: Decimal = 1.0
+        @Published var remainingCarbsFraction: Decimal = 1.0
+        @Published var remainingCarbsCap: Decimal = 90
+        @Published var noisyCGMTargetMultiplier: Decimal = 1.3
+
+        var insulinActionCurve: Decimal = 10
 
 
         var pumpSettings: PumpSettings {
         var pumpSettings: PumpSettings {
             provider.settings()
             provider.settings()
@@ -36,18 +32,22 @@ extension AlgorithmAdvancedSettings {
         override func subscribe() {
         override func subscribe() {
             units = settingsManager.settings.units
             units = settingsManager.settings.units
 
 
-            maxDailySafetyMultiplier = settings.preferences.maxDailySafetyMultiplier
-            currentBasalSafetyMultiplier = settings.preferences.currentBasalSafetyMultiplier
-            useCustomPeakTime = settings.preferences.useCustomPeakTime
-            insulinPeakTime = settings.preferences.insulinPeakTime
-            skipNeutralTemps = settings.preferences.skipNeutralTemps
-            unsuspendIfNoTemp = settings.preferences.unsuspendIfNoTemp
-            suspendZerosIOB = settings.preferences.suspendZerosIOB
-            min5mCarbimpact = settings.preferences.min5mCarbimpact
-            autotuneISFAdjustmentFraction = settings.preferences.autotuneISFAdjustmentFraction
-            remainingCarbsFraction = settings.preferences.remainingCarbsFraction
-            remainingCarbsCap = settings.preferences.remainingCarbsCap
-            noisyCGMTargetMultiplier = settings.preferences.noisyCGMTargetMultiplier
+            subscribePreferencesSetting(\.maxDailySafetyMultiplier, on: $maxDailySafetyMultiplier) {
+                maxDailySafetyMultiplier = $0 }
+            subscribePreferencesSetting(\.currentBasalSafetyMultiplier, on: $currentBasalSafetyMultiplier) {
+                currentBasalSafetyMultiplier = $0 }
+            subscribePreferencesSetting(\.useCustomPeakTime, on: $useCustomPeakTime) { useCustomPeakTime = $0 }
+            subscribePreferencesSetting(\.insulinPeakTime, on: $insulinPeakTime) { insulinPeakTime = $0 }
+            subscribePreferencesSetting(\.unsuspendIfNoTemp, on: $unsuspendIfNoTemp) { unsuspendIfNoTemp = $0 }
+            subscribePreferencesSetting(\.suspendZerosIOB, on: $suspendZerosIOB) { suspendZerosIOB = $0 }
+            subscribePreferencesSetting(\.suspendZerosIOB, on: $suspendZerosIOB) { suspendZerosIOB = $0 }
+            subscribePreferencesSetting(\.min5mCarbimpact, on: $min5mCarbimpact) { min5mCarbimpact = $0 }
+            subscribePreferencesSetting(\.autotuneISFAdjustmentFraction, on: $autotuneISFAdjustmentFraction) {
+                autotuneISFAdjustmentFraction = $0 }
+            subscribePreferencesSetting(\.remainingCarbsFraction, on: $remainingCarbsFraction) { remainingCarbsFraction = $0 }
+            subscribePreferencesSetting(\.remainingCarbsCap, on: $remainingCarbsCap) { remainingCarbsCap = $0 }
+            subscribePreferencesSetting(\.noisyCGMTargetMultiplier, on: $noisyCGMTargetMultiplier) {
+                noisyCGMTargetMultiplier = $0 }
 
 
             insulinActionCurve = pumpSettings.insulinActionCurve
             insulinActionCurve = pumpSettings.insulinActionCurve
         }
         }
@@ -56,42 +56,7 @@ extension AlgorithmAdvancedSettings {
             pumpSettings.insulinActionCurve == insulinActionCurve
             pumpSettings.insulinActionCurve == insulinActionCurve
         }
         }
 
 
-        var isSettingUnchanged: Bool {
-            preferences.maxDailySafetyMultiplier == maxDailySafetyMultiplier &&
-                preferences.currentBasalSafetyMultiplier == currentBasalSafetyMultiplier &&
-                preferences.useCustomPeakTime == useCustomPeakTime &&
-                preferences.insulinPeakTime == insulinPeakTime &&
-                preferences.skipNeutralTemps == skipNeutralTemps &&
-                preferences.unsuspendIfNoTemp == unsuspendIfNoTemp &&
-                preferences.suspendZerosIOB == suspendZerosIOB &&
-                preferences.min5mCarbimpact == min5mCarbimpact &&
-                preferences.autotuneISFAdjustmentFraction == autotuneISFAdjustmentFraction &&
-                preferences.remainingCarbsFraction == remainingCarbsFraction &&
-                preferences.remainingCarbsCap == remainingCarbsCap &&
-                preferences.noisyCGMTargetMultiplier == noisyCGMTargetMultiplier
-        }
-
         func saveIfChanged() {
         func saveIfChanged() {
-            if !isSettingUnchanged {
-                var newSettings = storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self) ?? Preferences()
-
-                newSettings.maxDailySafetyMultiplier = maxDailySafetyMultiplier
-                newSettings.currentBasalSafetyMultiplier = currentBasalSafetyMultiplier
-                newSettings.useCustomPeakTime = useCustomPeakTime
-                newSettings.insulinPeakTime = insulinPeakTime
-                newSettings.skipNeutralTemps = skipNeutralTemps
-                newSettings.unsuspendIfNoTemp = unsuspendIfNoTemp
-                newSettings.suspendZerosIOB = suspendZerosIOB
-                newSettings.min5mCarbimpact = min5mCarbimpact
-                newSettings.autotuneISFAdjustmentFraction = autotuneISFAdjustmentFraction
-                newSettings.remainingCarbsFraction = remainingCarbsFraction
-                newSettings.remainingCarbsCap = remainingCarbsCap
-                newSettings.noisyCGMTargetMultiplier = noisyCGMTargetMultiplier
-
-                newSettings.timestamp = Date()
-                storage.save(newSettings, as: OpenAPS.Settings.preferences)
-            }
-
             if !isPumpSettingUnchanged {
             if !isPumpSettingUnchanged {
                 let settings = PumpSettings(
                 let settings = PumpSettings(
                     insulinActionCurve: insulinActionCurve,
                     insulinActionCurve: insulinActionCurve,

+ 6 - 21
FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift

@@ -4,7 +4,7 @@ import Swinject
 extension AlgorithmAdvancedSettings {
 extension AlgorithmAdvancedSettings {
     struct RootView: BaseView {
     struct RootView: BaseView {
         let resolver: Resolver
         let resolver: Resolver
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
         @State private var shouldDisplayHint: Bool = false
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
         @State var hintDetent = PresentationDetent.large
         @State var selectedVerboseHint: AnyView?
         @State var selectedVerboseHint: AnyView?
@@ -14,23 +14,7 @@ extension AlgorithmAdvancedSettings {
 
 
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
         @EnvironmentObject var appIcons: Icons
         @EnvironmentObject var appIcons: Icons
-
-        private var color: LinearGradient {
-            colorScheme == .dark ? LinearGradient(
-                gradient: Gradient(colors: [
-                    Color.bgDarkBlue,
-                    Color.bgDarkerDarkBlue
-                ]),
-                startPoint: .top,
-                endPoint: .bottom
-            )
-                :
-                LinearGradient(
-                    gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
-                    startPoint: .top,
-                    endPoint: .bottom
-                )
-        }
+        @Environment(AppState.self) var appState
 
 
         var body: some View {
         var body: some View {
             List {
             List {
@@ -120,7 +104,7 @@ extension AlgorithmAdvancedSettings {
                     miniHint: "Number of hours insulin is active in your body.",
                     miniHint: "Number of hours insulin is active in your body.",
                     verboseHint:
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                     VStack(alignment: .leading, spacing: 10) {
-                        Text("Default: 6 hours").bold()
+                        Text("Default: 10 hours").bold()
                         Text(
                         Text(
                             "The Duration of Insulin Action (DIA) defines how long your insulin continues to lower glucose readings after a dose."
                             "The Duration of Insulin Action (DIA) defines how long your insulin continues to lower glucose readings after a dose."
                         )
                         )
@@ -130,7 +114,6 @@ extension AlgorithmAdvancedSettings {
                         Text(
                         Text(
                             "Tip: It is better to use Custom Peak Time rather than adjust your Duration of Insulin Action (DIA)"
                             "Tip: It is better to use Custom Peak Time rather than adjust your Duration of Insulin Action (DIA)"
                         )
                         )
-                        Text("Warning: Decreasing this setting is not advised.").bold()
                     }
                     }
                 )
                 )
 
 
@@ -381,6 +364,7 @@ extension AlgorithmAdvancedSettings {
                     }
                     }
                 )
                 )
             }
             }
+            .listSectionSpacing(sectionSpacing)
             .sheet(isPresented: $shouldDisplayHint) {
             .sheet(isPresented: $shouldDisplayHint) {
                 SettingInputHintView(
                 SettingInputHintView(
                     hintDetent: $hintDetent,
                     hintDetent: $hintDetent,
@@ -390,7 +374,8 @@ extension AlgorithmAdvancedSettings {
                     sheetTitle: "Help"
                     sheetTitle: "Help"
                 )
                 )
             }
             }
-            .scrollContentBackground(.hidden).background(color)
+            .scrollContentBackground(.hidden)
+            .background(appState.trioBackgroundColor(for: colorScheme))
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Additionals")
             .navigationTitle("Additionals")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)

+ 9 - 32
FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift

@@ -2,45 +2,22 @@ import Observation
 import SwiftUI
 import SwiftUI
 
 
 extension AutosensSettings {
 extension AutosensSettings {
-    @Observable final class StateModel: BaseStateModel<Provider> {
-        @ObservationIgnored @Injected() var settings: SettingsManager!
-        @ObservationIgnored @Injected() var storage: FileStorage!
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var settings: SettingsManager!
+        @Injected() var storage: FileStorage!
 
 
         var units: GlucoseUnits = .mgdL
         var units: GlucoseUnits = .mgdL
 
 
-        var autosensMax: Decimal = 1.2
-        var autosensMin: Decimal = 0.7
-        var rewindResetsAutosens: Bool = true
-
-        var preferences: Preferences {
-            settingsManager.preferences
-        }
+        @Published var autosensMax: Decimal = 1.2
+        @Published var autosensMin: Decimal = 0.7
+        @Published var rewindResetsAutosens: Bool = true
 
 
         override func subscribe() {
         override func subscribe() {
             units = settingsManager.settings.units
             units = settingsManager.settings.units
 
 
-            autosensMax = settings.preferences.autosensMax
-            autosensMin = settings.preferences.autosensMin
-            rewindResetsAutosens = settings.preferences.rewindResetsAutosens
-        }
-
-        var isSettingUnchanged: Bool {
-            preferences.autosensMax == autosensMax &&
-                preferences.autosensMin == autosensMin &&
-                preferences.rewindResetsAutosens == rewindResetsAutosens
-        }
-
-        func saveIfChanged() {
-            if !isSettingUnchanged {
-                var newSettings = storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self) ?? Preferences()
-
-                newSettings.autosensMax = autosensMax
-                newSettings.autosensMin = autosensMin
-                newSettings.rewindResetsAutosens = rewindResetsAutosens
-
-                newSettings.timestamp = Date()
-                storage.save(newSettings, as: OpenAPS.Settings.preferences)
-            }
+            subscribePreferencesSetting(\.autosensMax, on: $autosensMax) { autosensMax = $0 }
+            subscribePreferencesSetting(\.autosensMin, on: $autosensMin) { autosensMin = $0 }
+            subscribePreferencesSetting(\.rewindResetsAutosens, on: $rewindResetsAutosens) { rewindResetsAutosens = $0 }
         }
         }
     }
     }
 }
 }

+ 6 - 24
FreeAPS/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift

@@ -4,7 +4,7 @@ import Swinject
 extension AutosensSettings {
 extension AutosensSettings {
     struct RootView: BaseView {
     struct RootView: BaseView {
         let resolver: Resolver
         let resolver: Resolver
-        @State var state = StateModel()
+        @StateObject var state = StateModel()
         @State private var shouldDisplayHint: Bool = false
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
         @State var hintDetent = PresentationDetent.large
         @State var selectedVerboseHint: AnyView?
         @State var selectedVerboseHint: AnyView?
@@ -14,23 +14,7 @@ extension AutosensSettings {
 
 
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
         @EnvironmentObject var appIcons: Icons
         @EnvironmentObject var appIcons: Icons
-
-        private var color: LinearGradient {
-            colorScheme == .dark ? LinearGradient(
-                gradient: Gradient(colors: [
-                    Color.bgDarkBlue,
-                    Color.bgDarkerDarkBlue
-                ]),
-                startPoint: .top,
-                endPoint: .bottom
-            )
-                :
-                LinearGradient(
-                    gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
-                    startPoint: .top,
-                    endPoint: .bottom
-                )
-        }
+        @Environment(AppState.self) var appState
 
 
         var body: some View {
         var body: some View {
             List {
             List {
@@ -53,7 +37,7 @@ extension AutosensSettings {
                     VStack(alignment: .leading, spacing: 10) {
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: 120%").bold()
                         Text("Default: 120%").bold()
                         Text(
                         Text(
-                            "Autosens Max sets the maximum Autosens Ratio used by Autosens, Dynamic ISF, or Sigmoid Formula."
+                            "Autosens Max sets the maximum Autosens Ratio used by Autosens, Dynamic ISF, and Sigmoid Formula."
                         )
                         )
                         Text(
                         Text(
                             "The Autosens Ratio is used to calculate the amount of adjustment needed to basal rates, ISF, and CR."
                             "The Autosens Ratio is used to calculate the amount of adjustment needed to basal rates, ISF, and CR."
@@ -84,7 +68,7 @@ extension AutosensSettings {
                     VStack(alignment: .leading, spacing: 10) {
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: 80%").bold()
                         Text("Default: 80%").bold()
                         Text(
                         Text(
-                            "Autosens Min sets the minimum Autosens Ratio used by Autosens, Dynamic ISF, or Sigmoid Formula."
+                            "Autosens Min sets the minimum Autosens Ratio used by Autosens, Dynamic ISF, and Sigmoid Formula."
                         )
                         )
                         Text(
                         Text(
                             "The Autosens Ratio is used to calculate the amount of adjustment needed to basal rates, ISF, and CR."
                             "The Autosens Ratio is used to calculate the amount of adjustment needed to basal rates, ISF, and CR."
@@ -127,6 +111,7 @@ extension AutosensSettings {
                     }
                     }
                 )
                 )
             }
             }
+            .listSectionSpacing(sectionSpacing)
             .sheet(isPresented: $shouldDisplayHint) {
             .sheet(isPresented: $shouldDisplayHint) {
                 SettingInputHintView(
                 SettingInputHintView(
                     hintDetent: $hintDetent,
                     hintDetent: $hintDetent,
@@ -136,13 +121,10 @@ extension AutosensSettings {
                     sheetTitle: "Help"
                     sheetTitle: "Help"
                 )
                 )
             }
             }
-            .scrollContentBackground(.hidden).background(color)
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Autosens")
             .navigationTitle("Autosens")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
-            .onDisappear {
-                state.saveIfChanged()
-            }
         }
         }
     }
     }
 }
 }

+ 2 - 17
FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift

@@ -16,22 +16,7 @@ extension AutotuneConfig {
         @State var replaceAlert = false
         @State var replaceAlert = false
 
 
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
-        var color: LinearGradient {
-            colorScheme == .dark ? LinearGradient(
-                gradient: Gradient(colors: [
-                    Color.bgDarkBlue,
-                    Color.bgDarkerDarkBlue
-                ]),
-                startPoint: .top,
-                endPoint: .bottom
-            )
-                :
-                LinearGradient(
-                    gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
-                    startPoint: .top,
-                    endPoint: .bottom
-                )
-        }
+        @Environment(AppState.self) var appState
 
 
         private var isfFormatter: NumberFormatter {
         private var isfFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             let formatter = NumberFormatter()
@@ -197,7 +182,7 @@ extension AutotuneConfig {
                     sheetTitle: "Help"
                     sheetTitle: "Help"
                 )
                 )
             }
             }
-            .scrollContentBackground(.hidden).background(color)
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Autotune")
             .navigationTitle("Autotune")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)

+ 34 - 9
FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift

@@ -10,6 +10,7 @@ extension BasalProfileEditor {
         var items: [Item] = []
         var items: [Item] = []
         var total: Decimal = 0.0
         var total: Decimal = 0.0
         var showAlert: Bool = false
         var showAlert: Bool = false
+        var chartData: [BasalProfile]? = []
 
 
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
 
 
@@ -106,16 +107,14 @@ extension BasalProfileEditor {
                 .store(in: &lifetime)
                 .store(in: &lifetime)
         }
         }
 
 
-        func validate() {
-            DispatchQueue.main.async {
-                let uniq = Array(Set(self.items))
-                let sorted = uniq.sorted { $0.timeIndex < $1.timeIndex }
-                sorted.first?.timeIndex = 0
-                if self.items != sorted {
-                    self.items = sorted
-                }
-                self.calcTotal()
+        @MainActor func validate() {
+            let uniq = Array(Set(items))
+            let sorted = uniq.sorted { $0.timeIndex < $1.timeIndex }
+            sorted.first?.timeIndex = 0
+            if items != sorted {
+                items = sorted
             }
             }
+            calcTotal()
         }
         }
 
 
         func availableTimeIndices(_ itemIndex: Int) -> [Int] {
         func availableTimeIndices(_ itemIndex: Int) -> [Int] {
@@ -131,5 +130,31 @@ extension BasalProfileEditor {
 
 
             return (0 ..< timeValues.count).filter { !usedIndicesByOtherItems.contains($0) }
             return (0 ..< timeValues.count).filter { !usedIndicesByOtherItems.contains($0) }
         }
         }
+
+        @MainActor func calculateChartData() {
+            var basals: [BasalProfile] = []
+            let tzOffset = TimeZone.current.secondsFromGMT() * -1
+
+            basals.append(contentsOf: items.enumerated().map { index, item in
+                let startDate = Date(timeIntervalSinceReferenceDate: self.timeValues[item.timeIndex])
+                var endDate = Date(timeIntervalSinceReferenceDate: self.timeValues.last!).addingTimeInterval(30 * 60)
+                if self.items.count > index + 1 {
+                    let nextItem = self.items[index + 1]
+                    endDate = Date(timeIntervalSinceReferenceDate: self.timeValues[nextItem.timeIndex])
+                }
+
+                return BasalProfile(
+                    amount: Double(self.rateValues[item.rateIndex]),
+                    isOverwritten: false,
+                    startDate: startDate.addingTimeInterval(TimeInterval(tzOffset)),
+                    endDate: endDate.addingTimeInterval(TimeInterval(tzOffset))
+                )
+            })
+            basals.sort(by: {
+                $0.startDate > $1.startDate
+            })
+
+            chartData = basals
+        }
     }
     }
 }
 }

+ 112 - 60
FreeAPS/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift

@@ -1,3 +1,4 @@
+import Charts
 import SwiftUI
 import SwiftUI
 import Swinject
 import Swinject
 
 
@@ -7,23 +8,11 @@ extension BasalProfileEditor {
         @State var state = StateModel()
         @State var state = StateModel()
         @State private var editMode = EditMode.inactive
         @State private var editMode = EditMode.inactive
 
 
+        let chartScale = Calendar.current
+            .date(from: DateComponents(year: 2001, month: 01, day: 01, hour: 0, minute: 0, second: 0))
+
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
-        var color: LinearGradient {
-            colorScheme == .dark ? LinearGradient(
-                gradient: Gradient(colors: [
-                    Color.bgDarkBlue,
-                    Color.bgDarkerDarkBlue
-                ]),
-                startPoint: .top,
-                endPoint: .bottom
-            )
-                :
-                LinearGradient(
-                    gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
-                    startPoint: .top,
-                    endPoint: .bottom
-                )
-        }
+        @Environment(AppState.self) var appState
 
 
         private var dateFormatter: DateFormatter {
         private var dateFormatter: DateFormatter {
             let formatter = DateFormatter()
             let formatter = DateFormatter()
@@ -38,11 +27,102 @@ extension BasalProfileEditor {
             return formatter
             return formatter
         }
         }
 
 
+        var basalScheduleChart: some View {
+            Chart {
+                ForEach(state.chartData!, id: \.self) { profile in
+                    RectangleMark(
+                        xStart: .value("start", profile.startDate),
+                        xEnd: .value("end", profile.endDate!),
+                        yStart: .value("rate-start", profile.amount),
+                        yEnd: .value("rate-end", 0)
+                    ).foregroundStyle(
+                        .linearGradient(
+                            colors: [
+                                Color.insulin.opacity(0.6),
+                                Color.insulin.opacity(0.1)
+                            ],
+                            startPoint: .bottom,
+                            endPoint: .top
+                        )
+                    ).alignsMarkStylesWithPlotArea()
+
+                    LineMark(x: .value("End Date", profile.endDate!), y: .value("Amount", profile.amount))
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.insulin)
+
+                    LineMark(x: .value("Start Date", profile.startDate), y: .value("Amount", profile.amount))
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.insulin)
+                }
+            }
+            .chartXAxis {
+                AxisMarks(values: .automatic(desiredCount: 6)) { _ in
+                    AxisValueLabel(format: .dateTime.hour())
+                    AxisGridLine(centered: true, stroke: StrokeStyle(lineWidth: 1, dash: [2, 4]))
+                }
+            }
+            .chartYAxis {
+                AxisMarks(values: .automatic(desiredCount: 2)) { _ in
+                    AxisValueLabel()
+                    AxisGridLine(centered: true, stroke: StrokeStyle(lineWidth: 1, dash: [2, 4]))
+                }
+            }
+            .chartXScale(
+                domain: Calendar.current.startOfDay(for: chartScale!) ... Calendar.current.startOfDay(for: chartScale!)
+                    .addingTimeInterval(60 * 60 * 24)
+            )
+        }
+
+        var saveButton: some View {
+            ZStack {
+                let shouldDisableButton = state.syncInProgress || state.items.isEmpty || !state.hasChanges
+
+                Rectangle()
+                    .frame(width: UIScreen.main.bounds.width, height: 65)
+                    .foregroundStyle(colorScheme == .dark ? Color.bgDarkerDarkBlue : Color.white)
+                    .background(.thinMaterial)
+                    .opacity(0.8)
+                    .clipShape(Rectangle())
+
+                Group {
+                    HStack {
+                        Button {
+                            let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
+                            impactHeavy.impactOccurred()
+                            state.save()
+                        } label: {
+                            HStack {
+                                if state.syncInProgress {
+                                    ProgressView().padding(.trailing, 10)
+                                }
+                                Text(state.syncInProgress ? "Saving..." : "Save")
+                            }.padding(10)
+                        }
+                        .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
+                        .disabled(shouldDisableButton)
+                        .background(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
+                        .tint(.white)
+                        .clipShape(RoundedRectangle(cornerRadius: 8))
+                    }
+                }.padding(5)
+            }
+        }
+
         var body: some View {
         var body: some View {
             Form {
             Form {
-                let shouldDisableButton = state.syncInProgress || state.items.isEmpty || !state.hasChanges
+                if !state.canAdd {
+                    Section {
+                        VStack(alignment: .leading) {
+                            Text(
+                                "Basal profile covers 24 hours. You cannot add more rates. Please remove or adjust existing rates to make space."
+                            ).bold()
+                        }
+                    }.listRowBackground(Color.tabBar)
+                }
 
 
                 Section(header: Text("Schedule")) {
                 Section(header: Text("Schedule")) {
+                    if !state.items.isEmpty {
+                        basalScheduleChart.padding(.vertical)
+                    }
+
                     list
                     list
                 }.listRowBackground(Color.chart)
                 }.listRowBackground(Color.chart)
 
 
@@ -58,25 +138,8 @@ extension BasalProfileEditor {
                             .foregroundColor(.secondary)
                             .foregroundColor(.secondary)
                     }
                     }
                 }.listRowBackground(Color.chart)
                 }.listRowBackground(Color.chart)
-
-                Section {
-                    HStack {
-                        if state.syncInProgress {
-                            ProgressView().padding(.trailing, 10)
-                        }
-                        Button {
-                            let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
-                            impactHeavy.impactOccurred()
-                            state.save()
-                        } label: {
-                            Text(state.syncInProgress ? "Saving..." : "Save")
-                        }
-                        .disabled(shouldDisableButton)
-                        .frame(maxWidth: .infinity, alignment: .center)
-                        .tint(.white)
-                    }
-                }.listRowBackground(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
             }
             }
+            .safeAreaInset(edge: .bottom, spacing: 30) { saveButton }
             .alert(isPresented: $state.showAlert) {
             .alert(isPresented: $state.showAlert) {
                 Alert(
                 Alert(
                     title: Text("Unable to Save"),
                     title: Text("Unable to Save"),
@@ -84,22 +147,28 @@ extension BasalProfileEditor {
                     dismissButton: .default(Text("Close"))
                     dismissButton: .default(Text("Close"))
                 )
                 )
             }
             }
-            .onChange(of: state.items) { state.calcTotal() }
-            .scrollContentBackground(.hidden).background(color)
-            .onAppear(perform: configureView)
+            .onChange(of: state.items) {
+                state.calcTotal()
+                state.calculateChartData()
+            }
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .navigationTitle("Basal Profile")
             .navigationTitle("Basal Profile")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
             .toolbar(content: {
             .toolbar(content: {
-                ToolbarItem(placement: .topBarTrailing) {
-                    EditButton()
+                if state.items.isNotEmpty {
+                    ToolbarItem(placement: .topBarTrailing) {
+                        EditButton()
+                    }
                 }
                 }
                 ToolbarItem(placement: .topBarTrailing) {
                 ToolbarItem(placement: .topBarTrailing) {
-                    addButton
+                    Button(action: { state.add() }) { Image(systemName: "plus") }.disabled(!state.canAdd)
                 }
                 }
             })
             })
             .environment(\.editMode, $editMode)
             .environment(\.editMode, $editMode)
             .onAppear {
             .onAppear {
+                configureView()
                 state.validate()
                 state.validate()
+                state.calculateChartData()
             }
             }
         }
         }
 
 
@@ -135,7 +204,7 @@ extension BasalProfileEditor {
                 }.listRowBackground(Color.chart)
                 }.listRowBackground(Color.chart)
             }
             }
             .padding(.top)
             .padding(.top)
-            .scrollContentBackground(.hidden).background(color)
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .navigationTitle("Set Rate")
             .navigationTitle("Set Rate")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
         }
         }
@@ -162,27 +231,10 @@ extension BasalProfileEditor {
             }
             }
         }
         }
 
 
-        private var addButton: some View {
-            guard state.canAdd else {
-                return AnyView(EmptyView())
-            }
-
-            switch editMode {
-            case .inactive:
-                return AnyView(Button(action: onAdd) { Image(systemName: "plus") })
-            default:
-                return AnyView(EmptyView())
-            }
-        }
-
-        func onAdd() {
-            state.add()
-        }
-
         private func onDelete(offsets: IndexSet) {
         private func onDelete(offsets: IndexSet) {
             state.items.remove(atOffsets: offsets)
             state.items.remove(atOffsets: offsets)
             state.validate()
             state.validate()
-            state.calcTotal()
+            state.calculateChartData()
         }
         }
     }
     }
 }
 }

+ 15 - 0
FreeAPS/Sources/Modules/Base/BaseStateModel.swift

@@ -58,4 +58,19 @@ class BaseStateModel<Provider>: StateModel, Injectable where Provider: FreeAPS.P
             }
             }
             .store(in: &lifetime)
             .store(in: &lifetime)
     }
     }
+
+    func subscribePreferencesSetting<T: Equatable, U: Publisher>(
+        _ keyPath: WritableKeyPath<Preferences, T>,
+        on preferencesPublisher: U, initial: (T) -> Void, map: ((T) -> (T))? = nil, didSet: ((T) -> Void)? = nil
+    ) where U.Output == T, U.Failure == Never {
+        initial(settingsManager.preferences[keyPath: keyPath])
+        preferencesPublisher
+            .removeDuplicates()
+            .map(map ?? { $0 })
+            .sink { [weak self] value in
+                self?.settingsManager.preferences[keyPath: keyPath] = value
+                didSet?(value)
+            }
+            .store(in: &lifetime)
+    }
 }
 }

+ 14 - 27
FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift

@@ -15,22 +15,7 @@ extension BolusCalculatorConfig {
         @State private var booleanPlaceholder: Bool = false
         @State private var booleanPlaceholder: Bool = false
 
 
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
-        var color: LinearGradient {
-            colorScheme == .dark ? LinearGradient(
-                gradient: Gradient(colors: [
-                    Color.bgDarkBlue,
-                    Color.bgDarkerDarkBlue
-                ]),
-                startPoint: .top,
-                endPoint: .bottom
-            )
-                :
-                LinearGradient(
-                    gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
-                    startPoint: .top,
-                    endPoint: .bottom
-                )
-        }
+        @Environment(AppState.self) var appState
 
 
         private var conversionFormatter: NumberFormatter {
         private var conversionFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             let formatter = NumberFormatter()
@@ -47,7 +32,7 @@ extension BolusCalculatorConfig {
         }
         }
 
 
         var body: some View {
         var body: some View {
-            Form {
+            List {
                 SettingInputSection(
                 SettingInputSection(
                     decimalValue: $decimalPlaceholder,
                     decimalValue: $decimalPlaceholder,
                     booleanValue: $state.displayPresets,
                     booleanValue: $state.displayPresets,
@@ -115,21 +100,22 @@ extension BolusCalculatorConfig {
                     type: .conditionalDecimal("fattyMealFactor"),
                     type: .conditionalDecimal("fattyMealFactor"),
                     label: "Enable Fatty Meal Option",
                     label: "Enable Fatty Meal Option",
                     conditionalLabel: "Fatty Meal Bolus Percentage",
                     conditionalLabel: "Fatty Meal Bolus Percentage",
-                    miniHint: "\"Fatty Meal\" option appears in the bolus calculator.",
+                    miniHint: "Adds a \"Fatty Meal\" option to the bolus calculator.",
                     verboseHint:
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
                         Text("Default: OFF").bold()
                         Text("Default Percent: 70%").bold()
                         Text("Default Percent: 70%").bold()
                         Text("Do not enable this feature until you have optimized your CR (carb ratio) setting.").bold()
                         Text("Do not enable this feature until you have optimized your CR (carb ratio) setting.").bold()
                         Text(
                         Text(
-                            "Enabling this setting adds a \"Fatty Meal\" option to the bolus calculator. Once this feature is enabled, a percentage setting will appear below this for you to select."
+                            "Enabling this setting adds a \"Fatty Meal\" option to the bolus calculator. Once this feature is enabled, a percentage setting will appear for you to select."
                         )
                         )
                         Text(
                         Text(
-                            "When you use a Fatty Meal Bolus, the percentage you select for this setting will replace the Recommended Bolus Percentage setting used in that bolus calculation."
+                            "When \"Fatty Meal\" is selected in the bolus calculator, the recommended bolus will be multiplied by the \"Fatty Meal Bolus Percentage\" as well as the \"Recommended Bolus Percentage\"."
                         )
                         )
                         Text(
                         Text(
-                            "Tip: This setting should be ↓LOWER↓ than your Recommended Bolus Percentage setting to enable the bolus calculator the ability to give less than the calculated amount to prevent lows due to carbs absorbing very slowly. This could be useful when eating meals like pizza."
+                            "If you have a \"Recommended Bolus Percentage\" of 80%, and a \"Fatty Meal Bolus Percentage\" of 70%, your recommended bolus will be multiplied by: (80 × 70) ÷ 100 = 56%."
                         )
                         )
+                        Text("This could be useful for slow absorbing meals like pizza.")
                     }
                     }
                 )
                 )
 
 
@@ -148,25 +134,26 @@ extension BolusCalculatorConfig {
                     type: .conditionalDecimal("sweetMealFactor"),
                     type: .conditionalDecimal("sweetMealFactor"),
                     label: "Enable Super Bolus Option",
                     label: "Enable Super Bolus Option",
                     conditionalLabel: "Super Bolus Percentage",
                     conditionalLabel: "Super Bolus Percentage",
-                    miniHint: "\"Super Bolus\" option appears in the bolus calculator.",
+                    miniHint: "Adds a \"Super Bolus\" option to the bolus calculator.",
                     verboseHint:
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
                         Text("Default: OFF").bold()
                         Text("Default Percent: 200%").bold()
                         Text("Default Percent: 200%").bold()
                         Text("Do not enable this feature until you have optimized your CR (carb ratio) setting.").bold()
                         Text("Do not enable this feature until you have optimized your CR (carb ratio) setting.").bold()
                         Text(
                         Text(
-                            "Enabling this setting adds a \"Super Bolus\" option to the bolus calculator. Once this feature is enabled, a percentage setting will appear below this for you to select."
+                            "Enabling this setting adds a \"Super Bolus\" option to the bolus calculator. Once this feature is enabled, a percentage setting will appear for you to select."
                         )
                         )
                         Text(
                         Text(
-                            "When you use a Super Bolus, the percentage you select for this setting will replace the Recommended Bolus Percentage setting used in that bolus calculation."
+                            "When \"Super Bolus\" is selected in the bolus calculator, your current basal rate multiplied by \"Super Bolus Percentage\" will be added to your bolus recommendation."
                         )
                         )
-                        Text("The Super Bolus is a useful option for sweet or fast meals.")
                         Text(
                         Text(
-                            "Tip: This setting should be ↑HIGHER↑ than your Recommended Bolus Percentage setting to enable the bolus calculator the ability to give above the calculated amount to address carbs that absorb very quickly. This could be useful when eating sweets."
+                            "If your current basal rate is 0.8 U/hr and \"Super Bolus Percentage\" is set to 200%: 0.8 × (200 ÷ 100) = 1.6 units will be added to your bolus recommendation."
                         )
                         )
+                        Text("This could be useful for fast absorbing meals like sugary cereal.")
                     }
                     }
                 )
                 )
             }
             }
+            .listSectionSpacing(sectionSpacing)
             .sheet(isPresented: $shouldDisplayHint) {
             .sheet(isPresented: $shouldDisplayHint) {
                 SettingInputHintView(
                 SettingInputHintView(
                     hintDetent: $hintDetent,
                     hintDetent: $hintDetent,
@@ -176,7 +163,7 @@ extension BolusCalculatorConfig {
                     sheetTitle: "Help"
                     sheetTitle: "Help"
                 )
                 )
             }
             }
-            .scrollContentBackground(.hidden).background(color)
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationBarTitle("Bolus Calculator")
             .navigationBarTitle("Bolus Calculator")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)

+ 6 - 21
FreeAPS/Sources/Modules/CGM/View/CGMRootView.swift

@@ -17,26 +17,11 @@ extension CGM {
         @State private var booleanPlaceholder: Bool = false
         @State private var booleanPlaceholder: Bool = false
 
 
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
-        var color: LinearGradient {
-            colorScheme == .dark ? LinearGradient(
-                gradient: Gradient(colors: [
-                    Color.bgDarkBlue,
-                    Color.bgDarkerDarkBlue
-                ]),
-                startPoint: .top,
-                endPoint: .bottom
-            )
-                :
-                LinearGradient(
-                    gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
-                    startPoint: .top,
-                    endPoint: .bottom
-                )
-        }
+        @Environment(AppState.self) var appState
 
 
         var body: some View {
         var body: some View {
             NavigationView {
             NavigationView {
-                Form {
+                List {
                     Section(
                     Section(
                         header: Text("CGM Integration to Trio"),
                         header: Text("CGM Integration to Trio"),
                         content: {
                         content: {
@@ -52,7 +37,7 @@ extension CGM {
 
 
                                 HStack(alignment: .center) {
                                 HStack(alignment: .center) {
                                     Text(
                                     Text(
-                                        "Select your CGM"
+                                        "Select your CGM."
                                     )
                                     )
                                     .font(.footnote)
                                     .font(.footnote)
                                     .foregroundColor(.secondary)
                                     .foregroundColor(.secondary)
@@ -231,7 +216,7 @@ extension CGM {
                         }
                         }
                     )
                     )
                 }
                 }
-                .scrollContentBackground(.hidden).background(color)
+                .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
                 .onAppear(perform: configureView)
                 .onAppear(perform: configureView)
                 .navigationTitle("CGM")
                 .navigationTitle("CGM")
                 .navigationBarTitleDisplayMode(.automatic)
                 .navigationBarTitleDisplayMode(.automatic)
@@ -267,10 +252,10 @@ extension CGM {
                         )
                         )
                     }
                     }
                 }
                 }
-                .onChange(of: setupCGM) {
+                .onChange(of: setupCGM) { _, setupCGM in
                     state.setupCGM = setupCGM
                     state.setupCGM = setupCGM
                 }
                 }
-                .onChange(of: state.setupCGM) {
+                .onChange(of: state.setupCGM) { _, setupCGM in
                     self.setupCGM = setupCGM
                     self.setupCGM = setupCGM
                 }
                 }
                 .screenNavigation(self)
                 .screenNavigation(self)

+ 3 - 18
FreeAPS/Sources/Modules/CalendarEventSettings/View/CalendarEventSettingsRootView.swift

@@ -14,23 +14,7 @@ extension CalendarEventSettings {
 
 
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
         @EnvironmentObject var appIcons: Icons
         @EnvironmentObject var appIcons: Icons
-
-        private var color: LinearGradient {
-            colorScheme == .dark ? LinearGradient(
-                gradient: Gradient(colors: [
-                    Color.bgDarkBlue,
-                    Color.bgDarkerDarkBlue
-                ]),
-                startPoint: .top,
-                endPoint: .bottom
-            )
-                :
-                LinearGradient(
-                    gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
-                    startPoint: .top,
-                    endPoint: .bottom
-                )
-        }
+        @Environment(AppState.self) var appState
 
 
         var body: some View {
         var body: some View {
             List {
             List {
@@ -149,6 +133,7 @@ extension CalendarEventSettings {
                     }
                     }
                 }
                 }
             }
             }
+            .listSectionSpacing(sectionSpacing)
             .sheet(isPresented: $shouldDisplayHint) {
             .sheet(isPresented: $shouldDisplayHint) {
                 SettingInputHintView(
                 SettingInputHintView(
                     hintDetent: $hintDetent,
                     hintDetent: $hintDetent,
@@ -158,7 +143,7 @@ extension CalendarEventSettings {
                     sheetTitle: "Help"
                     sheetTitle: "Help"
                 )
                 )
             }
             }
-            .scrollContentBackground(.hidden).background(color)
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Calendar Events")
             .navigationTitle("Calendar Events")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)

+ 2 - 17
FreeAPS/Sources/Modules/Calibrations/View/CalibrationsRootView.swift

@@ -7,22 +7,7 @@ extension Calibrations {
         @State var state = StateModel()
         @State var state = StateModel()
 
 
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
-        var color: LinearGradient {
-            colorScheme == .dark ? LinearGradient(
-                gradient: Gradient(colors: [
-                    Color.bgDarkBlue,
-                    Color.bgDarkerDarkBlue
-                ]),
-                startPoint: .top,
-                endPoint: .bottom
-            )
-                :
-                LinearGradient(
-                    gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
-                    startPoint: .top,
-                    endPoint: .bottom
-                )
-        }
+        @Environment(AppState.self) var appState
 
 
         private var formatter: NumberFormatter {
         private var formatter: NumberFormatter {
             let formatter = NumberFormatter()
             let formatter = NumberFormatter()
@@ -107,7 +92,7 @@ extension Calibrations {
                     }
                     }
                 }
                 }
             }
             }
-            .scrollContentBackground(.hidden).background(color)
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .dynamicTypeSize(...DynamicTypeSize.xxLarge)
             .dynamicTypeSize(...DynamicTypeSize.xxLarge)
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Calibrations")
             .navigationTitle("Calibrations")

+ 1 - 1
FreeAPS/Sources/Modules/CarbRatioEditor/CarbRatioEditorStateModel.swift

@@ -10,7 +10,7 @@ extension CarbRatioEditor {
 
 
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
 
 
-        let rateValues = stride(from: 1.0, to: 501.0, by: 1.0).map { ($0.decimal ?? .zero) / 10 }
+        let rateValues = stride(from: 30.0, to: 501.0, by: 1.0).map { ($0.decimal ?? .zero) / 10 }
 
 
         var canAdd: Bool {
         var canAdd: Bool {
             guard let lastItem = items.last else { return true }
             guard let lastItem = items.last else { return true }

+ 117 - 60
FreeAPS/Sources/Modules/CarbRatioEditor/View/CarbRatioEditorRootView.swift

@@ -1,3 +1,4 @@
+import Charts
 import SwiftUI
 import SwiftUI
 import Swinject
 import Swinject
 
 
@@ -8,22 +9,7 @@ extension CarbRatioEditor {
         @State private var editMode = EditMode.inactive
         @State private var editMode = EditMode.inactive
 
 
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
-        var color: LinearGradient {
-            colorScheme == .dark ? LinearGradient(
-                gradient: Gradient(colors: [
-                    Color.bgDarkBlue,
-                    Color.bgDarkerDarkBlue
-                ]),
-                startPoint: .top,
-                endPoint: .bottom
-            )
-                :
-                LinearGradient(
-                    gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
-                    startPoint: .top,
-                    endPoint: .bottom
-                )
-        }
+        @Environment(AppState.self) var appState
 
 
         private var dateFormatter: DateFormatter {
         private var dateFormatter: DateFormatter {
             let formatter = DateFormatter()
             let formatter = DateFormatter()
@@ -38,10 +24,50 @@ extension CarbRatioEditor {
             return formatter
             return formatter
         }
         }
 
 
-        var body: some View {
-            Form {
+        var saveButton: some View {
+            ZStack {
                 let shouldDisableButton = state.shouldDisplaySaving || state.items.isEmpty || !state.hasChanges
                 let shouldDisableButton = state.shouldDisplaySaving || state.items.isEmpty || !state.hasChanges
 
 
+                Rectangle()
+                    .frame(width: UIScreen.main.bounds.width, height: 65)
+                    .foregroundStyle(colorScheme == .dark ? Color.bgDarkerDarkBlue : Color.white)
+                    .background(.thinMaterial)
+                    .opacity(0.8)
+                    .clipShape(Rectangle())
+
+                Group {
+                    HStack {
+                        HStack {
+                            Button {
+                                let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
+                                impactHeavy.impactOccurred()
+                                state.save()
+
+                                // deactivate saving display after 1.25 seconds
+                                DispatchQueue.main.asyncAfter(deadline: .now() + 1.25) {
+                                    state.shouldDisplaySaving = false
+                                }
+                            } label: {
+                                HStack {
+                                    if state.shouldDisplaySaving {
+                                        ProgressView().padding(.trailing, 10)
+                                    }
+                                    Text(state.shouldDisplaySaving ? "Saving..." : "Save")
+                                }.padding(10)
+                            }
+                        }
+                        .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
+                        .disabled(shouldDisableButton)
+                        .background(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
+                        .tint(.white)
+                        .clipShape(RoundedRectangle(cornerRadius: 8))
+                    }
+                }.padding(5)
+            }
+        }
+
+        var body: some View {
+            Form {
                 if let autotune = state.autotune, !state.settingsManager.settings.onlyAutotuneBasals {
                 if let autotune = state.autotune, !state.settingsManager.settings.onlyAutotuneBasals {
                     Section(header: Text("Autotune")) {
                     Section(header: Text("Autotune")) {
                         HStack {
                         HStack {
@@ -53,44 +79,33 @@ extension CarbRatioEditor {
                     }.listRowBackground(Color.chart)
                     }.listRowBackground(Color.chart)
                 }
                 }
 
 
+                if !state.canAdd {
+                    Section {
+                        VStack(alignment: .leading) {
+                            Text(
+                                "Carb Ratios cover 24 hours. You cannot add more rates. Please remove or adjust existing rates to make space."
+                            ).bold()
+                        }
+                    }.listRowBackground(Color.tabBar)
+                }
+
                 Section(header: Text("Schedule")) {
                 Section(header: Text("Schedule")) {
                     list
                     list
                 }.listRowBackground(Color.chart)
                 }.listRowBackground(Color.chart)
-
-                Section {
-                    HStack {
-                        if state.shouldDisplaySaving {
-                            ProgressView().padding(.trailing, 10)
-                        }
-
-                        Button {
-                            let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
-                            impactHeavy.impactOccurred()
-                            state.save()
-
-                            // deactivate saving display after 1.25 seconds
-                            DispatchQueue.main.asyncAfter(deadline: .now() + 1.25) {
-                                state.shouldDisplaySaving = false
-                            }
-                        } label: {
-                            Text(state.shouldDisplaySaving ? "Saving..." : "Save")
-                        }
-                        .disabled(shouldDisableButton)
-                        .frame(maxWidth: .infinity, alignment: .center)
-                        .tint(.white)
-                    }
-                }.listRowBackground(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
             }
             }
-            .scrollContentBackground(.hidden).background(color)
+            .safeAreaInset(edge: .bottom, spacing: 30) { saveButton }
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Carb Ratios")
             .navigationTitle("Carb Ratios")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
             .toolbar(content: {
             .toolbar(content: {
-                ToolbarItem(placement: .topBarTrailing) {
-                    EditButton()
+                if state.items.isNotEmpty {
+                    ToolbarItem(placement: .topBarTrailing) {
+                        EditButton()
+                    }
                 }
                 }
                 ToolbarItem(placement: .topBarTrailing) {
                 ToolbarItem(placement: .topBarTrailing) {
-                    addButton
+                    Button(action: { state.add() }) { Image(systemName: "plus") }.disabled(!state.canAdd)
                 }
                 }
             })
             })
             .environment(\.editMode, $editMode)
             .environment(\.editMode, $editMode)
@@ -130,13 +145,14 @@ extension CarbRatioEditor {
                 }.listRowBackground(Color.chart)
                 }.listRowBackground(Color.chart)
             }
             }
             .padding(.top)
             .padding(.top)
-            .scrollContentBackground(.hidden).background(color)
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .navigationTitle("Set Ratio")
             .navigationTitle("Set Ratio")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
         }
         }
 
 
         private var list: some View {
         private var list: some View {
             List {
             List {
+                chart.padding(.vertical)
                 ForEach(state.items.indexed(), id: \.1.id) { index, item in
                 ForEach(state.items.indexed(), id: \.1.id) { index, item in
                     NavigationLink(destination: pickers(for: index)) {
                     NavigationLink(destination: pickers(for: index)) {
                         HStack {
                         HStack {
@@ -157,21 +173,62 @@ extension CarbRatioEditor {
             }
             }
         }
         }
 
 
-        private var addButton: some View {
-            guard state.canAdd else {
-                return AnyView(EmptyView())
-            }
+        let chartScale = Calendar.current
+            .date(from: DateComponents(year: 2001, month: 01, day: 01, hour: 0, minute: 0, second: 0))
 
 
-            switch editMode {
-            case .inactive:
-                return AnyView(Button(action: onAdd) { Image(systemName: "plus") })
-            default:
-                return AnyView(EmptyView())
+        var chart: some View {
+            Chart {
+                ForEach(state.items.indexed(), id: \.1.id) { index, item in
+                    let displayValue = state.rateValues[item.rateIndex]
+
+                    let tzOffset = TimeZone.current.secondsFromGMT() * -1
+                    let startDate = Date(timeIntervalSinceReferenceDate: state.timeValues[item.timeIndex])
+                        .addingTimeInterval(TimeInterval(tzOffset))
+                    let endDate = state.items
+                        .count > index + 1 ?
+                        Date(timeIntervalSinceReferenceDate: state.timeValues[state.items[index + 1].timeIndex])
+                        .addingTimeInterval(TimeInterval(tzOffset)) :
+                        Date(timeIntervalSinceReferenceDate: state.timeValues.last!).addingTimeInterval(30 * 60)
+                        .addingTimeInterval(TimeInterval(tzOffset))
+                    RectangleMark(
+                        xStart: .value("start", startDate),
+                        xEnd: .value("end", endDate),
+                        yStart: .value("rate-start", displayValue),
+                        yEnd: .value("rate-end", 0)
+                    ).foregroundStyle(
+                        .linearGradient(
+                            colors: [
+                                Color.insulin.opacity(0.6),
+                                Color.insulin.opacity(0.1)
+                            ],
+                            startPoint: .bottom,
+                            endPoint: .top
+                        )
+                    ).alignsMarkStylesWithPlotArea()
+
+                    LineMark(x: .value("End Date", startDate), y: .value("Ratio", displayValue))
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.insulin)
+
+                    LineMark(x: .value("Start Date", endDate), y: .value("Ratio", displayValue))
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.insulin)
+                }
+            }
+            .chartXAxis {
+                AxisMarks(values: .automatic(desiredCount: 6)) { _ in
+                    AxisValueLabel(format: .dateTime.hour())
+                    AxisGridLine(centered: true, stroke: StrokeStyle(lineWidth: 1, dash: [2, 4]))
+                }
+            }
+            .chartXScale(
+                domain: Calendar.current.startOfDay(for: chartScale!) ... Calendar.current.startOfDay(for: chartScale!)
+                    .addingTimeInterval(60 * 60 * 24)
+            )
+            .chartYAxis {
+                AxisMarks(values: .automatic(desiredCount: 4)) { _ in
+                    AxisValueLabel()
+                    AxisGridLine(centered: true, stroke: StrokeStyle(lineWidth: 1, dash: [2, 4]))
+                }
             }
             }
-        }
-
-        func onAdd() {
-            state.add()
         }
         }
 
 
         private func onDelete(offsets: IndexSet) {
         private func onDelete(offsets: IndexSet) {

+ 2 - 17
FreeAPS/Sources/Modules/ConfigEditor/View/ConfigEditorRootView.swift

@@ -9,22 +9,7 @@ extension ConfigEditor {
         @State private var showShareSheet = false
         @State private var showShareSheet = false
 
 
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
-        var color: LinearGradient {
-            colorScheme == .dark ? LinearGradient(
-                gradient: Gradient(colors: [
-                    Color.bgDarkBlue,
-                    Color.bgDarkerDarkBlue
-                ]),
-                startPoint: .top,
-                endPoint: .bottom
-            )
-                :
-                LinearGradient(
-                    gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
-                    startPoint: .top,
-                    endPoint: .bottom
-                )
-        }
+        @Environment(AppState.self) var appState
 
 
         var body: some View {
         var body: some View {
             ZStack {
             ZStack {
@@ -58,7 +43,7 @@ extension ConfigEditor {
                     .navigationBarTitleDisplayMode(.automatic)
                     .navigationBarTitleDisplayMode(.automatic)
                     .padding()
                     .padding()
             }
             }
-            .scrollContentBackground(.hidden).background(color)
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
         }
         }
     }
     }
 }
 }

+ 3 - 0
FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift

@@ -10,6 +10,7 @@ enum DataTable {
         case treatments
         case treatments
         case meals
         case meals
         case glucose
         case glucose
+        case adjustments
 
 
         var id: String { rawValue }
         var id: String { rawValue }
 
 
@@ -22,6 +23,8 @@ enum DataTable {
                 name = "Meals"
                 name = "Meals"
             case .glucose:
             case .glucose:
                 name = "Glucose"
                 name = "Glucose"
+            case .adjustments:
+                name = "Adjustments"
             }
             }
 
 
             return NSLocalizedString(name, comment: "History Mode")
             return NSLocalizedString(name, comment: "History Mode")

+ 1 - 1
FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift

@@ -11,7 +11,7 @@ extension DataTable {
         func pumpSettings() -> PumpSettings {
         func pumpSettings() -> PumpSettings {
             storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self)
             storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self)
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
-                ?? PumpSettings(insulinActionCurve: 6, maxBolus: 10, maxBasal: 2)
+                ?? PumpSettings(insulinActionCurve: 10, maxBolus: 10, maxBasal: 2)
         }
         }
 
 
         func deleteCarbsFromNightscout(withID id: String) {
         func deleteCarbsFromNightscout(withID id: String) {

+ 0 - 0
FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift


Некоторые файлы не были показаны из-за большого количества измененных файлов