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

Merge branch 'core-data-sync-trio' into fix/dana_timezone

marionbarker 1 год назад
Родитель
Сommit
fd68ec6cbd
100 измененных файлов с 2233 добавлено и 2319 удалено
  1. 1 1
      .gitmodules
  2. 45 54
      FreeAPS.xcodeproj/project.pbxproj
  3. 0 3
      FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json
  4. 7 142
      FreeAPS/Sources/APS/APSManager.swift
  5. 0 52
      FreeAPS/Sources/APS/FetchAnnouncementsManager.swift
  6. 0 2
      FreeAPS/Sources/APS/OpenAPS/Constants.swift
  7. 5 126
      FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift
  8. 0 80
      FreeAPS/Sources/APS/Storage/AnnouncementsStorage.swift
  9. 1 1
      FreeAPS/Sources/APS/Storage/OverrideStorage.swift
  10. 0 1
      FreeAPS/Sources/Application/FreeAPSApp.swift
  11. 0 1
      FreeAPS/Sources/Assemblies/APSAssembly.swift
  12. 0 1
      FreeAPS/Sources/Assemblies/StorageAssembly.swift
  13. 2 23
      FreeAPS/Sources/Helpers/MainChartHelper.swift
  14. 0 57
      FreeAPS/Sources/Helpers/SettingsRowView.swift
  15. 0 6
      FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings
  16. 0 6
      FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings
  17. 0 6
      FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings
  18. 0 6
      FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings
  19. 0 6
      FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings
  20. 0 6
      FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings
  21. 0 6
      FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings
  22. 0 6
      FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings
  23. 0 6
      FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings
  24. 0 6
      FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings
  25. 0 6
      FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings
  26. 0 6
      FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings
  27. 0 6
      FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings
  28. 0 6
      FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings
  29. 0 6
      FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings
  30. 0 6
      FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings
  31. 0 6
      FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings
  32. 0 6
      FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings
  33. 0 6
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  34. 0 6
      FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings
  35. 0 6
      FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings
  36. 0 6
      FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings
  37. 0 6
      FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings
  38. 0 57
      FreeAPS/Sources/Models/Announcement.swift
  39. 0 17
      FreeAPS/Sources/Models/Autotune.swift
  40. 4 0
      FreeAPS/Sources/Models/BGTargets.swift
  41. 3 10
      FreeAPS/Sources/Models/DecimalPickerSettings.swift
  42. 0 15
      FreeAPS/Sources/Models/FreeAPSSettings.swift
  43. 0 6
      FreeAPS/Sources/Models/Preferences.swift
  44. 1 1
      FreeAPS/Sources/Modules/Adjustments/AdjustmentsProvider.swift
  45. 3 3
      FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+Overrides.swift
  46. 1 1
      FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+TempTargets.swift
  47. 2 2
      FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel.swift
  48. 42 473
      FreeAPS/Sources/Modules/Adjustments/View/AdjustmentsRootView.swift
  49. 20 30
      FreeAPS/Sources/Modules/Adjustments/View/Overrides/AddOverrideForm.swift
  50. 256 0
      FreeAPS/Sources/Modules/Adjustments/View/Overrides/AdjustmentsRootView+Overrides.swift
  51. 19 29
      FreeAPS/Sources/Modules/Adjustments/View/Overrides/EditOverrideForm.swift
  52. 53 0
      FreeAPS/Sources/Modules/Adjustments/View/Overrides/OverrideHelpView.swift
  53. 4 22
      FreeAPS/Sources/Modules/Adjustments/View/TempTargets/AddTempTargetForm.swift
  54. 231 0
      FreeAPS/Sources/Modules/Adjustments/View/TempTargets/AdjustmentsRootView+TempTargets.swift
  55. 16 3
      FreeAPS/Sources/Modules/Adjustments/View/TempTargets/EditTempTargetForm.swift
  56. 39 0
      FreeAPS/Sources/Modules/Adjustments/View/TempTargets/TempTargetHelpView.swift
  57. 3 3
      FreeAPS/Sources/Modules/Adjustments/View/ViewElements/TargetPicker.swift
  58. 0 3
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift
  59. 0 27
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift
  60. 3 1
      FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsDataFlow.swift
  61. 7 1
      FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsProvider.swift
  62. 42 0
      FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift
  63. 108 0
      FreeAPS/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift
  64. 0 10
      FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigDataFlow.swift
  65. 0 15
      FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigProvider.swift
  66. 0 113
      FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigStateModel.swift
  67. 0 198
      FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift
  68. 7 0
      FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift
  69. 1 1
      FreeAPS/Sources/Modules/Calibrations/View/CalibrationsChart.swift
  70. 0 1
      FreeAPS/Sources/Modules/CarbRatioEditor/CarbRatioEditorDataFlow.swift
  71. 0 4
      FreeAPS/Sources/Modules/CarbRatioEditor/CarbRatioEditorProvider.swift
  72. 0 3
      FreeAPS/Sources/Modules/CarbRatioEditor/CarbRatioEditorStateModel.swift
  73. 0 11
      FreeAPS/Sources/Modules/CarbRatioEditor/View/CarbRatioEditorRootView.swift
  74. 1 17
      FreeAPS/Sources/Modules/ContactImage/View/AddContactImageSheet.swift
  75. 1 17
      FreeAPS/Sources/Modules/ContactImage/View/ContactImageDetailView.swift
  76. 75 0
      FreeAPS/Sources/Modules/ContactImage/View/ContactImageHelpView.swift
  77. 9 0
      FreeAPS/Sources/Modules/ContactImage/View/ContactImageRootView.swift
  78. 1 13
      FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift
  79. 4 8
      FreeAPS/Sources/Modules/Home/HomeDataFlow.swift
  80. 9 31
      FreeAPS/Sources/Modules/Home/HomeProvider.swift
  81. 95 0
      FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/GlucoseTargetSetup.swift
  82. 18 0
      FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/StartEndMarkerSetup.swift
  83. 48 36
      FreeAPS/Sources/Modules/Home/HomeStateModel.swift
  84. 48 27
      FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/BasalChart.swift
  85. 1 1
      FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/CobIobChart.swift
  86. 2 2
      FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/DummyCharts.swift
  87. 2 2
      FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/GlucoseChartView.swift
  88. 35 0
      FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/GlucoseTargetsView.swift
  89. 1 1
      FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/OverrideView.swift
  90. 242 0
      FreeAPS/Sources/Modules/Home/View/Chart/ChartLegendView.swift
  91. 7 8
      FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift
  92. 248 0
      FreeAPS/Sources/Modules/Home/View/Header/LoopStatusHelpView.swift
  93. 254 0
      FreeAPS/Sources/Modules/Home/View/Header/LoopStatusView.swift
  94. 14 12
      FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift
  95. 48 23
      FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift
  96. 144 330
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  97. 0 2
      FreeAPS/Sources/Modules/ISFEditor/ISFEditorDataFlow.swift
  98. 0 10
      FreeAPS/Sources/Modules/ISFEditor/ISFEditorProvider.swift
  99. 0 37
      FreeAPS/Sources/Modules/ISFEditor/ISFEditorStateModel.swift
  100. 0 0
      FreeAPS/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift

+ 1 - 1
.gitmodules

@@ -40,4 +40,4 @@
 	branch = trio
 [submodule "DanaKit"]
 	path = DanaKit
-	url = https://github.com/bastiaanv/DanaKit
+	url = https://github.com/loopandlearn/DanaKit

+ 45 - 54
FreeAPS.xcodeproj/project.pbxproj

@@ -64,7 +64,6 @@
 		1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */; };
 		1D845DF2E3324130E1D95E67 /* DataTableProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60744C3E9BB3652895C908CC /* DataTableProvider.swift */; };
 		23888883D4EA091C88480FF2 /* TreatmentsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19984D62EFC0035A9E9644D /* TreatmentsProvider.swift */; };
-		3083261C4B268E353F36CD0B /* AutotuneConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DCCCCE633F5E98E41B0CD3C /* AutotuneConfigDataFlow.swift */; };
 		3171D2818C7C72CD1584BB5E /* GlucoseNotificationSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2C6489D29ECCCAD78E0721 /* GlucoseNotificationSettingsStateModel.swift */; };
 		320D030F724170A637F06D50 /* (null) in Sources */ = {isa = PBXBuildFile; };
 		3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE0725C9D32E00A708ED /* BaseView.swift */; };
@@ -121,8 +120,6 @@
 		38569349270B5DFB0002C50D /* AppGroupSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38569346270B5DFB0002C50D /* AppGroupSource.swift */; };
 		38569353270B5E350002C50D /* CGMRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38569352270B5E350002C50D /* CGMRootView.swift */; };
 		385CEA8225F23DFD002D6D5B /* NightscoutStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385CEA8125F23DFD002D6D5B /* NightscoutStatus.swift */; };
-		385CEAC125F2EA52002D6D5B /* Announcement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385CEAC025F2EA52002D6D5B /* Announcement.swift */; };
-		385CEAC425F2F154002D6D5B /* AnnouncementsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385CEAC325F2F154002D6D5B /* AnnouncementsStorage.swift */; };
 		3862CC2E2743F9F700BF832C /* CalendarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3862CC2D2743F9F700BF832C /* CalendarManager.swift */; };
 		3870FF4725EC187A0088248F /* BloodGlucose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3870FF4225EC13F40088248F /* BloodGlucose.swift */; };
 		3871F39C25ED892B0013ECB5 /* TempTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3871F39B25ED892B0013ECB5 /* TempTarget.swift */; };
@@ -143,12 +140,10 @@
 		389A572026079BAA00BC102F /* Interpolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 389A571F26079BAA00BC102F /* Interpolation.swift */; };
 		389ECDFE2601061500D86C4F /* View+Snapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 389ECDFD2601061500D86C4F /* View+Snapshot.swift */; };
 		389ECE052601144100D86C4F /* ConcurrentMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 389ECE042601144100D86C4F /* ConcurrentMap.swift */; };
-		38A00B1F25FC00F7006BC0B0 /* Autotune.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A00B1E25FC00F7006BC0B0 /* Autotune.swift */; };
 		38A00B2325FC2B55006BC0B0 /* LRUCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A00B2225FC2B55006BC0B0 /* LRUCache.swift */; };
 		38A0363B25ECF07E00FCBB52 /* GlucoseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A0363A25ECF07E00FCBB52 /* GlucoseStorage.swift */; };
 		38A0364225ED069400FCBB52 /* TempBasal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A0364125ED069400FCBB52 /* TempBasal.swift */; };
 		38A13D3225E28B4B00EAA382 /* PumpHistoryEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A13D3125E28B4B00EAA382 /* PumpHistoryEvent.swift */; };
-		38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */; };
 		38A504A425DD9C4000C5B9E8 /* UserDefaultsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A5049125DD9C4000C5B9E8 /* UserDefaultsExtensions.swift */; };
 		38A9260525F012D8009E3739 /* CarbRatios.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A9260425F012D8009E3739 /* CarbRatios.swift */; };
 		38AAF85525FFF846004AF583 /* CurrentGlucoseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */; };
@@ -265,7 +260,6 @@
 		58645BA52CA2D347008AFCE7 /* ForecastSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58645BA42CA2D347008AFCE7 /* ForecastSetup.swift */; };
 		58645BA72CA2D390008AFCE7 /* ChartAxisSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58645BA62CA2D390008AFCE7 /* ChartAxisSetup.swift */; };
 		5864E8592C42CFAE00294306 /* DeterminationStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5864E8582C42CFAE00294306 /* DeterminationStorage.swift */; };
-		587DA1F62B77F3DD00B28F8A /* SettingsRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587DA1F52B77F3DD00B28F8A /* SettingsRowView.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 */; };
@@ -309,12 +303,10 @@
 		7F7B756BE8543965D9FDF1A2 /* DataTableDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A401509D21F7F35D4E109EDA /* DataTableDataFlow.swift */; };
 		8194B80890CDD6A3C13B0FEE /* SnoozeStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26904AACA8D9C15D229D675 /* SnoozeStateModel.swift */; };
 		88AB39B23C9552BD6E0C9461 /* ISFEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB3BAE7494CB771ABAC7B8B /* ISFEditorRootView.swift */; };
-		891DECF7BC20968D7F566161 /* AutotuneConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EF98E22A39CD656A230704 /* AutotuneConfigProvider.swift */; };
 		8B759CFCF47B392BB365C251 /* BasalProfileEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F94DD2853CF42BA4E30616 /* BasalProfileEditorDataFlow.swift */; };
 		9702FF92A09C53942F20D7EA /* TargetsEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DD795BA46B193644D48138C /* TargetsEditorRootView.swift */; };
 		9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0A48AE3AC813A49A517846A /* NightscoutConfigStateModel.swift */; };
 		98641AF4F92123DA668AB931 /* CarbRatioEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BDC6993C1087310EDFC428 /* CarbRatioEditorRootView.swift */; };
-		A05235B9112E677ED03B6E8E /* AutotuneConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CF5ACEE1F0859670E71B2C0 /* AutotuneConfigRootView.swift */; };
 		A33352ED40476125EBAC6EE0 /* CarbRatioEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E22146D3DF4853786C78132 /* CarbRatioEditorDataFlow.swift */; };
 		AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF65DA88F972B56090AD6AC3 /* PumpConfigDataFlow.swift */; };
 		B7C465E9472624D8A2BE2A6A /* (null) in Sources */ = {isa = PBXBuildFile; };
@@ -327,6 +319,8 @@
 		BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */; };
 		BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3CC0712B0B89D50013189E /* MainChartView.swift */; };
 		BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */; };
+		BD4E1A7A2D3681B700D21626 /* GlucoseTargetSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4E1A792D3681AD00D21626 /* GlucoseTargetSetup.swift */; };
+		BD4E1A7C2D3686D900D21626 /* StartEndMarkerSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4E1A7B2D3686D400D21626 /* StartEndMarkerSetup.swift */; };
 		BD4ED4FD2CF9D5E8000EDC9C /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4ED4FC2CF9D5E8000EDC9C /* AppState.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 */; };
@@ -422,7 +416,6 @@
 		CEE9A65E2BBC9F6500EB5194 /* CalibrationsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE9A65D2BBC9F6500EB5194 /* CalibrationsTests.swift */; };
 		D6D02515BBFBE64FEBE89856 /* DataTableRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881E04BA5E0A003DE8E0A9C6 /* DataTableRootView.swift */; };
 		D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */; };
-		D76333C9256787610B3B4875 /* AutotuneConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D295A3F870E826BE371C0BB5 /* AutotuneConfigStateModel.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 */; };
@@ -458,7 +451,9 @@
 		DD1745522C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745512C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift */; };
 		DD1745552C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745542C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift */; };
 		DD1DB7CC2BECCA1F0048B367 /* BuildDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */; };
+		DD1E53592D273F26008F32A4 /* LoopStatusHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1E53582D273F20008F32A4 /* LoopStatusHelpView.swift */; };
 		DD21FCB52C6952AD00AF2C25 /* DecimalPickerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD21FCB42C6952AD00AF2C25 /* DecimalPickerSettings.swift */; };
+		DD2CC85C2D25DA1000445446 /* GlucoseTargetsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2CC85B2D25D9CE00445446 /* GlucoseTargetsView.swift */; };
 		DD32CF982CC82463003686D6 /* TrioRemoteControl+Bolus.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CF972CC82460003686D6 /* TrioRemoteControl+Bolus.swift */; };
 		DD32CF9A2CC8247B003686D6 /* TrioRemoteControl+Meal.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CF992CC8246F003686D6 /* TrioRemoteControl+Meal.swift */; };
 		DD32CF9C2CC82499003686D6 /* TrioRemoteControl+TempTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD32CF9B2CC82495003686D6 /* TrioRemoteControl+TempTarget.swift */; };
@@ -486,6 +481,13 @@
 		DD9ECB712CA9A0BA00AA7C45 /* RemoteControlConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB6E2CA9A0BA00AA7C45 /* RemoteControlConfigProvider.swift */; };
 		DD9ECB722CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB6F2CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift */; };
 		DD9ECB742CA9A0C300AA7C45 /* RemoteControlConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */; };
+		DDA6E2502D22187500C2988C /* ChartLegendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6E24F2D22187500C2988C /* ChartLegendView.swift */; };
+		DDA6E2852D2361F800C2988C /* LoopStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6E2842D2361F800C2988C /* LoopStatusView.swift */; };
+		DDA6E3202D258E0500C2988C /* OverrideHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6E31F2D258E0500C2988C /* OverrideHelpView.swift */; };
+		DDA6E3222D25901100C2988C /* TempTargetHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6E3212D25901100C2988C /* TempTargetHelpView.swift */; };
+		DDA6E3572D25988500C2988C /* ContactImageHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6E3562D25988500C2988C /* ContactImageHelpView.swift */; };
+		DDAA29832D2D1D93006546A1 /* AdjustmentsRootView+Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA29822D2D1D7B006546A1 /* AdjustmentsRootView+Overrides.swift */; };
+		DDAA29852D2D1D9E006546A1 /* AdjustmentsRootView+TempTargets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA29842D2D1D98006546A1 /* AdjustmentsRootView+TempTargets.swift */; };
 		DDB37CC52D05048F00D99BF4 /* ContactImageStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */; };
 		DDB37CC72D05127500D99BF4 /* FontExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC62D05127500D99BF4 /* FontExtensions.swift */; };
 		DDCEBF5B2CC1B76400DF4C36 /* LiveActivity+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */; };
@@ -817,8 +819,6 @@
 		38569346270B5DFB0002C50D /* AppGroupSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppGroupSource.swift; sourceTree = "<group>"; };
 		38569352270B5E350002C50D /* CGMRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMRootView.swift; sourceTree = "<group>"; };
 		385CEA8125F23DFD002D6D5B /* NightscoutStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutStatus.swift; sourceTree = "<group>"; };
-		385CEAC025F2EA52002D6D5B /* Announcement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Announcement.swift; sourceTree = "<group>"; };
-		385CEAC325F2F154002D6D5B /* AnnouncementsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementsStorage.swift; sourceTree = "<group>"; };
 		3862CC2D2743F9F700BF832C /* CalendarManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarManager.swift; sourceTree = "<group>"; };
 		3870FF4225EC13F40088248F /* BloodGlucose.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BloodGlucose.swift; sourceTree = "<group>"; };
 		3871F39B25ED892B0013ECB5 /* TempTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTarget.swift; sourceTree = "<group>"; };
@@ -841,12 +841,10 @@
 		389A571F26079BAA00BC102F /* Interpolation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Interpolation.swift; sourceTree = "<group>"; };
 		389ECDFD2601061500D86C4F /* View+Snapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Snapshot.swift"; sourceTree = "<group>"; };
 		389ECE042601144100D86C4F /* ConcurrentMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConcurrentMap.swift; sourceTree = "<group>"; };
-		38A00B1E25FC00F7006BC0B0 /* Autotune.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Autotune.swift; sourceTree = "<group>"; };
 		38A00B2225FC2B55006BC0B0 /* LRUCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LRUCache.swift; sourceTree = "<group>"; };
 		38A0363A25ECF07E00FCBB52 /* GlucoseStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseStorage.swift; sourceTree = "<group>"; };
 		38A0364125ED069400FCBB52 /* TempBasal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempBasal.swift; sourceTree = "<group>"; };
 		38A13D3125E28B4B00EAA382 /* PumpHistoryEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpHistoryEvent.swift; sourceTree = "<group>"; };
-		38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAnnouncementsManager.swift; sourceTree = "<group>"; };
 		38A5049125DD9C4000C5B9E8 /* UserDefaultsExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtensions.swift; sourceTree = "<group>"; };
 		38A9260425F012D8009E3739 /* CarbRatios.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbRatios.swift; sourceTree = "<group>"; };
 		38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentGlucoseView.swift; sourceTree = "<group>"; };
@@ -967,7 +965,6 @@
 		58645BA42CA2D347008AFCE7 /* ForecastSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastSetup.swift; sourceTree = "<group>"; };
 		58645BA62CA2D390008AFCE7 /* ChartAxisSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartAxisSetup.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>"; };
 		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>"; };
@@ -1007,8 +1004,6 @@
 		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>"; };
 		881E04BA5E0A003DE8E0A9C6 /* DataTableRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataTableRootView.swift; sourceTree = "<group>"; };
-		8CF5ACEE1F0859670E71B2C0 /* AutotuneConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AutotuneConfigRootView.swift; sourceTree = "<group>"; };
-		8DCCCCE633F5E98E41B0CD3C /* AutotuneConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AutotuneConfigDataFlow.swift; sourceTree = "<group>"; };
 		920DDB21E5D0EB813197500D /* ConfigEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorRootView.swift; sourceTree = "<group>"; };
 		9455FA2D92E77A6C4AFED8A3 /* DataTableStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DataTableStateModel.swift; sourceTree = "<group>"; };
 		96653287EDB276A111288305 /* ManualTempBasalDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalDataFlow.swift; sourceTree = "<group>"; };
@@ -1020,7 +1015,6 @@
 		AAFF91130F2FCCC7EBBA11AD /* BasalProfileEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorStateModel.swift; sourceTree = "<group>"; };
 		AF65DA88F972B56090AD6AC3 /* PumpConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigDataFlow.swift; sourceTree = "<group>"; };
 		B5822B15939E719628E9FF7C /* SnoozeRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeRootView.swift; sourceTree = "<group>"; };
-		B5EF98E22A39CD656A230704 /* AutotuneConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AutotuneConfigProvider.swift; sourceTree = "<group>"; };
 		B9B5C0607505A38F256BF99A /* CGMDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CGMDataFlow.swift; sourceTree = "<group>"; };
 		B9CAAEFB2AE70836000F68BC /* branch.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = branch.txt; sourceTree = SOURCE_ROOT; };
 		BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorDataFlow.swift; sourceTree = "<group>"; };
@@ -1030,6 +1024,8 @@
 		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>"; };
 		BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataObserver.swift; sourceTree = "<group>"; };
+		BD4E1A792D3681AD00D21626 /* GlucoseTargetSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseTargetSetup.swift; sourceTree = "<group>"; };
+		BD4E1A7B2D3686D400D21626 /* StartEndMarkerSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartEndMarkerSetup.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>"; };
 		BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideRunStored+helper.swift"; sourceTree = "<group>"; };
@@ -1062,9 +1058,8 @@
 		BDF530D72B40F8AC002CAF43 /* LockScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenView.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>"; };
-		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>"; };
+		C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantValues.swift; sourceTree = "<group>"; };
 		C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalRootView.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>"; };
@@ -1128,7 +1123,6 @@
 		CEE9A65D2BBC9F6500EB5194 /* CalibrationsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalibrationsTests.swift; sourceTree = "<group>"; };
 		CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalStateModel.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>"; };
 		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>"; };
@@ -1164,7 +1158,9 @@
 		DD1745512C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsLimitsSettingsStateModel.swift; sourceTree = "<group>"; };
 		DD1745542C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsLimitsSettingsRootView.swift; sourceTree = "<group>"; };
 		DD1DB7CB2BECCA1F0048B367 /* BuildDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildDetails.swift; sourceTree = "<group>"; };
+		DD1E53582D273F20008F32A4 /* LoopStatusHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStatusHelpView.swift; sourceTree = "<group>"; };
 		DD21FCB42C6952AD00AF2C25 /* DecimalPickerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecimalPickerSettings.swift; sourceTree = "<group>"; };
+		DD2CC85B2D25D9CE00445446 /* GlucoseTargetsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseTargetsView.swift; sourceTree = "<group>"; };
 		DD32CF972CC82460003686D6 /* TrioRemoteControl+Bolus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+Bolus.swift"; sourceTree = "<group>"; };
 		DD32CF992CC8246F003686D6 /* TrioRemoteControl+Meal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+Meal.swift"; sourceTree = "<group>"; };
 		DD32CF9B2CC82495003686D6 /* TrioRemoteControl+TempTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrioRemoteControl+TempTarget.swift"; sourceTree = "<group>"; };
@@ -1192,6 +1188,13 @@
 		DD9ECB6E2CA9A0BA00AA7C45 /* RemoteControlConfigProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfigProvider.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>"; };
+		DDA6E24F2D22187500C2988C /* ChartLegendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartLegendView.swift; sourceTree = "<group>"; };
+		DDA6E2842D2361F800C2988C /* LoopStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStatusView.swift; sourceTree = "<group>"; };
+		DDA6E31F2D258E0500C2988C /* OverrideHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideHelpView.swift; sourceTree = "<group>"; };
+		DDA6E3212D25901100C2988C /* TempTargetHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetHelpView.swift; sourceTree = "<group>"; };
+		DDA6E3562D25988500C2988C /* ContactImageHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageHelpView.swift; sourceTree = "<group>"; };
+		DDAA29822D2D1D7B006546A1 /* AdjustmentsRootView+Overrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentsRootView+Overrides.swift"; sourceTree = "<group>"; };
+		DDAA29842D2D1D98006546A1 /* AdjustmentsRootView+TempTargets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentsRootView+TempTargets.swift"; sourceTree = "<group>"; };
 		DDB37CC22D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDB37CC32D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageStorage.swift; sourceTree = "<group>"; };
@@ -1558,7 +1561,6 @@
 				DDD163032C4C67B400CD525A /* Adjustments */,
 				DD1745382C55BF8B00211FAC /* AlgorithmAdvancedSettings */,
 				DD1745422C55C5C400211FAC /* AutosensSettings */,
-				672F63EEAE27400625E14BAD /* AutotuneConfig */,
 				A42F1FEDFFD0DDE00AAD54D3 /* BasalProfileEditor */,
 				3811DE0425C9D32E00A708ED /* Base */,
 				BD7DA9A32AE06DBA00601B20 /* BolusCalculatorConfig */,
@@ -1823,7 +1825,6 @@
 				CE95BF562BA5F5FE00DC3DE3 /* PluginManager.swift */,
 				3811DF0F25CAAAE200A708ED /* APSManager.swift */,
 				38BF021E25E7F0DE00579895 /* DeviceDataManager.swift */,
-				38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */,
 				38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */,
 				38192E06261BA9960094D973 /* FetchTreatmentsManager.swift */,
 				3856933F270B57A00002C50D /* CGM */,
@@ -1888,6 +1889,7 @@
 		3833B51E260264AC003021B3 /* Chart */ = {
 			isa = PBXGroup;
 			children = (
+				DDA6E24F2D22187500C2988C /* ChartLegendView.swift */,
 				BD3CC0712B0B89D50013189E /* MainChartView.swift */,
 				BDDAF9F12D0055CC00B34E7A /* ChartElements */,
 			);
@@ -1897,6 +1899,8 @@
 		3833B51F260264B6003021B3 /* Header */ = {
 			isa = PBXGroup;
 			children = (
+				DD1E53582D273F20008F32A4 /* LoopStatusHelpView.swift */,
+				DDA6E2842D2361F800C2988C /* LoopStatusView.swift */,
 				383420D525FFE38C002D46C1 /* LoopView.swift */,
 				38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */,
 				38DAB27F260CBB7F00F74C1A /* PumpView.swift */,
@@ -2000,9 +2004,7 @@
 				DD07CA132CE80B73002D45A9 /* TimeInRangeChartStyle.swift */,
 				DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */,
 				DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */,
-				385CEAC025F2EA52002D6D5B /* Announcement.swift */,
 				388E5A5F25B6F2310019842D /* Autosens.swift */,
-				38A00B1E25FC00F7006BC0B0 /* Autotune.swift */,
 				388358C725EEF6D200E024B2 /* BasalProfileEntry.swift */,
 				38D0B3B525EBE24900CB6E88 /* Battery.swift */,
 				382C134A25F14E3700715CE1 /* BGTargets.swift */,
@@ -2079,7 +2081,6 @@
 				FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */,
 				FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */,
 				CEA4F62229BE10F70011ADF7 /* SavitzkyGolayFilter.swift */,
-				587DA1F52B77F3DD00B28F8A /* SettingsRowView.swift */,
 				BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */,
 				BD1661302B82ADAB00256551 /* CustomProgressView.swift */,
 				581516A32BCED84A00BF67D7 /* DebuggingIdentifiers.swift */,
@@ -2094,7 +2095,6 @@
 			isa = PBXGroup;
 			children = (
 				CE82E02428E867BA00473A9C /* AlertStorage.swift */,
-				385CEAC325F2F154002D6D5B /* AnnouncementsStorage.swift */,
 				38AEE75625F0F18E0013F05B /* CarbsStorage.swift */,
 				DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */,
 				5864E8582C42CFAE00294306 /* DeterminationStorage.swift */,
@@ -2331,14 +2331,6 @@
 			path = View;
 			sourceTree = "<group>";
 		};
-		55DE731ACE8289FAF3819077 /* View */ = {
-			isa = PBXGroup;
-			children = (
-				8CF5ACEE1F0859670E71B2C0 /* AutotuneConfigRootView.swift */,
-			);
-			path = View;
-			sourceTree = "<group>";
-		};
 		5825D1622BD405AE00F36E9B /* Helper */ = {
 			isa = PBXGroup;
 			children = (
@@ -2365,6 +2357,8 @@
 		58645B972CA2D16A008AFCE7 /* HomeStateModel+Setup */ = {
 			isa = PBXGroup;
 			children = (
+				BD4E1A7B2D3686D400D21626 /* StartEndMarkerSetup.swift */,
+				BD4E1A792D3681AD00D21626 /* GlucoseTargetSetup.swift */,
 				BDA6CC872CAF219800F942F9 /* TempTargetSetup.swift */,
 				58645B982CA2D1A4008AFCE7 /* GlucoseSetup.swift */,
 				58645B9A2CA2D24F008AFCE7 /* CarbSetup.swift */,
@@ -2401,17 +2395,6 @@
 			path = TargetsEditor;
 			sourceTree = "<group>";
 		};
-		672F63EEAE27400625E14BAD /* AutotuneConfig */ = {
-			isa = PBXGroup;
-			children = (
-				8DCCCCE633F5E98E41B0CD3C /* AutotuneConfigDataFlow.swift */,
-				B5EF98E22A39CD656A230704 /* AutotuneConfigProvider.swift */,
-				D295A3F870E826BE371C0BB5 /* AutotuneConfigStateModel.swift */,
-				55DE731ACE8289FAF3819077 /* View */,
-			);
-			path = AutotuneConfig;
-			sourceTree = "<group>";
-		};
 		6B1A8D1C2B14D91600E76752 /* LiveActivity */ = {
 			isa = PBXGroup;
 			children = (
@@ -2491,6 +2474,8 @@
 		BD793CAD2CE7660C00D669AC /* Overrides */ = {
 			isa = PBXGroup;
 			children = (
+				DDAA29822D2D1D7B006546A1 /* AdjustmentsRootView+Overrides.swift */,
+				DDA6E31F2D258E0500C2988C /* OverrideHelpView.swift */,
 				DDD1631B2C4C697400CD525A /* AddOverrideForm.swift */,
 				DDD163192C4C695E00CD525A /* EditOverrideForm.swift */,
 			);
@@ -2500,8 +2485,10 @@
 		BD793CAE2CE7661D00D669AC /* TempTargets */ = {
 			isa = PBXGroup;
 			children = (
+				DDA6E3212D25901100C2988C /* TempTargetHelpView.swift */,
 				58A3D5392C96D4DE003F90FC /* AddTempTargetForm.swift */,
 				5825A1BD2C97335C0046467E /* EditTempTargetForm.swift */,
+				DDAA29842D2D1D98006546A1 /* AdjustmentsRootView+TempTargets.swift */,
 			);
 			path = TempTargets;
 			sourceTree = "<group>";
@@ -2528,6 +2515,7 @@
 		BDDAF9F12D0055CC00B34E7A /* ChartElements */ = {
 			isa = PBXGroup;
 			children = (
+				DD2CC85B2D25D9CE00445446 /* GlucoseTargetsView.swift */,
 				BDDAF9EE2D00553E00B34E7A /* SelectionPopoverView.swift */,
 				582DF9742C8CDB92001F516D /* GlucoseChartView.swift */,
 				582DF9762C8CDBE7001F516D /* InsulinView.swift */,
@@ -3004,6 +2992,7 @@
 		E592A3722CEEC038009A472C /* View */ = {
 			isa = PBXGroup;
 			children = (
+				DDA6E3562D25988500C2988C /* ContactImageHelpView.swift */,
 				E592A3712CEEC038009A472C /* ContactImageRootView.swift */,
 				BDC531112D1060FA00088832 /* ContactImageDetailView.swift */,
 				BDC531132D10611D00088832 /* AddContactImageSheet.swift */,
@@ -3431,7 +3420,6 @@
 				5825A1BE2C97335C0046467E /* EditTempTargetForm.swift in Sources */,
 				19D466A329AA2B80004D5F33 /* MealSettingsDataFlow.swift in Sources */,
 				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
-				385CEAC425F2F154002D6D5B /* AnnouncementsStorage.swift in Sources */,
 				38AEE73D25F0200C0013F05B /* FreeAPSSettings.swift in Sources */,
 				38FCF3FD25E997A80078B0D1 /* PumpHistoryStorage.swift in Sources */,
 				58645BA72CA2D390008AFCE7 /* ChartAxisSetup.swift in Sources */,
@@ -3549,11 +3537,16 @@
 				3811DEE825CA063400A708ED /* Injected.swift in Sources */,
 				DD1745152C54388A00211FAC /* TherapySettingsView.swift in Sources */,
 				585E2CAE2BE7BF46006ECF1A /* PumpEvent+helper.swift in Sources */,
+				DDAA29832D2D1D93006546A1 /* AdjustmentsRootView+Overrides.swift in Sources */,
 				DDD1631F2C4C6F6900CD525A /* TrioCoreDataPersistentContainer.xcdatamodeld in Sources */,
 				DD1745482C55C61D00211FAC /* AutosensSettingsStateModel.swift in Sources */,
 				DD1745462C55C61500211FAC /* AutosensSettingsProvider.swift in Sources */,
+				DDA6E2852D2361F800C2988C /* LoopStatusView.swift in Sources */,
+				DDA6E3202D258E0500C2988C /* OverrideHelpView.swift in Sources */,
+				DDA6E2502D22187500C2988C /* ChartLegendView.swift in Sources */,
 				3811DEAF25C9D88300A708ED /* KeyValueStorage.swift in Sources */,
 				DDD6D4D32CDE90720029439A /* HbA1cDisplayUnit.swift in Sources */,
+				DDA6E3572D25988500C2988C /* ContactImageHelpView.swift in Sources */,
 				38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */,
 				388358C825EEF6D200E024B2 /* BasalProfileEntry.swift in Sources */,
 				3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */,
@@ -3631,6 +3624,7 @@
 				190EBCC829FF13AA00BA767D /* UserInterfaceSettingsStateModel.swift in Sources */,
 				DDF847EA2C5DABAC0049BB3B /* WatchConfigGarminView.swift in Sources */,
 				38BF021F25E7F0DE00579895 /* DeviceDataManager.swift in Sources */,
+				BD4E1A7A2D3681B700D21626 /* GlucoseTargetSetup.swift in Sources */,
 				BDB3C1192C03DD1000CEEAA1 /* UserDefaultsExtension.swift in Sources */,
 				38A504A425DD9C4000C5B9E8 /* UserDefaultsExtensions.swift in Sources */,
 				38FE826A25CC82DB001FF17A /* NetworkService.swift in Sources */,
@@ -3653,13 +3647,13 @@
 				BDFD165A2AE40438007F0DDA /* TreatmentsRootView.swift in Sources */,
 				38E98A2525F52C9300C0CED0 /* IssueReporter.swift in Sources */,
 				DD1745522C55CA5D00211FAC /* UnitsLimitsSettingsStateModel.swift in Sources */,
+				DD2CC85C2D25DA1000445446 /* GlucoseTargetsView.swift in Sources */,
 				190EBCC429FF136900BA767D /* UserInterfaceSettingsDataFlow.swift in Sources */,
 				5A2325582BFCC168003518CA /* NightscoutConnectView.swift in Sources */,
 				3811DEB025C9D88300A708ED /* BaseKeychain.swift in Sources */,
 				110AEDE42C5193D200615CC9 /* BolusIntentRequest.swift in Sources */,
 				3811DE4325C9D4A100A708ED /* SettingsProvider.swift in Sources */,
 				45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */,
-				587DA1F62B77F3DD00B28F8A /* SettingsRowView.swift in Sources */,
 				CE7CA34E2A064973004BE681 /* AppShortcuts.swift in Sources */,
 				38C4D33A25E9A1ED00D30B77 /* NSObject+AssociatedValues.swift in Sources */,
 				110AEDEB2C51A0AE00615CC9 /* ShortcutsConfigView.swift in Sources */,
@@ -3684,8 +3678,8 @@
 				CE1F6DDB2BAE08B60064EB8D /* TidepoolManager.swift in Sources */,
 				BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */,
 				9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */,
+				DDA6E3222D25901100C2988C /* TempTargetHelpView.swift in Sources */,
 				58645B9D2CA2D275008AFCE7 /* DeterminationSetup.swift in Sources */,
-				38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */,
 				DD1745442C55C60E00211FAC /* AutosensSettingsDataFlow.swift in Sources */,
 				BDCAF2382C639F35002DC907 /* SettingItems.swift in Sources */,
 				58D08B342C8DF9A700AA37D3 /* CobIobChart.swift in Sources */,
@@ -3713,7 +3707,6 @@
 				DD1745242C55526000211FAC /* SMBSettingsStateModel.swift in Sources */,
 				F90692D1274B99B60037068D /* HealthKitProvider.swift in Sources */,
 				19F95FF729F10FEE00314DDC /* StatStateModel.swift in Sources */,
-				385CEAC125F2EA52002D6D5B /* Announcement.swift in Sources */,
 				8B759CFCF47B392BB365C251 /* BasalProfileEditorDataFlow.swift in Sources */,
 				195D80B42AF6973A00D25097 /* DynamicSettingsRootView.swift in Sources */,
 				389442CB25F65F7100FA1F27 /* NightscoutTreatment.swift in Sources */,
@@ -3730,6 +3723,7 @@
 				F90692AA274B7AAE0037068D /* HealthKitManager.swift in Sources */,
 				38887CCE25F5725200944304 /* IOBEntry.swift in Sources */,
 				38E98A2425F52C9300C0CED0 /* Logger.swift in Sources */,
+				DD1E53592D273F26008F32A4 /* LoopStatusHelpView.swift in Sources */,
 				CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */,
 				195D80BB2AF6980B00D25097 /* DynamicSettingsStateModel.swift in Sources */,
 				E00EEC0327368630002FF094 /* ServiceAssembly.swift in Sources */,
@@ -3793,6 +3787,7 @@
 				CE7CA34F2A064973004BE681 /* BaseIntentsRequest.swift in Sources */,
 				E592A3772CEEC038009A472C /* ContactImageStateModel.swift in Sources */,
 				E592A3782CEEC038009A472C /* ContactImageDataFlow.swift in Sources */,
+				DDAA29852D2D1D9E006546A1 /* AdjustmentsRootView+TempTargets.swift in Sources */,
 				E592A3792CEEC038009A472C /* ContactImageRootView.swift in Sources */,
 				BDC531182D1062F200088832 /* ContactImageState.swift in Sources */,
 				E592A37A2CEEC038009A472C /* ContactImageProvider.swift in Sources */,
@@ -3802,7 +3797,6 @@
 				DDD163162C4C690300CD525A /* AdjustmentsDataFlow.swift in Sources */,
 				BDF34F932C10D0E100D51995 /* LiveActivityAttributes+Helper.swift in Sources */,
 				E0D4F80527513ECF00BDF1FE /* HealthKitSample.swift in Sources */,
-				38A00B1F25FC00F7006BC0B0 /* Autotune.swift in Sources */,
 				38AAF85525FFF846004AF583 /* CurrentGlucoseView.swift in Sources */,
 				041D1E995A6AE92E9289DC49 /* TreatmentsDataFlow.swift in Sources */,
 				DD32CF9E2CC824C5003686D6 /* TrioRemoteControl+Override.swift in Sources */,
@@ -3816,6 +3810,7 @@
 				19D4E4EB29FC6A9F00351451 /* Charts.swift in Sources */,
 				BDC531162D10629000088832 /* ContactPicture.swift in Sources */,
 				FEFFA7A22929FE49007B8193 /* UIDevice+Extensions.swift in Sources */,
+				BD4E1A7C2D3686D900D21626 /* StartEndMarkerSetup.swift in Sources */,
 				F90692D3274B9A130037068D /* AppleHealthKitRootView.swift in Sources */,
 				BDF34F852C10C62E00D51995 /* GlucoseData.swift in Sources */,
 				19E1F7EC29D082FE005C8D20 /* IconConfigStateModel.swift in Sources */,
@@ -3833,11 +3828,7 @@
 				38E44534274E411700EC9A94 /* Disk+InternalHelpers.swift in Sources */,
 				38A00B2325FC2B55006BC0B0 /* LRUCache.swift in Sources */,
 				DDD163122C4C689900CD525A /* AdjustmentsStateModel.swift in Sources */,
-				3083261C4B268E353F36CD0B /* AutotuneConfigDataFlow.swift in Sources */,
 				DD1745132C54169400211FAC /* DevicesView.swift in Sources */,
-				891DECF7BC20968D7F566161 /* AutotuneConfigProvider.swift in Sources */,
-				D76333C9256787610B3B4875 /* AutotuneConfigStateModel.swift in Sources */,
-				A05235B9112E677ED03B6E8E /* AutotuneConfigRootView.swift in Sources */,
 				7F7B756BE8543965D9FDF1A2 /* DataTableDataFlow.swift in Sources */,
 				1D845DF2E3324130E1D95E67 /* DataTableProvider.swift in Sources */,
 				19F95FFA29F1102A00314DDC /* StatRootView.swift in Sources */,

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

@@ -1,9 +1,6 @@
 {
   "units" : "mg/dL",
   "closedLoop" : false,
-  "allowAnnouncements" : false,
-  "useAutotune" : false,
-  "onlyAutotuneBasals" : false,
   "isUploadEnabled" : false,
   "isDownloadEnabled" : false,
   "useLocalGlucoseSource" : false,

+ 7 - 142
FreeAPS/Sources/APS/APSManager.swift

@@ -8,7 +8,6 @@ import Swinject
 
 protocol APSManager {
     func heartbeat(date: Date)
-    func autotune() async -> Autotune?
     func enactBolus(amount: Double, isSMB: Bool) async
     var pumpManager: PumpManagerUI? { get set }
     var bluetoothManager: BluetoothStateManager? { get }
@@ -21,14 +20,12 @@ protocol APSManager {
     var pumpExpiresAtDate: CurrentValueSubject<Date?, Never> { get }
     var isManualTempBasal: Bool { get }
     func enactTempBasal(rate: Double, duration: TimeInterval) async
-    func makeProfiles() async throws -> Bool
     func determineBasal() async -> Bool
     func determineBasalSync() async
     func simulateDetermineBasal(carbs: Decimal, iob: Decimal) async -> Determination?
     func roundBolus(amount: Decimal) -> Decimal
     var lastError: CurrentValueSubject<Error?, Never> { get }
     func cancelBolus() async
-    func enactAnnouncement(_ announcement: Announcement)
 }
 
 enum APSError: LocalizedError {
@@ -64,13 +61,11 @@ final class BaseAPSManager: APSManager, Injectable {
     @Injected() private var alertHistoryStorage: AlertHistoryStorage!
     @Injected() private var tempTargetsStorage: TempTargetsStorage!
     @Injected() private var carbsStorage: CarbsStorage!
-    @Injected() private var announcementsStorage: AnnouncementsStorage!
     @Injected() private var determinationStorage: DeterminationStorage!
     @Injected() private var deviceDataManager: DeviceDataManager!
     @Injected() private var nightscout: NightscoutManager!
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var broadcaster: Broadcaster!
-    @Persisted(key: "lastAutotuneDate") private var lastAutotuneDate = Date()
     @Persisted(key: "lastLoopStartDate") private var lastLoopStartDate: Date = .distantPast
     @Persisted(key: "lastLoopDate") var lastLoopDate: Date = .distantPast {
         didSet {
@@ -135,7 +130,7 @@ final class BaseAPSManager: APSManager, Injectable {
             let wasParsed = storage.parseOnFileSettingsToMgdL()
             if wasParsed {
                 Task {
-                    try await makeProfiles()
+                    await openAPS.createProfiles()
                 }
             }
         }
@@ -390,16 +385,13 @@ final class BaseAPSManager: APSManager, Injectable {
         do {
             let now = Date()
 
-            // Start fetching asynchronously
-            let (currentTemp, _, _, _) = try await (
-                fetchCurrentTempBasal(date: now),
-                makeProfiles(),
-                autosense(),
-                dailyAutotune()
-            )
+            // Parallelize the fetches using async let
+            async let currentTemp = fetchCurrentTempBasal(date: now)
+            async let autosenseResult = autosense()
 
-            // Determine basal using the fetched temp and current time
-            let determination = try await openAPS.determineBasal(currentTemp: currentTemp, clock: now)
+            _ = try await autosenseResult
+            await openAPS.createProfiles()
+            let determination = try await openAPS.determineBasal(currentTemp: await currentTemp, clock: now)
 
             if let determination = determination {
                 DispatchQueue.main.async {
@@ -433,18 +425,6 @@ final class BaseAPSManager: APSManager, Injectable {
         }
     }
 
-    func makeProfiles() async throws -> Bool {
-        let tunedProfile = await openAPS.makeProfiles(useAutotune: settings.useAutotune)
-        if let basalProfile = tunedProfile?.basalProfile {
-            processQueue.async {
-                self.broadcaster.notify(BasalProfileObserver.self, on: self.processQueue) {
-                    $0.basalProfileDidChange(basalProfile)
-                }
-            }
-        }
-        return tunedProfile != nil
-    }
-
     func roundBolus(amount: Decimal) -> Decimal {
         guard let pump = pumpManager else { return amount }
         let rounded = Decimal(pump.roundToSupportedBolusVolume(units: Double(amount)))
@@ -537,121 +517,6 @@ final class BaseAPSManager: APSManager, Injectable {
         }
     }
 
-    func dailyAutotune() async throws -> Bool {
-        guard settings.useAutotune else {
-            return false
-        }
-
-        let now = Date()
-
-        guard lastAutotuneDate.isBeforeDate(now, granularity: .day) else {
-            return false
-        }
-        lastAutotuneDate = now
-
-        let result = await autotune()
-        return result != nil
-    }
-
-    func autotune() async -> Autotune? {
-        await openAPS.autotune()
-    }
-
-    func enactAnnouncement(_ announcement: Announcement) {
-        guard let action = announcement.action else {
-            warning(.apsManager, "Invalid Announcement action")
-            return
-        }
-
-        guard let pump = pumpManager else {
-            warning(.apsManager, "Pump is not set")
-            return
-        }
-
-        debug(.apsManager, "Start enact announcement: \(action)")
-
-        switch action {
-        case let .bolus(amount):
-            if let error = verifyStatus() {
-                processError(error)
-                return
-            }
-            let roundedAmount = pump.roundToSupportedBolusVolume(units: Double(amount))
-            pump.enactBolus(units: roundedAmount, activationType: .manualRecommendationAccepted) { error in
-                if let error = error {
-                    // warning(.apsManager, "Announcement Bolus failed with error: \(error.localizedDescription)")
-                    switch error {
-                    case .uncertainDelivery:
-                        // Do not generate notification on uncertain delivery error
-                        break
-                    default:
-                        // Do not generate notifications for automatic boluses that fail.
-                        warning(.apsManager, "Announcement Bolus failed with error: \(error.localizedDescription)")
-                    }
-
-                } else {
-                    debug(.apsManager, "Announcement Bolus succeeded")
-                    self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
-                    self.bolusProgress.send(0)
-                }
-            }
-        case let .pump(pumpAction):
-            switch pumpAction {
-            case .suspend:
-                if let error = verifyStatus() {
-                    processError(error)
-                    return
-                }
-                pump.suspendDelivery { error in
-                    if let error = error {
-                        debug(.apsManager, "Pump not suspended by Announcement: \(error.localizedDescription)")
-                    } else {
-                        debug(.apsManager, "Pump suspended by Announcement")
-                        self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
-                    }
-                }
-            case .resume:
-                guard pump.status.pumpStatus.suspended else {
-                    return
-                }
-                pump.resumeDelivery { error in
-                    if let error = error {
-                        warning(.apsManager, "Pump not resumed by Announcement: \(error.localizedDescription)")
-                    } else {
-                        debug(.apsManager, "Pump resumed by Announcement")
-                        self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
-                    }
-                }
-            }
-        case let .looping(closedLoop):
-            settings.closedLoop = closedLoop
-            debug(.apsManager, "Closed loop \(closedLoop) by Announcement")
-            announcementsStorage.storeAnnouncements([announcement], enacted: true)
-        case let .tempbasal(rate, duration):
-            if let error = verifyStatus() {
-                processError(error)
-                return
-            }
-            // unable to do temp basal during manual temp basal 😁
-            if isManualTempBasal {
-                processError(APSError.manualBasalTemp(message: "Loop not possible during the manual basal temp"))
-                return
-            }
-            guard !settings.closedLoop else {
-                return
-            }
-            let roundedRate = pump.roundToSupportedBasalRate(unitsPerHour: Double(rate))
-            pump.enactTempBasal(unitsPerHour: roundedRate, for: TimeInterval(duration) * 60) { error in
-                if let error = error {
-                    warning(.apsManager, "Announcement TempBasal failed with error: \(error.localizedDescription)")
-                } else {
-                    debug(.apsManager, "Announcement TempBasal succeeded")
-                    self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
-                }
-            }
-        }
-    }
-
     private func fetchCurrentTempBasal(date: Date) async -> TempBasal {
         let results = await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: PumpEventStored.self,

+ 0 - 52
FreeAPS/Sources/APS/FetchAnnouncementsManager.swift

@@ -1,52 +0,0 @@
-import Combine
-import Foundation
-import SwiftDate
-import Swinject
-
-protocol FetchAnnouncementsManager {}
-
-final class BaseFetchAnnouncementsManager: FetchAnnouncementsManager, Injectable {
-    private let processQueue = DispatchQueue(label: "BaseFetchAnnouncementsManager.processQueue")
-    @Injected() var announcementsStorage: AnnouncementsStorage!
-    @Injected() var nightscoutManager: NightscoutManager!
-    @Injected() var apsManager: APSManager!
-    @Injected() var settingsManager: SettingsManager!
-
-    private var lifetime = Lifetime()
-    private let timer = DispatchTimer(timeInterval: 5.minutes.timeInterval)
-
-    init(resolver: Resolver) {
-        injectServices(resolver)
-        subscribe()
-    }
-
-    private func subscribe() {
-        timer.publisher
-            .receive(on: processQueue)
-            .flatMap { _ -> AnyPublisher<[Announcement], Never> in
-                guard self.settingsManager.settings.allowAnnouncements else {
-                    return Just([]).eraseToAnyPublisher()
-                }
-                debug(.nightscout, "FetchAnnouncementsManager heartbeat")
-                debug(.nightscout, "Start fetching announcements")
-                return self.nightscoutManager.fetchAnnouncements()
-            }
-            .sink { announcements in
-                guard let last = announcements.filter({ $0.createdAt > self.announcementsStorage.syncDate() })
-                    .sorted(by: { $0.createdAt < $1.createdAt })
-                    .last
-                else { return }
-
-                self.announcementsStorage.storeAnnouncements([last], enacted: false)
-                if self.settingsManager.settings.allowAnnouncements, let recent = self.announcementsStorage.recent(),
-                   recent.action != nil
-                {
-                    debug(.nightscout, "New announcements found")
-                    self.apsManager.enactAnnouncement(recent)
-                }
-            }
-            .store(in: &lifetime)
-        timer.fire()
-        timer.resume()
-    }
-}

+ 0 - 2
FreeAPS/Sources/APS/OpenAPS/Constants.swift

@@ -92,8 +92,6 @@ extension OpenAPS {
 
     enum FreeAPS {
         static let settings = "freeaps/freeaps_settings.json"
-        static let announcements = "freeaps/announcements.json"
-        static let announcementsEnacted = "freeaps/announcements_enacted.json"
         static let tempTargetsPresets = "freeaps/temptargets_presets.json"
         static let calibrations = "freeaps/calibrations.json"
     }

+ 5 - 126
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -462,66 +462,8 @@ final class OpenAPS {
         }
     }
 
-    func autotune(categorizeUamAsBasal: Bool = false, tuneInsulinCurve: Bool = false) async -> Autotune? {
-        debug(.openAPS, "Start autotune")
-
-        // Perform asynchronous calls in parallel
-        async let pumpHistoryObjectIDs = fetchPumpHistoryObjectIDs() ?? []
-        async let carbs = fetchAndProcessCarbs()
-        async let glucose = fetchAndProcessGlucose()
-        async let getProfile = loadFileFromStorageAsync(name: Settings.profile)
-        async let getPumpProfile = loadFileFromStorageAsync(name: Settings.pumpProfile)
-        async let getPreviousAutotune = storage.retrieveAsync(Settings.autotune, as: RawJSON.self)
-
-        // Await the results of asynchronous tasks
-        let (pumpHistoryJSON, carbsAsJSON, glucoseAsJSON, profile, pumpProfile, previousAutotune) = await (
-            parsePumpHistory(await pumpHistoryObjectIDs),
-            carbs,
-            glucose,
-            getProfile,
-            getPumpProfile,
-            getPreviousAutotune
-        )
-
-        // Error need to be handled here because the function is not declared as throws
-        do {
-            // Autotune Prepare
-            let autotunePreppedGlucose = try await autotunePrepare(
-                pumphistory: pumpHistoryJSON,
-                profile: profile,
-                glucose: glucoseAsJSON,
-                pumpprofile: pumpProfile,
-                carbs: carbsAsJSON,
-                categorizeUamAsBasal: categorizeUamAsBasal,
-                tuneInsulinCurve: tuneInsulinCurve
-            )
-
-            debug(.openAPS, "AUTOTUNE PREP: \(autotunePreppedGlucose)")
-
-            // Autotune Run
-            let autotuneResult = try await autotuneRun(
-                autotunePreparedData: autotunePreppedGlucose,
-                previousAutotuneResult: previousAutotune ?? profile,
-                pumpProfile: pumpProfile
-            )
-
-            debug(.openAPS, "AUTOTUNE RESULT: \(autotuneResult)")
-
-            if let autotune = Autotune(from: autotuneResult) {
-                storage.save(autotuneResult, as: Settings.autotune)
-
-                return autotune
-            } else {
-                return nil
-            }
-        } catch {
-            debug(.openAPS, "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to prepare/run Autotune")
-            return nil
-        }
-    }
-
-    func makeProfiles(useAutotune _: Bool) async -> Autotune? {
-        debug(.openAPS, "Start makeProfiles")
+    func createProfiles() async {
+        debug(.openAPS, "Start creating pump profile and user profile")
 
         // Load required settings and profiles asynchronously
         async let getPumpSettings = loadFileFromStorageAsync(name: Settings.settings)
@@ -531,10 +473,9 @@ final class OpenAPS {
         async let getCR = loadFileFromStorageAsync(name: Settings.carbRatios)
         async let getTempTargets = loadFileFromStorageAsync(name: Settings.tempTargets)
         async let getModel = loadFileFromStorageAsync(name: Settings.model)
-        async let getAutotune = loadFileFromStorageAsync(name: Settings.autotune)
         async let getFreeAPS = loadFileFromStorageAsync(name: FreeAPS.settings)
 
-        let (pumpSettings, bgTargets, basalProfile, isf, cr, tempTargets, model, autotune, freeaps) = await (
+        let (pumpSettings, bgTargets, basalProfile, isf, cr, tempTargets, model, freeaps) = await (
             getPumpSettings,
             getBGTargets,
             getBasalProfile,
@@ -542,7 +483,6 @@ final class OpenAPS {
             getCR,
             getTempTargets,
             getModel,
-            getAutotune,
             getFreeAPS
         )
 
@@ -588,27 +528,18 @@ final class OpenAPS {
                 carbRatio: cr,
                 tempTargets: tempTargets,
                 model: model,
-                autotune: autotune.isEmpty ? .null : autotune,
+                autotune: RawJSON.null,
                 freeaps: freeaps
             )
 
             // Save the profiles
             await storage.saveAsync(pumpProfile, as: Settings.pumpProfile)
             await storage.saveAsync(profile, as: Settings.profile)
-
-            // Return the Autotune object, if available
-            if let tunedProfile = Autotune(from: profile) {
-                return tunedProfile
-            } else {
-                return nil
-            }
         } catch {
-            // Handle errors and log failure
             debug(
                 .apsManager,
-                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to execute makeProfiles()"
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to create pump profile and normal profile: \(error)"
             )
-            return nil
         }
     }
 
@@ -687,58 +618,6 @@ final class OpenAPS {
         }
     }
 
-    private func autotunePrepare(
-        pumphistory: JSON,
-        profile: JSON,
-        glucose: JSON,
-        pumpprofile: JSON,
-        carbs: JSON,
-        categorizeUamAsBasal: Bool,
-        tuneInsulinCurve: Bool
-    ) async throws -> RawJSON {
-        try await withCheckedThrowingContinuation { continuation in
-            jsWorker.inCommonContext { worker in
-                worker.evaluateBatch(scripts: [
-                    Script(name: Prepare.log),
-                    Script(name: Bundle.autotunePrep),
-                    Script(name: Prepare.autotunePrep)
-                ])
-                let result = worker.call(function: Function.generate, with: [
-                    pumphistory,
-                    profile,
-                    glucose,
-                    pumpprofile,
-                    carbs,
-                    categorizeUamAsBasal,
-                    tuneInsulinCurve
-                ])
-                continuation.resume(returning: result)
-            }
-        }
-    }
-
-    private func autotuneRun(
-        autotunePreparedData: JSON,
-        previousAutotuneResult: JSON,
-        pumpProfile: JSON
-    ) async throws -> RawJSON {
-        try await withCheckedThrowingContinuation { continuation in
-            jsWorker.inCommonContext { worker in
-                worker.evaluateBatch(scripts: [
-                    Script(name: Prepare.log),
-                    Script(name: Bundle.autotuneCore),
-                    Script(name: Prepare.autotuneCore)
-                ])
-                let result = worker.call(function: Function.generate, with: [
-                    autotunePreparedData,
-                    previousAutotuneResult,
-                    pumpProfile
-                ])
-                continuation.resume(returning: result)
-            }
-        }
-    }
-
     private func determineBasal(
         glucose: JSON,
         currentTemp: JSON,

+ 0 - 80
FreeAPS/Sources/APS/Storage/AnnouncementsStorage.swift

@@ -1,80 +0,0 @@
-import Foundation
-import SwiftDate
-import Swinject
-
-protocol AnnouncementsStorage {
-    func storeAnnouncements(_ announcements: [Announcement], enacted: Bool)
-    func syncDate() -> Date
-    func recent() -> Announcement?
-    func validate() -> [Announcement]
-}
-
-final class BaseAnnouncementsStorage: AnnouncementsStorage, Injectable {
-    enum Config {
-        static let recentInterval = 10.minutes.timeInterval
-    }
-
-    private let processQueue = DispatchQueue(label: "BaseAnnouncementsStorage.processQueue")
-    @Injected() private var storage: FileStorage!
-
-    init(resolver: Resolver) {
-        injectServices(resolver)
-    }
-
-    func storeAnnouncements(_ announcements: [Announcement], enacted: Bool) {
-        processQueue.sync {
-            let file = enacted ? OpenAPS.FreeAPS.announcementsEnacted : OpenAPS.FreeAPS.announcements
-            self.storage.transaction { storage in
-                storage.append(announcements, to: file, uniqBy: \.createdAt)
-                let uniqEvents = storage.retrieve(file, as: [Announcement].self)?
-                    .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
-                    .sorted { $0.createdAt > $1.createdAt } ?? []
-                storage.save(Array(uniqEvents), as: file)
-            }
-        }
-    }
-
-    func syncDate() -> Date {
-        guard let events = storage.retrieve(OpenAPS.FreeAPS.announcementsEnacted, as: [Announcement].self),
-              let recentEnacted = events.filter({ $0.enteredBy == Announcement.remote }).first
-        else {
-            return Date().addingTimeInterval(-Config.recentInterval)
-        }
-        return recentEnacted.createdAt.addingTimeInterval(Config.recentInterval)
-    }
-
-    func recent() -> Announcement? {
-        guard let events = storage.retrieve(OpenAPS.FreeAPS.announcements, as: [Announcement].self)
-        else {
-            return nil
-        }
-        guard let recent = events
-            .filter({
-                $0.enteredBy == Announcement.remote && $0.createdAt.addingTimeInterval(Config.recentInterval) > Date()
-            })
-            .first
-        else {
-            return nil
-        }
-        guard let enactedEvents = storage.retrieve(OpenAPS.FreeAPS.announcementsEnacted, as: [Announcement].self)
-        else {
-            return recent
-        }
-
-        guard enactedEvents.first(where: { $0.createdAt == recent.createdAt }) == nil
-        else {
-            return nil
-        }
-        return recent
-    }
-
-    func validate() -> [Announcement] {
-        guard let enactedEvents = storage.retrieve(OpenAPS.FreeAPS.announcementsEnacted, as: [Announcement].self)?.reversed()
-        else {
-            return []
-        }
-        let validate = enactedEvents
-            .filter({ $0.enteredBy == Announcement.remote })
-        return validate
-    }
-}

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

@@ -218,7 +218,7 @@ final class BaseOverrideStorage: @preconcurrency OverrideStorage, Injectable {
             guard let fetchedOverrides = results as? [OverrideStored] else { return [] }
 
             return fetchedOverrides.map { override in
-                let duration = override.indefinite ? 1440 : override.duration ?? 0 // 1440 min = 1 day
+                let duration = override.indefinite ? 43200 : override.duration ?? 0 // 43200 min = 30 days
                 return NightscoutExercise(
                     duration: Int(truncating: duration),
                     eventType: OverrideStored.EventType.nsExercise,

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

@@ -46,7 +46,6 @@ import Swinject
         _ = resolver.resolve(APSManager.self)!
         _ = resolver.resolve(FetchGlucoseManager.self)!
         _ = resolver.resolve(FetchTreatmentsManager.self)!
-        _ = resolver.resolve(FetchAnnouncementsManager.self)!
         _ = resolver.resolve(CalendarManager.self)!
         _ = resolver.resolve(UserNotificationsManager.self)!
         _ = resolver.resolve(WatchManager.self)!

+ 0 - 1
FreeAPS/Sources/Assemblies/APSAssembly.swift

@@ -7,7 +7,6 @@ final class APSAssembly: Assembly {
         container.register(APSManager.self) { r in BaseAPSManager(resolver: r) }
         container.register(FetchGlucoseManager.self) { r in BaseFetchGlucoseManager(resolver: r) }
         container.register(FetchTreatmentsManager.self) { r in BaseFetchTreatmentsManager(resolver: r) }
-        container.register(FetchAnnouncementsManager.self) { r in BaseFetchAnnouncementsManager(resolver: r) }
         container.register(BluetoothStateManager.self) { r in BaseBluetoothStateManager(resolver: r) }
         container.register(PluginManager.self) { r in BasePluginManager(resolver: r) }
         container.register(CalibrationService.self) { r in BaseCalibrationService(resolver: r) }

+ 0 - 1
FreeAPS/Sources/Assemblies/StorageAssembly.swift

@@ -14,7 +14,6 @@ final class StorageAssembly: Assembly {
         container.register(TempTargetsStorage.self) { r in BaseTempTargetsStorage(resolver: r) }
         container.register(CarbsStorage.self) { r in BaseCarbsStorage(resolver: r) }
         container.register(ContactImageStorage.self) { r in BaseContactImageStorage(resolver: r) }
-        container.register(AnnouncementsStorage.self) { r in BaseAnnouncementsStorage(resolver: r) }
         container.register(SettingsManager.self) { r in BaseSettingsManager(resolver: r) }
         container.register(Keychain.self) { _ in BaseKeychain() }
         container.register(AlertHistoryStorage.self) { r in BaseAlertHistoryStorage(resolver: r) }

+ 2 - 23
FreeAPS/Sources/Helpers/MainChartHelper.swift

@@ -109,7 +109,7 @@ extension MainChartView {
         RuleMark(
             x: .value(
                 "",
-                startMarker,
+                state.startMarker,
                 unit: .second
             )
         ).foregroundStyle(Color.clear)
@@ -119,7 +119,7 @@ extension MainChartView {
         RuleMark(
             x: .value(
                 "",
-                endMarker,
+                state.endMarker,
                 unit: .second
             )
         ).foregroundStyle(Color.clear)
@@ -181,29 +181,8 @@ extension MainChartView {
             }
         }
     }
-}
-
-// 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
-    }
 }

+ 0 - 57
FreeAPS/Sources/Helpers/SettingsRowView.swift

@@ -1,57 +0,0 @@
-import SwiftUI
-
-struct SettingsRowView: View {
-    let imageName: String
-    let title: String
-    let tint: Color
-    let spacing: CGFloat?
-    let font: CGFloat?
-
-    init(imageName: String, title: String, tint: Color, spacing: CGFloat? = 12, font: CGFloat? = 35) {
-        self.imageName = imageName
-        self.title = title
-        self.tint = tint
-        self.spacing = spacing
-        self.font = font
-    }
-
-    var body: some View {
-        HStack(spacing: spacing ?? 12, content: {
-            Image(systemName: imageName)
-                .imageScale(.small)
-                .font(.system(size: font ?? 35))
-                .foregroundColor(tint)
-
-            Text(title)
-                .font(.subheadline)
-                .foregroundStyle(.primary)
-        })
-    }
-}
-
-struct SettingsRowViewCustomImage: View {
-    let imageName: String
-    let title: String
-    let frame: CGFloat?
-    let spacing: CGFloat?
-
-    init(imageName: String, title: String, frame: CGFloat? = 35, spacing: CGFloat? = 12) {
-        self.imageName = imageName
-        self.title = title
-        self.frame = frame
-        self.spacing = spacing
-    }
-
-    var body: some View {
-        HStack(spacing: spacing ?? 12, content: {
-            Image(imageName)
-                .resizable()
-                .aspectRatio(contentMode: .fit)
-                .frame(width: frame ?? 35, height: frame ?? 35)
-
-            Text(title)
-                .font(.subheadline)
-                .foregroundStyle(.primary)
-        })
-    }
-}

+ 0 - 6
FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings

@@ -1235,12 +1235,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Enacted";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Announcements";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Enacted announcements";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

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

@@ -1086,12 +1086,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Enacted";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Announcements";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Enacted announcements";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings

@@ -1196,12 +1196,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Udført";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Announcements";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Enacted announcements";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings

@@ -1241,12 +1241,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "letzte Berechnung";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Notifikationen";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Erlassene Ankündigungen";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings

@@ -1238,12 +1238,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Enacted";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Announcements";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Enacted announcements";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings

@@ -1234,12 +1234,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Enacted";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Announcements";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Enacted announcements";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings

@@ -1241,12 +1241,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Enacted";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Announcements";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Enacted announcements";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings

@@ -1235,12 +1235,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Activé";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Annonces";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Annonces émises";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings

@@ -1235,12 +1235,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Enacted";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Announcements";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Enacted announcements";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings

@@ -1193,12 +1193,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Enacted";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Announcements";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Enacted announcements";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings

@@ -1235,12 +1235,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Debug Attivato";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Annunci";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Annunci attivati";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings

@@ -1235,12 +1235,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Utført";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Kunngjøringer";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Utførte kunngjøringer";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings

@@ -1235,12 +1235,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Vastgesteld";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Meldingen";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Ingevoerde meldingen";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings

@@ -1237,12 +1237,6 @@ Połączono z Nightscout!";
 /* Debug option view Enacted */
 "Enacted" = "Enacted";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Announcements";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Enacted announcements";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings

@@ -1235,12 +1235,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Enacted";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Announcements";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Enacted announcements";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings

@@ -1235,12 +1235,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Enacted";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Announcements";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Enacted announcements";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings

@@ -1238,12 +1238,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Принято";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Оповещения";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Принятые оповещения";
-
 /* Debug option view Autotune */
 "Autotune" = "Автотюн";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings

@@ -1193,12 +1193,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Prijaté";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Oznámenia";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Prijaté oznámenia";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings

@@ -1235,12 +1235,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Utfört";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Meddelanden (via NS)";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Överförda meddelanden";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings

@@ -1239,12 +1239,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Enacted";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Duyurular";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Enacted announcements";
-
 /* Debug option view Autotune */
 "Autotune" = "Otoayar";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings

@@ -1235,12 +1235,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Введено в дію";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Оголошення";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Введені в дію оголошення";
-
 /* Debug option view Autotune */
 "Autotune" = "Автотюн";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings

@@ -1193,12 +1193,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Đã kích hoạt";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Các thông báo";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Thông báo đã kích hoạt";
-
 /* Debug option view Autotune */
 "Autotune" = "Autotune";
 

+ 0 - 6
FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings

@@ -1235,12 +1235,6 @@ Enact a temp Basal or a temp target */
 /* Debug option view Enacted */
 "Enacted" = "Enacted";
 
-/* Debug option view Announcements (from NS) */
-"Announcements" = "Announcements";
-
-/* Debug option view Enacted announcements announcements (from NS) */
-"Enacted announcements" = "Enacted announcements";
-
 /* Debug option view Autotune */
 "Autotune" = "自动调谐";
 

+ 0 - 57
FreeAPS/Sources/Models/Announcement.swift

@@ -1,57 +0,0 @@
-import Foundation
-
-struct Announcement: JSON, Equatable, Hashable {
-    let createdAt: Date
-    let enteredBy: String
-    let notes: String
-
-    static let remote = "remote"
-
-    var action: AnnouncementAction? {
-        let components = notes.replacingOccurrences(of: " ", with: "").split(separator: ":")
-        guard components.count == 2 else {
-            return nil
-        }
-        let command = String(components[0]).lowercased()
-        let arguments = String(components[1]).lowercased()
-        switch command {
-        case "bolus":
-            guard let amount = Decimal(from: arguments) else { return nil }
-            return .bolus(amount)
-        case "pump":
-            guard let action = PumpAction(rawValue: arguments) else { return nil }
-            return .pump(action)
-        case "looping":
-            guard let looping = Bool(from: arguments) else { return nil }
-            return .looping(looping)
-        case "tempbasal":
-            let basalComponents = arguments.split(separator: ",")
-            guard basalComponents.count == 2 else { return nil }
-            let rateArg = String(basalComponents[0])
-            let durationArg = String(basalComponents[1])
-            guard let rate = Decimal(from: rateArg), let duration = Decimal(from: durationArg) else { return nil }
-            return .tempbasal(rate: rate, duration: duration)
-        default: return nil
-        }
-    }
-}
-
-extension Announcement {
-    private enum CodingKeys: String, CodingKey {
-        case createdAt = "created_at"
-        case enteredBy
-        case notes
-    }
-}
-
-enum AnnouncementAction {
-    case bolus(Decimal)
-    case pump(PumpAction)
-    case looping(Bool)
-    case tempbasal(rate: Decimal, duration: Decimal)
-}
-
-enum PumpAction: String {
-    case suspend
-    case resume
-}

+ 0 - 17
FreeAPS/Sources/Models/Autotune.swift

@@ -1,17 +0,0 @@
-import Foundation
-
-struct Autotune: JSON, Equatable {
-    var createdAt: Date?
-    let basalProfile: [BasalProfileEntry]
-    let sensitivity: Decimal
-    let carbRatio: Decimal
-}
-
-extension Autotune {
-    private enum CodingKeys: String, CodingKey {
-        case createdAt = "created_at"
-        case basalProfile = "basalprofile"
-        case sensitivity = "sens"
-        case carbRatio = "carb_ratio"
-    }
-}

+ 4 - 0
FreeAPS/Sources/Models/BGTargets.swift

@@ -6,6 +6,10 @@ struct BGTargets: JSON {
     var targets: [BGTargetEntry]
 }
 
+protocol BGTargetsObserver {
+    func bgTargetsDidChange(_ bgTargets: BGTargets)
+}
+
 extension BGTargets {
     private enum CodingKeys: String, CodingKey {
         case units

+ 3 - 10
FreeAPS/Sources/Models/DecimalPickerSettings.swift

@@ -40,7 +40,7 @@ struct DecimalPickerSettings {
         value: 0.5,
         step: 0.05,
         min: 0.1,
-        max: 2,
+        max: 1.2,
         type: PickerSetting.PickerSettingType.factor
     )
     var high = PickerSetting(value: 180, step: 1, min: 100, max: 500, type: PickerSetting.PickerSettingType.glucose)
@@ -78,13 +78,6 @@ struct DecimalPickerSettings {
     )
     var maxCOB = PickerSetting(value: 120, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
     var min5mCarbimpact = PickerSetting(value: 8, step: 1, min: 1, max: 20, type: PickerSetting.PickerSettingType.glucose)
-    var autotuneISFAdjustmentFraction = PickerSetting(
-        value: 1.0,
-        step: 0.05,
-        min: 0,
-        max: 1,
-        type: PickerSetting.PickerSettingType.factor
-    )
     var remainingCarbsFraction = PickerSetting(
         value: 1.0,
         step: 0.05,
@@ -137,8 +130,8 @@ struct DecimalPickerSettings {
     )
     var threshold_setting = PickerSetting(value: 60, step: 1, min: 60, max: 120, type: PickerSetting.PickerSettingType.glucose)
     var updateInterval = PickerSetting(value: 20, step: 5, min: 1, max: 60, type: PickerSetting.PickerSettingType.minute)
-    var delay = PickerSetting(value: 60, step: 15, min: 30, max: 120, type: PickerSetting.PickerSettingType.minute)
-    var minuteInterval = PickerSetting(value: 20, step: 5, min: 5, max: 60, type: PickerSetting.PickerSettingType.minute)
+    var delay = PickerSetting(value: 60, step: 10, min: 60, max: 120, type: PickerSetting.PickerSettingType.minute)
+    var minuteInterval = PickerSetting(value: 30, step: 5, min: 10, max: 60, type: PickerSetting.PickerSettingType.minute)
     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 dia = PickerSetting(value: 10, step: 0.5, min: 5, max: 10, type: PickerSetting.PickerSettingType.hour)

+ 0 - 15
FreeAPS/Sources/Models/FreeAPSSettings.swift

@@ -18,8 +18,6 @@ enum BolusShortcutLimit: String, JSON, CaseIterable, Identifiable {
 struct FreeAPSSettings: JSON, Equatable {
     var units: GlucoseUnits = .mgdL
     var closedLoop: Bool = false
-    var allowAnnouncements: Bool = false
-    var useAutotune: Bool = false
     var isUploadEnabled: Bool = false
     var isDownloadEnabled: Bool = false
     var useLocalGlucoseSource: Bool = false
@@ -68,7 +66,6 @@ struct FreeAPSSettings: JSON, Equatable {
     var maxProtein: Decimal = 250
     var displayFatAndProteinOnWatch: Bool = false
     var confirmBolusFaster: Bool = false
-    var onlyAutotuneBasals: Bool = false
     var overrideFactor: Decimal = 0.8
     var fattyMeals: Bool = false
     var fattyMealFactor: Decimal = 0.7
@@ -94,14 +91,6 @@ extension FreeAPSSettings: Decodable {
             settings.closedLoop = closedLoop
         }
 
-        if let allowAnnouncements = try? container.decode(Bool.self, forKey: .allowAnnouncements) {
-            settings.allowAnnouncements = allowAnnouncements
-        }
-
-        if let useAutotune = try? container.decode(Bool.self, forKey: .useAutotune) {
-            settings.useAutotune = useAutotune
-        }
-
         if let isUploadEnabled = try? container.decode(Bool.self, forKey: .isUploadEnabled) {
             settings.isUploadEnabled = isUploadEnabled
         }
@@ -319,10 +308,6 @@ extension FreeAPSSettings: Decodable {
             settings.confirmBolusFaster = confirmBolusFaster
         }
 
-        if let onlyAutotuneBasals = try? container.decode(Bool.self, forKey: .onlyAutotuneBasals) {
-            settings.onlyAutotuneBasals = onlyAutotuneBasals
-        }
-
         if let displayPresets = try? container.decode(Bool.self, forKey: .displayPresets) {
             settings.displayPresets = displayPresets
         }

+ 0 - 6
FreeAPS/Sources/Models/Preferences.swift

@@ -20,7 +20,6 @@ struct Preferences: JSON, Equatable {
     var skipNeutralTemps: Bool = false
     var unsuspendIfNoTemp: Bool = false
     var min5mCarbimpact: Decimal = 8
-    var autotuneISFAdjustmentFraction: Decimal = 1.0
     var remainingCarbsFraction: Decimal = 1.0
     var remainingCarbsCap: Decimal = 90
     var enableUAM: Bool = false
@@ -77,7 +76,6 @@ extension Preferences {
         case skipNeutralTemps = "skip_neutral_temps"
         case unsuspendIfNoTemp = "unsuspend_if_no_temp"
         case min5mCarbimpact = "min_5m_carbimpact"
-        case autotuneISFAdjustmentFraction = "autotune_isf_adjustmentFraction"
         case remainingCarbsFraction
         case remainingCarbsCap
         case enableUAM
@@ -202,10 +200,6 @@ extension Preferences: Decodable {
             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
         }

+ 1 - 1
FreeAPS/Sources/Modules/Adjustments/AdjustmentsProvider.swift

@@ -1,6 +1,6 @@
 extension Adjustments {
     final class Provider: BaseProvider, AdjustmentsProvider {
-        func getBGTarget() async -> BGTargets {
+        func getBGTargets() 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: [])

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

@@ -12,7 +12,7 @@ extension Adjustments.StateModel {
             overrideToEnact?.enabled = true
             overrideToEnact?.date = Date()
             overrideToEnact?.isUploadedToNS = false
-            isEnabled = true
+            isOverrideEnabled = true
 
             await disableAllActiveOverrides(except: id, createOverrideRunEntry: currentActiveOverride != nil)
             await resetStateVariables()
@@ -201,8 +201,8 @@ extension Adjustments.StateModel {
             let result = try IDs.compactMap { id in
                 try viewContext.existingObject(with: id) as? OverrideStored
             }
-            isEnabled = result.first?.enabled ?? false
-            if !isEnabled {
+            isOverrideEnabled = result.first?.enabled ?? false
+            if !isOverrideEnabled {
                 await resetStateVariables()
             }
         } catch {

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

@@ -25,7 +25,7 @@ extension Adjustments.StateModel {
                 try viewContext.existingObject(with: id) as? TempTargetStored
             }
             isTempTargetEnabled = result.first?.enabled ?? false
-            if !isEnabled {
+            if !isOverrideEnabled {
                 await resetTempTargetState()
             }
         } catch {

+ 2 - 2
FreeAPS/Sources/Modules/Adjustments/AdjustmentsStateModel.swift

@@ -16,7 +16,7 @@ extension Adjustments {
         // MARK: - Override and Temp Target Properties
 
         var overridePercentage: Double = 100
-        var isEnabled = false
+        var isOverrideEnabled = false
         var indefinite = true
         var overrideDuration: Decimal = 0
         var target: Decimal = 0
@@ -105,7 +105,7 @@ extension Adjustments {
             dateFormatter.dateFormat = "HH:mm:ss"
             dateFormatter.timeZone = TimeZone.current
 
-            let bgTargets = await provider.getBGTarget()
+            let bgTargets = await provider.getBGTargets()
             let entries: [(start: String, value: Decimal)] = bgTargets.targets.map { ($0.start, $0.low) }
 
             for (index, entry) in entries.enumerated() {

+ 42 - 473
FreeAPS/Sources/Modules/Adjustments/View/AdjustmentsRootView.swift

@@ -6,25 +6,34 @@ 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
+        @State var isEditing = false
+        @State var showOverrideCreationSheet = false
+        @State var showTempTargetCreationSheet = false
+        @State var showingDetail = false
+        @State var showOverrideCheckmark: Bool = false
+        @State var showTempTargetCheckmark: Bool = false
+        @State var selectedOverridePresetID: String?
+        @State var selectedTempTargetPresetID: String?
+        @State var selectedOverride: OverrideStored?
+        @State var selectedTempTarget: TempTargetStored?
+        @State var isConfirmDeletePresented = false
+        @State var isPromptPresented = false
+        @State var isRemoveAlertPresented = false
+        @State var removeAlert: Alert?
+        @State var isEditingTT = false
+
+        private var shouldDisplayStickyOverrideStopButton: Bool {
+            state.isOverrideEnabled && state.activeOverrideName.isNotEmpty
+        }
+
+        private var shouldDisplayStickyTempTargetStopButton: Bool {
+            state.isTempTargetEnabled && state.activeTempTargetName.isNotEmpty
+        }
 
         @Environment(\.colorScheme) var colorScheme
         @Environment(AppState.self) var appState
 
-        private func formattedGlucose(glucose: Decimal) -> String {
+        func formattedGlucose(glucose: Decimal) -> String {
             let formattedValue: String
             if state.units == .mgdL {
                 formattedValue = Formatter.glucoseFormatter(for: state.units)
@@ -55,7 +64,18 @@ extension Adjustments {
                     .background(appState.trioBackgroundColor(for: colorScheme))
                 }
                 .listSectionSpacing(10)
-                .safeAreaInset(edge: .bottom, spacing: 30) { stickyStopButton }
+                .safeAreaInset(
+                    edge: .bottom,
+                    spacing: shouldDisplayStickyOverrideStopButton || shouldDisplayStickyTempTargetStopButton ? 30 : 0
+                ) {
+                    if shouldDisplayStickyOverrideStopButton, state.selectedTab == .overrides {
+                        stickyStopOverrideButton
+                    } else if shouldDisplayStickyTempTargetStopButton, state.selectedTab == .tempTargets {
+                        stickyStopTempTargetButton
+                    } else {
+                        EmptyView()
+                    }
+                }
                 .scrollContentBackground(.hidden)
                 .background(appState.trioBackgroundColor(for: colorScheme))
                 .onAppear(perform: configureView)
@@ -126,32 +146,7 @@ extension Adjustments {
             }).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 {
+        var defaultText: some View {
             switch state.selectedTab {
             case .overrides:
                 Section {} header: {
@@ -170,193 +165,7 @@ extension Adjustments {
             }
         }
 
-        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 {
+        var currentActiveAdjustment: some View {
             switch state.selectedTab {
             case .overrides:
                 Section {
@@ -411,56 +220,7 @@ extension Adjustments {
             }
         }
 
-        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 {
+        var cancelAdjustmentButton: some View {
             switch state.selectedTab {
             case .overrides:
                 Button(action: {
@@ -474,8 +234,8 @@ extension Adjustments {
 
                 })
                     .frame(maxWidth: .infinity, alignment: .center)
-                    .disabled(!state.isEnabled)
-                    .listRowBackground(!state.isEnabled ? Color(.systemGray4) : Color(.systemRed))
+                    .disabled(!state.isOverrideEnabled)
+                    .listRowBackground(!state.isOverrideEnabled ? Color(.systemGray4) : Color(.systemRed))
                     .tint(.white)
             case .tempTargets:
                 Button(action: {
@@ -498,75 +258,7 @@ extension Adjustments {
             }
         }
 
-        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 {
+        func formattedTimeRemaining(_ timeInterval: TimeInterval) -> String {
             let totalSeconds = Int(timeInterval)
             let hours = totalSeconds / 3600
             let minutes = (totalSeconds % 3600) / 60
@@ -580,128 +272,5 @@ extension Adjustments {
                 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)
-                    }
-                }
-            }
-        }
     }
 }

+ 20 - 30
FreeAPS/Sources/Modules/Adjustments/View/Overrides/AddOverrideForm.swift

@@ -55,23 +55,7 @@ struct AddOverrideForm: View {
             }
             .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
-                )
+                OverrideHelpView(state: state, helpSheetDetent: $state.helpSheetDetent)
             }
         }
     }
@@ -94,9 +78,9 @@ struct AddOverrideForm: View {
                     Spacer()
                     Text("\(state.overridePercentage.formatted(.number)) %")
                         .foregroundColor(!displayPickerPercentage ? .primary : .accentColor)
-                }
-                .onTapGesture {
-                    displayPickerPercentage = toggleScrollWheel(displayPickerPercentage)
+                        .onTapGesture {
+                            displayPickerPercentage = toggleScrollWheel(displayPickerPercentage)
+                        }
                 }
 
                 if displayPickerPercentage {
@@ -232,6 +216,9 @@ struct AddOverrideForm: View {
                                 state.convertTo12HourFormat(Int(truncating: state.start as NSNumber))
                         )
                         .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
+                        .onTapGesture {
+                            displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
+                        }
                         Spacer()
                         Divider().frame(width: 1, height: 20)
                         Spacer()
@@ -242,11 +229,11 @@ struct AddOverrideForm: View {
                                 state.convertTo12HourFormat(Int(truncating: state.end as NSNumber))
                         )
                         .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
+                        .onTapGesture {
+                            displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
+                        }
                         Spacer()
                     }
-                    .onTapGesture {
-                        displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
-                    }
 
                     if displayPickerDisableSmbSchedule {
                         HStack {
@@ -301,6 +288,9 @@ struct AddOverrideForm: View {
                             Spacer()
                             Text("\(state.smbMinutes.formatted(.number)) min")
                                 .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
+                                .onTapGesture {
+                                    displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                                }
                             Spacer()
                             Divider().frame(width: 1, height: 20)
                             Spacer()
@@ -308,9 +298,9 @@ struct AddOverrideForm: View {
                             Spacer()
                             Text("\(state.uamMinutes.formatted(.number)) min")
                                 .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
-                        }
-                        .onTapGesture {
-                            displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                                .onTapGesture {
+                                    displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                                }
                         }
 
                         if displayPickerSmbMinutes {
@@ -355,9 +345,9 @@ struct AddOverrideForm: View {
                         Spacer()
                         Text(state.formatHrMin(Int(state.overrideDuration)))
                             .foregroundColor(!displayPickerDuration ? .primary : .accentColor)
-                    }
-                    .onTapGesture {
-                        displayPickerDuration = toggleScrollWheel(displayPickerDuration)
+                            .onTapGesture {
+                                displayPickerDuration = toggleScrollWheel(displayPickerDuration)
+                            }
                     }
 
                     if displayPickerDuration {
@@ -408,7 +398,7 @@ struct AddOverrideForm: View {
                     Button(action: {
                         Task {
                             if state.indefinite { state.overrideDuration = 0 }
-                            state.isEnabled.toggle()
+                            state.isOverrideEnabled.toggle()
                             await state.saveCustomOverride()
                             await state.resetStateVariables()
                             dismiss()

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

@@ -0,0 +1,256 @@
+import CoreData
+import SwiftUI
+
+extension Adjustments.RootView {
+    @ViewBuilder func overrides() -> some View {
+        if state.isOverrideEnabled, state.activeOverrideName.isNotEmpty {
+            currentActiveAdjustment
+        }
+        if state.overridePresets.isNotEmpty {
+            overridePresets
+        } else {
+            defaultText
+        }
+    }
+
+    var overridePresets: some View {
+        Section {
+            ForEach(state.overridePresets) { preset in
+                overridesView(for: preset, showCheckMark: showOverrideCheckmark) {
+                    enactOverridePreset(preset)
+                }
+                .swipeActions(edge: .trailing, allowsFullSwipe: true) {
+                    swipeActionsForOverrides(for: preset)
+                }
+            }
+            .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.")
+            }
+        }
+    }
+
+    func enactOverridePreset(_ preset: OverrideStored) {
+        Task {
+            let objectID = preset.objectID
+            await state.enactOverridePreset(withID: objectID)
+            state.hideModal()
+            selectedOverridePresetID = preset.id
+            showOverrideCheckmark = true
+
+            // Deactivate checkmark after 3 seconds
+            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
+                showOverrideCheckmark = false
+            }
+        }
+    }
+
+    func swipeActionsForOverrides(for preset: OverrideStored) -> some View {
+        Group {
+            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)
+            })
+        }
+    }
+
+    var overrideLabelDivider: some View {
+        Divider()
+            .frame(width: 1, height: 20)
+    }
+
+    var stickyStopOverrideButton: 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())
+
+            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, maxHeight: .infinity)
+                    .padding(10)
+            })
+                .frame(width: UIScreen.main.bounds.width * 0.9, height: 40, alignment: .center)
+                .disabled(!state.isOverrideEnabled)
+                .background(!state.isOverrideEnabled ? Color(.systemGray4) : Color(.systemRed))
+                .tint(.white)
+                .clipShape(RoundedRectangle(cornerRadius: 8))
+
+                .padding(5)
+        }
+    }
+
+    @ViewBuilder func overridesView(
+        for preset: OverrideStored,
+        showCheckMark _: Bool = false,
+        onTap: (() -> Void)? = nil
+    ) -> some View {
+        let isSelected = preset.id == selectedOverridePresetID
+        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 {
+                        onTap?()
+                    }
+                }
+                // show checkmark to indicate if the preset was actually pressed
+                if showOverrideCheckmark && isSelected {
+                    Image(systemName: "checkmark.circle.fill")
+                        .imageScale(.large)
+                        .fontWeight(.bold)
+                        .foregroundStyle(Color.green)
+                } else {
+                    Image(systemName: "line.3.horizontal")
+                        .imageScale(.medium)
+                        .foregroundStyle(.secondary)
+                }
+            }
+        }
+    }
+}

+ 19 - 29
FreeAPS/Sources/Modules/Adjustments/View/Overrides/EditOverrideForm.swift

@@ -117,23 +117,7 @@ struct EditOverrideForm: View {
                 }
             }
             .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
-                )
+                OverrideHelpView(state: state, helpSheetDetent: $state.helpSheetDetent)
             }
         }
     }
@@ -160,9 +144,9 @@ struct EditOverrideForm: View {
                     Spacer()
                     Text("\(percentage.formatted(.number)) %")
                         .foregroundColor(!displayPickerPercentage ? .primary : .accentColor)
-                }
-                .onTapGesture {
-                    displayPickerPercentage = toggleScrollWheel(displayPickerPercentage)
+                        .onTapGesture {
+                            displayPickerPercentage = toggleScrollWheel(displayPickerPercentage)
+                        }
                 }
 
                 if displayPickerPercentage {
@@ -302,6 +286,9 @@ struct EditOverrideForm: View {
                                 state.convertTo12HourFormat(Int(truncating: start! as NSNumber))
                         )
                         .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
+                        .onTapGesture {
+                            displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
+                        }
 
                         Spacer()
 
@@ -316,9 +303,9 @@ struct EditOverrideForm: View {
                                 state.convertTo12HourFormat(Int(truncating: end! as NSNumber))
                         )
                         .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
-                    }
-                    .onTapGesture {
-                        displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
+                        .onTapGesture {
+                            displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
+                        }
                     }
 
                     if displayPickerDisableSmbSchedule {
@@ -383,6 +370,9 @@ struct EditOverrideForm: View {
                             Spacer()
                             Text("\(smbMinutes?.formatted(.number) ?? "\(state.defaultSmbMinutes)") min")
                                 .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
+                                .onTapGesture {
+                                    displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                                }
 
                             Spacer()
 
@@ -394,9 +384,9 @@ struct EditOverrideForm: View {
                             Spacer()
                             Text("\(uamMinutes?.formatted(.number) ?? "\(state.defaultUamMinutes)") min")
                                 .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
-                        }
-                        .onTapGesture {
-                            displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                                .onTapGesture {
+                                    displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                                }
                         }
 
                         if displayPickerSmbMinutes {
@@ -452,9 +442,9 @@ struct EditOverrideForm: View {
                         Spacer()
                         Text(state.formatHrMin(Int(truncating: duration as NSNumber)))
                             .foregroundColor(!displayPickerDuration ? .primary : .accentColor)
-                    }
-                    .onTapGesture {
-                        displayPickerDuration = toggleScrollWheel(displayPickerDuration)
+                            .onTapGesture {
+                                displayPickerDuration = toggleScrollWheel(displayPickerDuration)
+                            }
                     }
 
                     if displayPickerDuration {

+ 53 - 0
FreeAPS/Sources/Modules/Adjustments/View/Overrides/OverrideHelpView.swift

@@ -0,0 +1,53 @@
+import SwiftUI
+
+struct OverrideHelpView: View {
+    var state: Adjustments.StateModel
+    var helpSheetDetent: Binding<PresentationDetent>
+
+    var body: some View {
+        NavigationStack {
+            List {
+                VStack(alignment: .leading, spacing: 10) {
+                    VStack(alignment: .leading, spacing: 0) {
+                        Text(
+                            "This feature can be used to override these therapy settings for a chosen length of time:"
+                        )
+                        .fixedSize(horizontal: false, vertical: true)
+                        Text("• Basal Rate")
+                        Text("• Insulin Sensitivity")
+                        Text("• Carb Ratio")
+                        Text("• Glucose Target")
+                    }
+                    Text(
+                        "There are also options to override your Max SMB Minutes and Max UAM SMB Minutes, as well as to disable SMBs."
+                    )
+                    Text(
+                        "Select \"Start Override\" to immediately start using the Override, or select \"Save as Preset\" to be able to easily start the Override at a later time."
+                    )
+                    Text(
+                        "If an active override preset is edited, the changes will also apply to the currently running override. However, if you edit the currently running override directly, the preset stays unchanged."
+                    )
+                    Text(
+                        "If using Dynamic ISF (without Sigmoid), overriding your ISF will only adjust the limits of the ISF the algorithm is allowed to set."
+                    )
+                    Text(
+                        "If using Dynamic ISF (with Sigmoid), overriding your ISF will adjust the ISF used at your glucose target which extends to the ISF used at other glucose. Overriding your glucose target will change glucose level your ISF will be set to your profile ISF. Both of these can be combined in a single Override."
+                    )
+                }.listRowBackground(Color.gray.opacity(0.1))
+            }
+            .navigationBarTitle("Help", displayMode: .inline)
+
+            Button { state.isHelpSheetPresented.toggle() }
+            label: { Text("Got it!").bold().frame(maxWidth: .infinity, minHeight: 30, alignment: .center) }
+                .buttonStyle(.bordered)
+                .padding(.top)
+        }
+
+        .padding()
+        .scrollContentBackground(.hidden)
+        .presentationDetents(
+            [.fraction(0.9), .large],
+            selection: helpSheetDetent
+        )
+    }
+}

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


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

@@ -0,0 +1,231 @@
+import CoreData
+import SwiftUI
+
+extension Adjustments.RootView {
+    @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 scheduledTempTargets: some View {
+        Section {
+            ForEach(state.scheduledTempTargets) { tempTarget in
+                tempTargetView(for: tempTarget)
+                    .swipeActions(edge: .trailing, allowsFullSwipe: true) {
+                        swipeActionsForTempTargets(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: showTempTargetCheckmark) {
+                    enactTempTargetPreset(preset)
+                }
+                .swipeActions(edge: .trailing, allowsFullSwipe: true) {
+                    swipeActionsForTempTargets(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
+            showTempTargetCheckmark = true
+
+            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
+                showTempTargetCheckmark = false
+            }
+        }
+    }
+
+    private func swipeActionsForTempTargets(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
+    }
+
+    var stickyStopTempTargetButton: 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())
+
+            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, maxHeight: .infinity)
+                    .padding(10)
+            })
+                .frame(width: UIScreen.main.bounds.width * 0.9, height: 40, alignment: .center)
+                .disabled(!state.isTempTargetEnabled)
+                .background(!state.isTempTargetEnabled ? Color(.systemGray4) : Color(.systemRed))
+                .tint(.white)
+                .clipShape(RoundedRectangle(cornerRadius: 8))
+                .padding(5)
+        }
+    }
+
+    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 == selectedTempTargetPresetID
+        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)
+            }
+        }
+    }
+}

+ 16 - 3
FreeAPS/Sources/Modules/Adjustments/View/TempTargets/EditTempTargetForm.swift

@@ -73,10 +73,23 @@ struct EditTempTargetForm: View {
                         Text("Cancel")
                     })
                 }
+                ToolbarItem(placement: .topBarTrailing) {
+                    Button(
+                        action: {
+                            state.isHelpSheetPresented.toggle()
+                        },
+                        label: {
+                            Image(systemName: "questionmark.circle")
+                        }
+                    )
+                }
             }
             .onAppear {
                 if halfBasalTarget != state.settingHalfBasalTarget { tempTargetSensitivityAdjustmentType = .slider }
             }
+            .sheet(isPresented: $state.isHelpSheetPresented) {
+                TempTargetHelpView(state: state, helpSheetDetent: $state.helpSheetDetent)
+            }
         }
     }
 
@@ -221,9 +234,9 @@ struct EditTempTargetForm: View {
                         Spacer()
                         Text(state.formatHrMin(Int(duration)))
                             .foregroundColor(!displayPickerDuration ? (duration > 0 ? .primary : .secondary) : .accentColor)
-                    }
-                    .onTapGesture {
-                        displayPickerDuration = toggleScrollWheel(displayPickerDuration)
+                            .onTapGesture {
+                                displayPickerDuration = toggleScrollWheel(displayPickerDuration)
+                            }
                     }
 
                     if displayPickerDuration {

+ 39 - 0
FreeAPS/Sources/Modules/Adjustments/View/TempTargets/TempTargetHelpView.swift

@@ -0,0 +1,39 @@
+import SwiftUI
+
+struct TempTargetHelpView: View {
+    var state: Adjustments.StateModel
+    var helpSheetDetent: Binding<PresentationDetent>
+
+    var body: some View {
+        NavigationStack {
+            List {
+                VStack(alignment: .leading, spacing: 10) {
+                    Text(
+                        "A Temporary Target replaces the current Target Glucose specified in Therapy settings."
+                    )
+                    Text(
+                        "Depending on your Target Behavior settings (see Settings > the Algorithm > Target Behavior), these temporary glucose targets can also raise Insulin Sensitivity for high targets or lower sensitivity for low targets."
+                    )
+                    Text(
+                        "Furthermore, you could adjust that sensitivity change independently from the Half Basal Exercise Target specified in Algorithm > Target Behavior settings by deliberatly setting a customized Insulin Percentage for a Temp Target."
+                    )
+                    Text(
+                        "A pre-condition to have Temp Targets adjust Sensitivity is that the respective Target Behavior settings High Temp Target Raises Sensitivity or Low Temp Target Lowers Sensitivity are set to enabled."
+                    )
+                }.listRowBackground(Color.gray.opacity(0.1))
+            }
+            .navigationBarTitle("Help", displayMode: .inline)
+
+            Button { state.isHelpSheetPresented.toggle() }
+            label: { Text("Got it!").bold().frame(maxWidth: .infinity, minHeight: 30, alignment: .center) }
+                .buttonStyle(.bordered)
+                .padding(.top)
+        }
+        .padding()
+        .scrollContentBackground(.hidden)
+        .presentationDetents(
+            [.fraction(0.9), .large],
+            selection: helpSheetDetent
+        )
+    }
+}

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

@@ -18,9 +18,9 @@ struct TargetPicker: View {
                 (units == .mgdL ? selection.description : selection.formattedAsMmolL) + " " + units.rawValue
             )
             .foregroundColor(!displayPickerTarget ? .primary : .accentColor)
-        }
-        .onTapGesture {
-            displayPickerTarget = toggleScrollWheel(displayPickerTarget)
+            .onTapGesture {
+                displayPickerTarget = toggleScrollWheel(displayPickerTarget)
+            }
         }
         if displayPickerTarget {
             HStack {

+ 0 - 3
FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift

@@ -18,7 +18,6 @@ extension AlgorithmAdvancedSettings {
         @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
@@ -42,8 +41,6 @@ extension AlgorithmAdvancedSettings {
             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) {

+ 0 - 27
FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift

@@ -228,33 +228,6 @@ extension AlgorithmAdvancedSettings {
                     }
                 )
 
-                // Commenting out Autotune from Settings Menu until full removal is complete
-                // SettingInputSection(
-                // decimalValue: $state.autotuneISFAdjustmentFraction,
-                // booleanValue: $booleanPlaceholder,
-                // shouldDisplayHint: $shouldDisplayHint,
-                // selectedVerboseHint: Binding(
-                // get: { selectedVerboseHint },
-                // set: {
-                // selectedVerboseHint = $0.map { AnyView($0) }
-                // hintLabel = NSLocalizedString(
-                // "Autotune ISF Adjustment Percent",
-                // comment: "Autotune ISF Adjustment Percent"
-                // )
-                // }
-                // ),
-                // units: state.units,
-                // type: .decimal("autotuneISFAdjustmentFraction"),
-                // label: NSLocalizedString("Autotune ISF Adjustment Percent", comment: "Autotune ISF Adjustment Percent"),
-                // miniHint: "Using Autotune is not advised",
-                // verboseHint: Text(
-                // NSLocalizedString(
-                // "The default of 50% for this value keeps autotune ISF closer to pump ISF via a weighted average of fullNewISF and pumpISF. 100% allows full adjustment, 0% is no adjustment from pump ISF.",
-                // comment: "Autotune ISF Adjustment Percent"
-                // )
-                // )
-                // )
-
                 SettingInputSection(
                     decimalValue: $state.min5mCarbimpact,
                     booleanValue: $booleanPlaceholder,

+ 3 - 1
FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsDataFlow.swift

@@ -2,4 +2,6 @@ enum AutosensSettings {
     enum Config {}
 }
 
-protocol AutosensSettingsProvider: Provider {}
+protocol AutosensSettingsProvider: Provider {
+    var autosense: Autosens { get }
+}

+ 7 - 1
FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsProvider.swift

@@ -1,3 +1,9 @@
 extension AutosensSettings {
-    final class Provider: BaseProvider, AutosensSettingsProvider {}
+    final class Provider: BaseProvider, AutosensSettingsProvider {
+        var autosense: Autosens {
+            storage.retrieve(OpenAPS.Settings.autosense, as: Autosens.self)
+                ?? Autosens(from: OpenAPS.defaults(for: OpenAPS.Settings.autosense))
+                ?? Autosens(ratio: 1, newisf: nil, timestamp: nil)
+        }
+    }
 }

+ 42 - 0
FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift

@@ -1,3 +1,4 @@
+import CoreData
 import Observation
 import SwiftUI
 
@@ -5,19 +6,60 @@ extension AutosensSettings {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var settings: SettingsManager!
         @Injected() var storage: FileStorage!
+        @Injected() var determinationStorage: DeterminationStorage!
 
         var units: GlucoseUnits = .mgdL
 
+        private(set) var autosensISF: Decimal?
+        private(set) var autosensRatio: Decimal = 0
+        var determinationsFromPersistence: [OrefDetermination] = []
+
+        let viewContext = CoreDataStack.shared.persistentContainer.viewContext
+
         @Published var autosensMax: Decimal = 1.2
         @Published var autosensMin: Decimal = 0.7
         @Published var rewindResetsAutosens: Bool = true
 
+        var preferences: Preferences {
+            settingsManager.preferences
+        }
+
         override func subscribe() {
             units = settingsManager.settings.units
 
             subscribePreferencesSetting(\.autosensMax, on: $autosensMax) { autosensMax = $0 }
             subscribePreferencesSetting(\.autosensMin, on: $autosensMin) { autosensMin = $0 }
             subscribePreferencesSetting(\.rewindResetsAutosens, on: $rewindResetsAutosens) { rewindResetsAutosens = $0 }
+
+            if let newISF = provider.autosense.newisf {
+                autosensISF = newISF
+            }
+
+            autosensRatio = provider.autosense.ratio
+            setupDeterminationsArray()
+        }
+
+        private func setupDeterminationsArray() {
+            Task {
+                let ids = await determinationStorage.fetchLastDeterminationObjectID(
+                    predicate: NSPredicate.enactedDetermination
+                )
+                await updateDeterminationsArray(with: ids)
+            }
+        }
+
+        @MainActor private func updateDeterminationsArray(with IDs: [NSManagedObjectID]) {
+            do {
+                let objects = try IDs.compactMap { id in
+                    try viewContext.existingObject(with: id) as? OrefDetermination
+                }
+                determinationsFromPersistence = objects
+
+            } catch {
+                debugPrint(
+                    "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the glucose array: \(error.localizedDescription)"
+                )
+            }
         }
     }
 }

+ 108 - 0
FreeAPS/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift

@@ -16,8 +16,116 @@ extension AutosensSettings {
         @EnvironmentObject var appIcons: Icons
         @Environment(AppState.self) var appState
 
+        private var rateFormatter: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.numberStyle = .decimal
+            formatter.maximumFractionDigits = 2
+            return formatter
+        }
+
+        var autosensVerboseHint: some View {
+            VStack(alignment: .leading, spacing: 15) {
+                Text(
+                    "Autosens automatically adjusts insulin delivery based on how sensitive or resistant you are to insulin at the time of the current loop cycle by analyzing past data to keep blood sugar levels stable."
+                )
+
+                VStack(alignment: .leading, spacing: 5) {
+                    Text("How it Works").bold()
+                    Text(
+                        "It looks at the last 8-24 hours of data, excluding meal-related changes, and adjusts insulin settings like basal rates and targets when needed to match your sensitivity or resistance to insulin."
+                    )
+                }
+
+                VStack(alignment: .leading, spacing: 5) {
+                    Text("What it Adjusts").bold()
+                    Text(
+                        "Autosens modifies Insulin Sensitivity Factor (ISF), basal rates, and target blood sugar levels. It doesn’t account for carbs but adjusts for insulin effectiveness based on patterns in your glucose data."
+                    )
+                }
+
+                VStack(alignment: .leading, spacing: 5) {
+                    Text("Key Limitations").bold()
+                    Text(
+                        "Autosens has safety limits determined by your Autosens Max and Autosens Min settings. These settings prevent over-adjusting."
+                    )
+                }
+
+                Text(
+                    "Autosens functions alongside certain settings, like Super Micro Bolus (SMB). Other settings, like Dynamic ISF, alter portions of the Autosens formula. Please review the in-app hints for the Algorithm Settings prior to enabling them to understand how they may influence it."
+                )
+            }
+        }
+
+        var AutosensView: some View {
+            Section(
+                header: !state.settingsManager.preferences
+                    .useNewFormula ? Text("Autosens") : Text("Dynamic Sensitivity")
+            ) {
+                VStack {
+                    let dynamicRatio = state.determinationsFromPersistence.first?.sensitivityRatio
+                    let dynamicISF = state.determinationsFromPersistence.first?.insulinSensitivity
+                    let newISF = state.autosensISF
+                    HStack {
+                        Text("Sensitivity Ratio")
+                        Spacer()
+                        Text(
+                            rateFormatter
+                                .string(from: (
+                                    (
+                                        !state.settingsManager.preferences.useNewFormula ? state
+                                            .autosensRatio as NSDecimalNumber : dynamicRatio
+                                    ) ?? 1
+                                ) as NSNumber) ?? "1"
+                        )
+                    }.padding(.vertical)
+                    HStack {
+                        Text("Calculated Sensitivity")
+                        Spacer()
+                        if state.units == .mgdL {
+                            Text(
+                                !state.settingsManager.preferences
+                                    .useNewFormula ? newISF!.description : (dynamicISF ?? 0).description
+                            )
+                        } else {
+                            Text((
+                                !state.settingsManager.preferences
+                                    .useNewFormula ? newISF!.formattedAsMmolL : dynamicISF?.decimalValue.formattedAsMmolL
+                            ) ?? "0")
+                        }
+                        Text(state.units.rawValue + "/U").foregroundColor(.secondary)
+                    }
+
+                    HStack(alignment: .top) {
+                        Text(
+                            "This adjusted ISF is temporary, will change with the next loop cycle, and should not be directly used as your profile ISF value."
+                        )
+                        .font(.footnote)
+                        .foregroundColor(.secondary)
+                        .lineLimit(nil)
+                        Spacer()
+                        Button(
+                            action: {
+                                hintLabel = "Autosens"
+                                selectedVerboseHint = AnyView(autosensVerboseHint)
+                                shouldDisplayHint.toggle()
+                            },
+                            label: {
+                                HStack {
+                                    Image(systemName: "questionmark.circle")
+                                }
+                            }
+                        ).buttonStyle(BorderlessButtonStyle())
+                    }.padding(.top)
+                }.padding(.bottom)
+            }.listRowBackground(Color.chart)
+        }
+
         var body: some View {
             List {
+                if state.autosensISF != nil {
+                    AutosensView
+                }
+
                 SettingInputSection(
                     decimalValue: $state.autosensMax,
                     booleanValue: $booleanPlaceholder,

+ 0 - 10
FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigDataFlow.swift

@@ -1,10 +0,0 @@
-import Combine
-
-enum AutotuneConfig {
-    enum Config {}
-}
-
-protocol AutotuneConfigProvider: Provider {
-    var autotune: Autotune? { get }
-    func deleteAutotune()
-}

+ 0 - 15
FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigProvider.swift

@@ -1,15 +0,0 @@
-import Combine
-
-extension AutotuneConfig {
-    final class Provider: BaseProvider, AutotuneConfigProvider {
-        @Injected() private var apsManager: APSManager!
-
-        var autotune: Autotune? {
-            storage.retrieve(OpenAPS.Settings.autotune, as: Autotune.self)
-        }
-
-        func deleteAutotune() {
-            storage.remove(OpenAPS.Settings.autotune)
-        }
-    }
-}

+ 0 - 113
FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigStateModel.swift

@@ -1,113 +0,0 @@
-import Combine
-import LoopKit
-import SwiftUI
-
-extension AutotuneConfig {
-    final class StateModel: BaseStateModel<Provider> {
-        @Injected() var apsManager: APSManager!
-        @Injected() private var storage: FileStorage!
-        @Published var useAutotune = false
-        @Published var onlyAutotuneBasals = false
-        @Published var autotune: Autotune?
-        private(set) var units: GlucoseUnits = .mgdL
-        @Published var publishedDate = Date()
-        @Persisted(key: "lastAutotuneDate") private var lastAutotuneDate = Date() {
-            didSet {
-                DispatchQueue.main.async {
-                    self.publishedDate = self.lastAutotuneDate
-                }
-            }
-        }
-
-        override func subscribe() {
-            autotune = provider.autotune
-            units = settingsManager.settings.units
-            useAutotune = settingsManager.settings.useAutotune
-            publishedDate = lastAutotuneDate
-            subscribeSetting(\.onlyAutotuneBasals, on: $onlyAutotuneBasals) { onlyAutotuneBasals = $0 }
-
-            $useAutotune
-                .removeDuplicates()
-                .flatMap { [weak self] use -> AnyPublisher<Bool, Never> in
-                    guard let self = self else {
-                        return Just(false).eraseToAnyPublisher()
-                    }
-                    self.settingsManager.settings.useAutotune = use
-                    return Future { promise in
-                        Task.init(priority: .background) {
-                            do {
-                                _ = try await self.apsManager.makeProfiles()
-                                promise(.success(true))
-
-                            } catch {
-                                promise(.success(false))
-                            }
-                        }
-                    }
-                    .eraseToAnyPublisher()
-                }
-                .cancellable()
-                .store(in: &lifetime)
-        }
-
-        func run() {
-            Task {
-                do {
-                    if let result = await self.apsManager.autotune() {
-                        autotune = result
-                        _ = try await self.apsManager.makeProfiles()
-                        lastAutotuneDate = Date()
-                    }
-                } catch {
-                    debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to run Autotune")
-                }
-            }
-        }
-
-        func delete() async {
-            provider.deleteAutotune()
-            autotune = nil
-            do {
-                _ = try await apsManager.makeProfiles()
-            } catch {
-                return
-            }
-        }
-
-        func replace() {
-            if let autotunedBasals = autotune {
-                let basals = autotunedBasals.basalProfile
-                    .map { basal -> BasalProfileEntry in
-                        BasalProfileEntry(
-                            start: String(basal.start.prefix(5)),
-                            minutes: basal.minutes,
-                            rate: basal.rate
-                        )
-                    }
-                guard let pump = apsManager.pumpManager else {
-                    storage.save(basals, as: OpenAPS.Settings.basalProfile)
-                    debug(.service, "Basals have been replaced with Autotuned Basals by user.")
-                    return
-                }
-                let syncValues = basals.map {
-                    RepeatingScheduleValue(startTime: TimeInterval($0.minutes * 60), value: Double($0.rate))
-                }
-                pump.syncBasalRateSchedule(items: syncValues) { result in
-                    switch result {
-                    case .success:
-                        self.storage.save(basals, as: OpenAPS.Settings.basalProfile)
-                        debug(.service, "Basals saved to pump!")
-                    case .failure:
-                        debug(.service, "Basals couldn't be save to pump")
-                    }
-                }
-            }
-        }
-    }
-}
-
-extension AutotuneConfig.StateModel: SettingsObserver {
-    func settingsDidChange(_: FreeAPSSettings) {
-        units = settingsManager.settings.units
-    }
-}

+ 0 - 198
FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift

@@ -1,198 +0,0 @@
-import SwiftUI
-import Swinject
-
-extension AutotuneConfig {
-    struct RootView: BaseView {
-        let resolver: Resolver
-        @StateObject var state = StateModel()
-
-        @State private var shouldDisplayHint: Bool = false
-        @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: AnyView?
-        @State var hintLabel: String?
-        @State private var decimalPlaceholder: Decimal = 0.0
-        @State private var booleanPlaceholder: Bool = false
-
-        @State var replaceAlert = false
-
-        @Environment(\.colorScheme) var colorScheme
-        @Environment(AppState.self) var appState
-
-        private var isfFormatter: NumberFormatter {
-            let formatter = NumberFormatter()
-            formatter.numberStyle = .decimal
-            formatter.maximumFractionDigits = 2
-            return formatter
-        }
-
-        private var rateFormatter: NumberFormatter {
-            let formatter = NumberFormatter()
-            formatter.numberStyle = .decimal
-            formatter.maximumFractionDigits = 2
-            return formatter
-        }
-
-        private var dateFormatter: DateFormatter {
-            let formatter = DateFormatter()
-            formatter.dateStyle = .medium
-            formatter.timeStyle = .short
-            return formatter
-        }
-
-        var body: some View {
-            Form {
-                SettingInputSection(
-                    decimalValue: $decimalPlaceholder,
-                    booleanValue: $state.useAutotune,
-                    shouldDisplayHint: $shouldDisplayHint,
-                    selectedVerboseHint: Binding(
-                        get: { selectedVerboseHint },
-                        set: {
-                            selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = "Use Autotune"
-                        }
-                    ),
-                    units: state.units,
-                    type: .boolean,
-                    label: "Use Autotune",
-                    miniHint: "It is not advised to use Autotune with Trio.",
-                    verboseHint:
-                    VStack(alignment: .leading, spacing: 10) {
-                        Text("It is not advised to use Autotune with Trio").bold()
-                        Text("Autotune is not designed to work with Trio. It is best to keep Autotune off and do not use it.")
-
-                    },
-                    headerText: "Data-driven Adjustments"
-                )
-
-                if state.useAutotune {
-                    SettingInputSection(
-                        decimalValue: $decimalPlaceholder,
-                        booleanValue: $state.onlyAutotuneBasals,
-                        shouldDisplayHint: $shouldDisplayHint,
-                        selectedVerboseHint: Binding(
-                            get: { selectedVerboseHint },
-                            set: {
-                                selectedVerboseHint = $0.map { AnyView($0) }
-                                hintLabel = "Only Autotune Basal Insulin"
-                            }
-                        ),
-                        units: state.units,
-                        type: .boolean,
-                        label: "Only Autotune Basal Insulin",
-                        miniHint: "Restricts Autotune adjustments to only basal settings.",
-                        verboseHint: Text("Restricts Autotune adjustments to only basal settings.")
-                    )
-                }
-
-                Section(
-                    header: HStack {
-                        Text("Last run")
-                        Spacer()
-                        Text(dateFormatter.string(from: state.publishedDate))
-                    },
-                    content: {
-                        Button {
-                            state.run()
-                        } label: {
-                            Text("Run now")
-                        }
-                        .frame(maxWidth: .infinity, alignment: .center)
-                        .listRowBackground(Color(.systemBlue))
-                        .tint(.white)
-                    }
-                )
-
-                if let autotune = state.autotune {
-                    if !state.onlyAutotuneBasals {
-                        Section {
-                            HStack {
-                                Text("Carb ratio")
-                                Spacer()
-                                Text(isfFormatter.string(from: autotune.carbRatio as NSNumber) ?? "0")
-                                Text("g/U").foregroundColor(.secondary)
-                            }
-                            HStack {
-                                Text("Sensitivity")
-                                Spacer()
-                                if state.units == .mmolL {
-                                    Text(isfFormatter.string(from: autotune.sensitivity.asMmolL as NSNumber) ?? "0")
-                                } else {
-                                    Text(isfFormatter.string(from: autotune.sensitivity as NSNumber) ?? "0")
-                                }
-                                Text(state.units.rawValue + "/U").foregroundColor(.secondary)
-                            }
-                        }
-                        .listRowBackground(Color.chart)
-                    }
-
-                    Section(header: Text("Basal profile")) {
-                        ForEach(0 ..< autotune.basalProfile.count, id: \.self) { index in
-                            HStack {
-                                Text(autotune.basalProfile[index].start).foregroundColor(.secondary)
-                                Spacer()
-                                Text(rateFormatter.string(from: autotune.basalProfile[index].rate as NSNumber) ?? "0")
-                                Text("U/hr").foregroundColor(.secondary)
-                            }
-                        }
-                        HStack {
-                            Text("Total")
-                                .bold()
-                                .foregroundColor(.primary)
-                            Spacer()
-                            Text(rateFormatter.string(from: autotune.basalProfile.reduce(0) { $0 + $1.rate } as NSNumber) ?? "0")
-                                .foregroundColor(.primary) +
-                                Text(" U/day")
-                                .foregroundColor(.secondary)
-                        }
-                    }
-                    .listRowBackground(Color.chart)
-
-                    Section {
-                        Button {
-                            Task {
-                                await state.delete()
-                            }
-                        } label: {
-                            Text("Delete Autotune Data")
-                        }
-                        .frame(maxWidth: .infinity, alignment: .center)
-                        .listRowBackground(Color(.loopRed))
-                        .tint(.white)
-                    }
-
-                    Section {
-                        Button {
-                            replaceAlert = true
-                        } label: {
-                            Text("Save as Normal Basal Rates")
-                        }
-                        .frame(maxWidth: .infinity, alignment: .center)
-                        .listRowBackground(Color(.systemGray4))
-                        .tint(.white)
-                    }
-                }
-            }
-            .sheet(isPresented: $shouldDisplayHint) {
-                SettingInputHintView(
-                    hintDetent: $hintDetent,
-                    shouldDisplayHint: $shouldDisplayHint,
-                    hintLabel: hintLabel ?? "",
-                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
-                    sheetTitle: "Help"
-                )
-            }
-            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
-            .onAppear(perform: configureView)
-            .navigationTitle("Autotune")
-            .navigationBarTitleDisplayMode(.automatic)
-            .alert(Text("Are you sure?"), isPresented: $replaceAlert) {
-                Button("Yes", action: {
-                    state.replace()
-                    replaceAlert.toggle()
-                })
-                Button("No", action: { replaceAlert.toggle() })
-            }
-        }
-    }
-}

+ 7 - 0
FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift

@@ -4,6 +4,7 @@ import SwiftUI
 extension BasalProfileEditor {
     @Observable final class StateModel: BaseStateModel<Provider> {
         @ObservationIgnored @Injected() private var nightscout: NightscoutManager!
+        @ObservationIgnored @Injected() private var broadcaster: Broadcaster!
 
         var syncInProgress: Bool = false
         var initialItems: [Item] = []
@@ -105,6 +106,12 @@ extension BasalProfileEditor {
                     print("We were successful")
                 }
                 .store(in: &lifetime)
+
+            DispatchQueue.main.async {
+                self.broadcaster.notify(BasalProfileObserver.self, on: .main) {
+                    $0.basalProfileDidChange(profile)
+                }
+            }
         }
 
         @MainActor func validate() {

+ 1 - 1
FreeAPS/Sources/Modules/Calibrations/View/CalibrationsChart.swift

@@ -45,7 +45,7 @@ struct CalibrationsChart: View {
                             )
                         Text(dateFormatter.string(from: value.date))
                             .foregroundColor(.white)
-                            .font(.system(size: 10))
+                            .font(.caption2)
                             .position(
                                 x: value.x / maxValue * geo.size.width,
                                 y: geo.size.width - (value.y / maxValue * geo.size.width) + 10

+ 0 - 1
FreeAPS/Sources/Modules/CarbRatioEditor/CarbRatioEditorDataFlow.swift

@@ -27,5 +27,4 @@ enum CarbRatioEditor {
 protocol CarbRatioEditorProvider: Provider {
     var profile: CarbRatios { get }
     func saveProfile(_ profile: CarbRatios)
-    var autotune: Autotune? { get }
 }

+ 0 - 4
FreeAPS/Sources/Modules/CarbRatioEditor/CarbRatioEditorProvider.swift

@@ -11,9 +11,5 @@ extension CarbRatioEditor {
         func saveProfile(_ profile: CarbRatios) {
             storage.save(profile, as: OpenAPS.Settings.carbRatios)
         }
-
-        var autotune: Autotune? {
-            storage.retrieve(OpenAPS.Settings.autotune, as: Autotune.self)
-        }
     }
 }

+ 0 - 3
FreeAPS/Sources/Modules/CarbRatioEditor/CarbRatioEditorStateModel.swift

@@ -5,7 +5,6 @@ extension CarbRatioEditor {
         @Injected() private var nightscout: NightscoutManager!
         @Published var items: [Item] = []
         @Published var initialItems: [Item] = []
-        @Published var autotune: Autotune?
         @Published var shouldDisplaySaving: Bool = false
 
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
@@ -39,8 +38,6 @@ extension CarbRatioEditor {
             }
 
             initialItems = items.map { Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
-
-            autotune = provider.autotune
         }
 
         func add() {

+ 0 - 11
FreeAPS/Sources/Modules/CarbRatioEditor/View/CarbRatioEditorRootView.swift

@@ -68,17 +68,6 @@ extension CarbRatioEditor {
 
         var body: some View {
             Form {
-                if let autotune = state.autotune, !state.settingsManager.settings.onlyAutotuneBasals {
-                    Section(header: Text("Autotune")) {
-                        HStack {
-                            Text("Calculated Ratio")
-                            Spacer()
-                            Text(rateFormatter.string(from: autotune.carbRatio as NSNumber) ?? "0")
-                            Text("g/U").foregroundColor(.secondary)
-                        }
-                    }.listRowBackground(Color.chart)
-                }
-
                 if !state.canAdd {
                     Section {
                         VStack(alignment: .leading) {

+ 1 - 17
FreeAPS/Sources/Modules/ContactImage/View/AddContactImageSheet.swift

@@ -164,23 +164,7 @@ struct AddContactImageSheet: View {
                 }
             }
             .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
-                )
+                ContactImageHelpView(state: state, helpSheetDetent: $state.helpSheetDetent)
             }
         }
     }

+ 1 - 17
FreeAPS/Sources/Modules/ContactImage/View/ContactImageDetailView.swift

@@ -133,23 +133,7 @@ struct ContactImageDetailView: View {
             }
         }
         .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
-            )
+            ContactImageHelpView(state: state, helpSheetDetent: $state.helpSheetDetent)
         }
     }
 

+ 75 - 0
FreeAPS/Sources/Modules/ContactImage/View/ContactImageHelpView.swift

@@ -0,0 +1,75 @@
+import SwiftUI
+
+struct ContactImageHelpView: View {
+    var state: ContactImage.StateModel
+    var helpSheetDetent: Binding<PresentationDetent>
+
+    var body: some View {
+        NavigationStack {
+            List {
+                DefinitionRow(
+                    term: "How Trio Manages Contact Images",
+                    definition: Text(
+                        "Trio will automatically assign a name like 'Trio 1' to any contact image you add, and a create an entry under your iOS Contacts. Use the 'Save' button at the bottom to save your customized contact image."
+                    )
+                ).listRowBackground(Color.gray.opacity(0.1))
+
+                DefinitionRow(
+                    term: "Preview Contact Image",
+                    definition: Text(
+                        "See a live preview of your contact image design at the top of the screen. Changes made to styles, layouts, or settings are instantly reflected."
+                    )
+                ).listRowBackground(Color.gray.opacity(0.1))
+
+                DefinitionRow(term: "Customize Layout and Style", definition: VStack(alignment: .leading) {
+                    Text("Choose from multiple layout options using the Layout Picker in the 'Style' section.")
+                    Text("Enable High Contrast Mode for better visibility in certain conditions.")
+                    Text("Available Layouts:")
+                    Text("• Default: Single 'primary' value with up to two smaller values ('Top', 'Bottom') above and below it.")
+                    Text("• Split: Divides values into two separate areas of same size.")
+                }).listRowBackground(Color.gray.opacity(0.1))
+
+                DefinitionRow(term: "Set Display Values", definition: VStack(alignment: .leading) {
+                    Text("Select what values to show on the contact image (e.g., glucose, trend, none) for the available slots:")
+                    Text("• None: No value displayed.")
+                    Text("• Glucose Reading: Current CGM provided glucose value.")
+                    Text("• Eventual Glucose: Glucose value as forecasted by the oref algorithm.")
+                    Text("• Glucose Delta: Change in glucose value.")
+                    Text("• Glucose Trend: Direction of glucose change.")
+                    Text("• COB: Carbs on Board.")
+                    Text("• IOB: Insulin on Board.")
+                    Text("• Loop Status: Indicates current loop status (green, yellow, red).")
+                    Text("• Last Loop Time: Time of the last algorithm run.")
+                }).listRowBackground(Color.gray.opacity(0.1))
+
+                DefinitionRow(term: "Adjust Ring Settings", definition: VStack(alignment: .leading) {
+                    Text("Add visual Rings around the contact image to highlight information.")
+                    Text("Fine-tune the ring’s Width and Gap to suit your design preferences.")
+                    Text("Available Rings:")
+                    Text("• Hidden: No ring displayed.")
+                    Text("• Loop Status: Indicates current loop status (green, yellow, red).")
+                }).listRowBackground(Color.gray.opacity(0.1))
+
+                DefinitionRow(term: "Customize Fonts", definition: VStack(alignment: .leading) {
+                    Text("Select font size, weight, and width to match your style:")
+                    Text("• Font Size: Adjust the main text size.")
+                    Text("• Secondary Font Size: Adjust text size for values in split layouts.")
+                    Text("• Font Weight: Control how bold the text appears.")
+                    Text("• Font Width: Choose between standard or expanded text spacing.")
+                }).listRowBackground(Color.gray.opacity(0.1))
+            }
+            .scrollContentBackground(.hidden)
+            .navigationBarTitle("Help", displayMode: .inline)
+
+            Button { state.isHelpSheetPresented.toggle() }
+            label: { Text("Got it!").bold().frame(maxWidth: .infinity, minHeight: 30, alignment: .center) }
+                .buttonStyle(.bordered)
+                .padding(.top)
+        }
+        .padding()
+        .presentationDetents(
+            [.fraction(0.9), .large],
+            selection: helpSheetDetent
+        )
+    }
+}

+ 9 - 0
FreeAPS/Sources/Modules/ContactImage/View/ContactImageRootView.swift

@@ -74,6 +74,15 @@ extension ContactImage {
                     }
                     .onDelete(perform: onDelete)
                 }
+
+                Section {} header: {
+                    Text(
+                        "Add one or more contacts to your iOS Contacts to display real-time Trio metrics on your watch face. Be sure to grant Trio full access to your Contacts when prompted."
+                    )
+                    .textCase(nil)
+                    .foregroundStyle(.secondary)
+                }
+
             }.listRowBackground(Color.chart)
         }
 

+ 1 - 13
FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -110,18 +110,6 @@ extension DataTable {
                 .navigationTitle("History")
                 .navigationBarTitleDisplayMode(.large)
                 .toolbar {
-                    ToolbarItem(placement: .topBarLeading, content: {
-                        Button(
-                            action: { state.showModal(for: .statistics) },
-                            label: {
-                                HStack {
-                                    Text("Statistics")
-                                }
-                            }
-                        )
-                    })
-                }
-                .toolbar {
                     ToolbarItem(placement: .topBarTrailing, content: {
                         addButton({
                             showManualGlucose = true
@@ -141,7 +129,7 @@ extension DataTable {
                     HStack {
                         Text("Add Glucose")
                         Image(systemName: "plus")
-                            .font(.system(size: 20))
+                            .font(.title2)
                     }
                 }
             )

+ 4 - 8
FreeAPS/Sources/Modules/Home/HomeDataFlow.swift

@@ -7,12 +7,8 @@ enum Home {
 
 protocol HomeProvider: Provider {
     func heartbeatNow()
-    func pumpSettings() -> PumpSettings
-    func autotunedBasalProfile() -> [BasalProfileEntry]
-    func basalProfile() -> [BasalProfileEntry]
-    func tempTargets(hours: Int) -> [TempTarget]
-    func pumpReservoir() -> Decimal?
-    func tempTarget() -> TempTarget?
-    func announcement(_ hours: Int) -> [Announcement]
-    func getBGTarget() async -> BGTargets
+    func pumpSettings() async -> PumpSettings
+    func getBasalProfile() async -> [BasalProfileEntry]
+    func pumpReservoir() async -> Decimal?
+    func getBGTargets() async -> BGTargets
 }

+ 9 - 31
FreeAPS/Sources/Modules/Home/HomeProvider.swift

@@ -7,7 +7,6 @@ extension Home {
         @Injected() var apsManager: APSManager!
         @Injected() var glucoseStorage: GlucoseStorage!
         @Injected() var tempTargetsStorage: TempTargetsStorage!
-        @Injected() var announcementStorage: AnnouncementsStorage!
 
         func pumpTimeZone() -> TimeZone? {
             apsManager.pumpManager?.status.timeZone
@@ -17,44 +16,23 @@ extension Home {
             apsManager.heartbeat(date: Date())
         }
 
-        func tempTargets(hours: Int) -> [TempTarget] {
-            tempTargetsStorage.recent().filter {
-                $0.createdAt.addingTimeInterval(hours.hours.timeInterval) > Date()
-            }
-        }
-
-        func tempTarget() -> TempTarget? {
-            tempTargetsStorage.current()
-        }
-
-        func announcement(_ hours: Int) -> [Announcement] {
-            announcementStorage.validate().filter {
-                $0.createdAt.addingTimeInterval(hours.hours.timeInterval) > Date()
-            }
-        }
-
-        func pumpSettings() -> PumpSettings {
-            storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self)
+        func pumpSettings() async -> PumpSettings {
+            await storage.retrieveAsync(OpenAPS.Settings.settings, as: PumpSettings.self)
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
                 ?? PumpSettings(insulinActionCurve: 10, maxBolus: 10, maxBasal: 2)
         }
 
-        func pumpReservoir() -> Decimal? {
-            storage.retrieve(OpenAPS.Monitor.reservoir, as: Decimal.self)
-        }
-
-        func autotunedBasalProfile() -> [BasalProfileEntry] {
-            storage.retrieve(OpenAPS.Settings.profile, as: Autotune.self)?.basalProfile
-                ?? storage.retrieve(OpenAPS.Settings.pumpProfile, as: Autotune.self)?.basalProfile
-                ?? [BasalProfileEntry(start: "00:00", minutes: 0, rate: 1)]
+        func pumpReservoir() async -> Decimal? {
+            await storage.retrieveAsync(OpenAPS.Monitor.reservoir, as: Decimal.self)
         }
 
-        func basalProfile() -> [BasalProfileEntry] {
-            storage.retrieve(OpenAPS.Settings.pumpProfile, as: Autotune.self)?.basalProfile
-                ?? [BasalProfileEntry(start: "00:00", minutes: 0, rate: 1)]
+        func getBasalProfile() async -> [BasalProfileEntry] {
+            await storage.retrieveAsync(OpenAPS.Settings.basalProfile, as: [BasalProfileEntry].self)
+                ?? [BasalProfileEntry](from: OpenAPS.defaults(for: OpenAPS.Settings.basalProfile))
+                ?? []
         }
 
-        func getBGTarget() async -> BGTargets {
+        func getBGTargets() 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: [])

+ 95 - 0
FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/GlucoseTargetSetup.swift

@@ -0,0 +1,95 @@
+import Foundation
+
+extension Home.StateModel {
+    /**
+     Processes raw glucose target data into a list of target profiles for visualization.
+
+     - Parameters:
+        - rawTargets: The raw glucose target data containing offset and glucose values.
+        - startMarker: The reference date to start the target profiles from.
+     - Returns: An array of `TargetProfile` objects, each representing a glucose target range starting from the day of the startMarker and ending two days later.
+
+     The function:
+     - Converts glucose targets into profiles covering three consecutive days (day of startMarker, day after startMarker and day after that).
+     - Calculates start and end times for each target based on the offsets provided.
+     - Handles conversions between mg/dL and mmol/L as per user settings.
+     - Ensures targets span across midnight to avoid data cutoff.
+     */
+    func processFetchedTargets(_ rawTargets: BGTargets, startMarker: Date) -> [TargetProfile] {
+        var targetProfiles: [TargetProfile] = []
+
+        // Ensure there are targets to process
+        guard !rawTargets.targets.isEmpty else {
+            print("Warning: No targets to process in rawTargets.")
+            return []
+        }
+
+        let targets = rawTargets.targets
+
+        // Base date is the start of the day for the startMarker
+        let baseDate = Calendar.current.startOfDay(for: startMarker)
+
+        // Process each target three times
+        for index in 0 ..< (targets.count * 3) {
+            // Calculate the day offset (0 for today, 1 for tomorrow, 2 for day after)
+            let dayOffset = index / targets.count
+            let targetIndex = index % targets.count
+
+            // Validate target index to ensure safety
+            guard targetIndex < targets.count else {
+                print("Error: Invalid target index \(targetIndex).")
+                continue
+            }
+
+            // Fetch the target for the current iteration
+            let target = targets[targetIndex]
+
+            // Calculate the time offset for the current day
+            let dayTimeOffset = TimeInterval(dayOffset * 24 * 60 * 60)
+
+            // Calculate the start time for the current target
+            let startTime = baseDate
+                .addingTimeInterval(dayTimeOffset)
+                .addingTimeInterval(TimeInterval(target.offset * 60))
+
+            // Calculate the end time for the current target
+            let endTime: Date = {
+                if targetIndex + 1 < targets.count {
+                    // End time is the start time of the next target within the same day
+                    return baseDate
+                        .addingTimeInterval(dayTimeOffset)
+                        .addingTimeInterval(TimeInterval(targets[targetIndex + 1].offset * 60))
+                } else {
+                    // End time is the end of the day (midnight of the next day)
+                    return baseDate.addingTimeInterval(dayTimeOffset + 24 * 60 * 60)
+                }
+            }()
+
+            // Convert glucose value based on user unit preference (mg/dL or mmol/L)
+            let targetValue = units == .mgdL ? target.low : target.low.asMmolL
+
+            // Append the processed target profile to the list
+            targetProfiles.append(
+                TargetProfile(
+                    value: targetValue,
+                    startTime: startTime.timeIntervalSinceReferenceDate,
+                    endTime: endTime.timeIntervalSinceReferenceDate
+                )
+            )
+        }
+
+        return targetProfiles
+    }
+}
+
+struct TargetProfile: Hashable {
+    let value: Decimal
+    let startTime: TimeInterval
+    let endTime: TimeInterval
+}
+
+private extension Date {
+    var startOfDay: Date {
+        Calendar.current.startOfDay(for: self)
+    }
+}

+ 18 - 0
FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/StartEndMarkerSetup.swift

@@ -0,0 +1,18 @@
+import Foundation
+
+extension Home.StateModel {
+    // 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 * minCount * 60
+        ))
+
+        endMarker = forecastDisplayType == .lines ? threeHourSinceNow : dynamicFutureDateForCone <= threeHourSinceNow ?
+            dynamicFutureDateForCone.addingTimeInterval(TimeInterval(minutes: 30)) : threeHourSinceNow
+    }
+}

+ 48 - 36
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -19,14 +19,16 @@ extension Home {
         @ObservationIgnored @Injected() var overrideStorage: OverrideStorage!
         private let timer = DispatchTimer(timeInterval: 5)
         private(set) var filteredHours = 24
+        var startMarker = Date(timeIntervalSinceNow: TimeInterval(hours: -24))
+        var endMarker = Date(timeIntervalSinceNow: TimeInterval(hours: 3))
         var manualGlucose: [BloodGlucose] = []
-        var announcement: [Announcement] = []
         var uploadStats = false
         var recentGlucose: BloodGlucose?
         var maxBasal: Decimal = 2
-        var autotunedBasalProfile: [BasalProfileEntry] = []
         var basalProfile: [BasalProfileEntry] = []
-        var tempTargets: [TempTarget] = []
+        var bgTargets = BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
+            ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])
+        var targetProfiles: [TargetProfile] = []
         var timerDate = Date()
         var closedLoop = false
         var pumpSuspended = false
@@ -37,7 +39,6 @@ extension Home {
         var reservoir: Decimal?
         var pumpName = ""
         var pumpExpiresAtDate: Date?
-        var tempTarget: TempTarget?
         var highTTraisesSens: Bool = false
         var lowTTlowersSens: Bool = false
         var isExerciseModeActive: Bool = false
@@ -66,9 +67,8 @@ extension Home {
         var timeZone: TimeZone?
         var hours: Int16 = 6
         var totalBolus: Decimal = 0
-        var isStatusPopupPresented: Bool = false
+        var isLoopStatusPresented: Bool = false
         var isLegendPresented: Bool = false
-        var legendSheetDetent = PresentationDetent.large
         var totalInsulinDisplayType: TotalInsulinDisplayType = .totalDailyDose
         var roundedTotalBolus: String = ""
         var selectedTab: Int = 0
@@ -163,10 +163,13 @@ extension Home {
                         self.setupBatteryArray()
                     }
                     group.addTask {
-                        self.setupPumpSettings()
+                        await self.setupPumpSettings()
                     }
                     group.addTask {
-                        self.setupBasalProfile()
+                        await self.setupBasalProfile()
+                    }
+                    group.addTask {
+                        await self.setupGlucoseTargets()
                     }
                     group.addTask {
                         self.setupReservoir()
@@ -265,12 +268,12 @@ extension Home {
         }
 
         private func registerObservers() {
-            broadcaster.register(GlucoseObserver.self, observer: self)
             broadcaster.register(DeterminationObserver.self, observer: self)
             broadcaster.register(SettingsObserver.self, observer: self)
             broadcaster.register(PreferencesObserver.self, observer: self)
             broadcaster.register(PumpSettingsObserver.self, observer: self)
             broadcaster.register(BasalProfileObserver.self, observer: self)
+            broadcaster.register(BGTargetsObserver.self, observer: self)
             broadcaster.register(PumpReservoirObserver.self, observer: self)
             broadcaster.register(PumpDeactivatedObserver.self, observer: self)
 
@@ -460,25 +463,35 @@ extension Home {
             return roundedAmount.formatted()
         }
 
-        private func setupPumpSettings() {
-            DispatchQueue.main.async { [weak self] in
-                guard let self = self else { return }
-                self.maxBasal = self.provider.pumpSettings().maxBasal
+        private func setupPumpSettings() async {
+            let maxBasal = await provider.pumpSettings().maxBasal
+            await MainActor.run {
+                self.maxBasal = maxBasal
             }
         }
 
-        private func setupBasalProfile() {
-            DispatchQueue.main.async { [weak self] in
-                guard let self = self else { return }
-                self.autotunedBasalProfile = self.provider.autotunedBasalProfile()
-                self.basalProfile = self.provider.basalProfile()
+        private func setupBasalProfile() async {
+            let basalProfile = await provider.getBasalProfile()
+            await MainActor.run {
+                self.basalProfile = basalProfile
+            }
+        }
+
+        private func setupGlucoseTargets() async {
+            let bgTargets = await provider.getBGTargets()
+            let targetProfiles = processFetchedTargets(bgTargets, startMarker: startMarker)
+            await MainActor.run {
+                self.bgTargets = bgTargets
+                self.targetProfiles = targetProfiles
             }
         }
 
         private func setupReservoir() {
-            DispatchQueue.main.async { [weak self] in
-                guard let self = self else { return }
-                self.reservoir = self.provider.pumpReservoir()
+            Task {
+                let reservoir = await provider.pumpReservoir()
+                await MainActor.run {
+                    self.reservoir = reservoir
+                }
             }
         }
 
@@ -496,7 +509,6 @@ extension Home {
             dateFormatter.dateFormat = "HH:mm"
             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() {
@@ -544,21 +556,16 @@ extension Home {
 }
 
 extension Home.StateModel:
-    GlucoseObserver,
     DeterminationObserver,
     SettingsObserver,
     PreferencesObserver,
     PumpSettingsObserver,
     BasalProfileObserver,
+    BGTargetsObserver,
     PumpReservoirObserver,
     PumpTimeZoneObserver,
     PumpDeactivatedObserver
 {
-    // TODO: still needed?
-    func glucoseDidUpdate(_: [BloodGlucose]) {
-//        setupGlucose()
-    }
-
     func determinationDidUpdate(_: Determination) {
         waitForSuggestion = false
     }
@@ -595,18 +602,23 @@ extension Home.StateModel:
         lowTTlowersSens = settingsManager.preferences.lowTemptargetLowersSensitivity
     }
 
-    // TODO: is this ever really triggered? react to MOC changes?
-    func pumpHistoryDidUpdate(_: [PumpHistoryEvent]) {
-        displayPumpStatusHighlightMessage()
-    }
-
     func pumpSettingsDidChange(_: PumpSettings) {
-        setupPumpSettings()
-        setupBatteryArray()
+        Task {
+            await setupPumpSettings()
+            setupBatteryArray()
+        }
     }
 
     func basalProfileDidChange(_: [BasalProfileEntry]) {
-        setupBasalProfile()
+        Task {
+            await setupBasalProfile()
+        }
+    }
+
+    func bgTargetsDidChange(_: BGTargets) {
+        Task {
+            await setupGlucoseTargets()
+        }
     }
 
     func pumpReservoirDidChange(_: Decimal) {

+ 48 - 27
FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/BasalChart.swift

@@ -34,7 +34,7 @@ extension MainChartView {
             }
             .frame(minHeight: geo.size.height * 0.05)
             .frame(width: fullWidth(viewWidth: screenSize.width))
-            .chartXScale(domain: startMarker ... endMarker)
+            .chartXScale(domain: state.startMarker ... state.endMarker)
             .chartXAxis { basalChartXAxis }
             .chartXAxis(.hidden)
             .chartYAxis(.hidden)
@@ -96,7 +96,7 @@ extension MainChartView {
                 series: .value("profile", "profile")
             ).lineStyle(.init(lineWidth: 2, dash: [2, 4])).foregroundStyle(Color.insulin)
             LineMark(
-                x: .value("End Date", profile.endDate ?? endMarker),
+                x: .value("End Date", profile.endDate ?? state.endMarker),
                 y: .value("Amount", profile.amount),
                 series: .value("profile", "profile")
             ).lineStyle(.init(lineWidth: 2.5, dash: [2, 4])).foregroundStyle(Color.insulin)
@@ -153,7 +153,7 @@ extension MainChartView {
         return tempBasals.compactMap { temp -> (start: Date, end: Date, rate: Double)? in
             let duration = temp.tempBasal?.duration ?? 0
             let timestamp = temp.timestamp ?? Date()
-            let end = min(timestamp + duration.minutes, now)
+            let end = timestamp + duration.minutes
             let isInsulinSuspended = state.suspensions.contains { $0.timestamp ?? now >= timestamp && $0.timestamp ?? now <= end }
 
             let rate = Double(truncating: temp.tempBasal?.rate ?? Decimal.zero as NSDecimalNumber) * (isInsulinSuspended ? 0 : 1)
@@ -168,14 +168,13 @@ extension MainChartView {
 
     func findRegularBasalPoints(
         timeBegin: TimeInterval,
-        timeEnd: TimeInterval,
-        autotuned: Bool
+        timeEnd: TimeInterval
     ) async -> [BasalProfile] {
         guard timeBegin < timeEnd else { return [] }
 
         let beginDate = Date(timeIntervalSince1970: timeBegin)
         let startOfDay = Calendar.current.startOfDay(for: beginDate)
-        let profile = autotuned ? state.autotunedBasalProfile : state.basalProfile
+        let profile = state.basalProfile
         var basalPoints: [BasalProfile] = []
 
         // Iterate over the next three days, multiplying the time intervals
@@ -203,34 +202,56 @@ extension MainChartView {
         Task {
             let dayAgoTime = Date().addingTimeInterval(-1.days.timeInterval).timeIntervalSince1970
 
-            // Get Regular and Autotuned Basal parallel
             async let getRegularBasalPoints = findRegularBasalPoints(
                 timeBegin: dayAgoTime,
-                timeEnd: endMarker.timeIntervalSince1970,
-                autotuned: false
+                timeEnd: state.endMarker.timeIntervalSince1970
             )
 
-            async let getAutotunedBasalPoints = findRegularBasalPoints(
-                timeBegin: dayAgoTime,
-                timeEnd: endMarker.timeIntervalSince1970,
-                autotuned: true
-            )
+            var regularPoints = await getRegularBasalPoints
+            regularPoints.sort { $0.startDate < $1.startDate }
 
-            let (regularPoints, autotunedBasalPoints) = await (getRegularBasalPoints, getAutotunedBasalPoints)
+            var basals: [BasalProfile] = []
 
-            var totalBasal = regularPoints + autotunedBasalPoints
-            totalBasal.sort {
-                $0.startDate.timeIntervalSince1970 < $1.startDate.timeIntervalSince1970
+            // No basal data? Then there's nothing to draw
+            if regularPoints.isEmpty {
+                // basals stays empty; do nothing
             }
-
-            var basals: [BasalProfile] = []
-            totalBasal.indices.forEach { index in
-                basals.append(BasalProfile(
-                    amount: totalBasal[index].amount,
-                    isOverwritten: totalBasal[index].isOverwritten,
-                    startDate: totalBasal[index].startDate,
-                    endDate: totalBasal.count > index + 1 ? totalBasal[index + 1].startDate : endMarker
-                ))
+            // Exactly one data point?
+            else if regularPoints.count == 1 {
+                let single = regularPoints[0]
+                // Make one BasalProfile that stretches entire marker area
+                basals.append(
+                    BasalProfile(
+                        amount: single.amount,
+                        isOverwritten: single.isOverwritten,
+                        startDate: state.startMarker,
+                        endDate: state.endMarker
+                    )
+                )
+            }
+            // Multiple data points: chain them so each point ends where the next begins
+            else {
+                for i in 0 ..< (regularPoints.count - 1) {
+                    basals.append(
+                        BasalProfile(
+                            amount: regularPoints[i].amount,
+                            isOverwritten: regularPoints[i].isOverwritten,
+                            startDate: regularPoints[i].startDate,
+                            endDate: regularPoints[i + 1].startDate
+                        )
+                    )
+                }
+                // The last item goes from its start to endMarker
+                if let lastItem = regularPoints.last {
+                    basals.append(
+                        BasalProfile(
+                            amount: lastItem.amount,
+                            isOverwritten: lastItem.isOverwritten,
+                            startDate: lastItem.startDate,
+                            endDate: state.endMarker
+                        )
+                    )
+                }
             }
 
             await MainActor.run {

+ 1 - 1
FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/CobIobChart.swift

@@ -46,7 +46,7 @@ extension MainChartView {
         .chartLegend(.hidden)
         .frame(minHeight: geo.size.height * 0.12)
         .frame(width: fullWidth(viewWidth: screenSize.width))
-        .chartXScale(domain: startMarker ... endMarker)
+        .chartXScale(domain: state.startMarker ... state.endMarker)
         .chartXSelection(value: $selection)
         .chartXAxis { basalChartXAxis }
         .chartYAxis { cobIobChartYAxis }

+ 2 - 2
FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/DummyCharts.swift

@@ -43,7 +43,7 @@ extension MainChartView {
         )
         .frame(width: screenSize.width - 10)
         .chartXAxis { mainChartXAxis }
-        .chartXScale(domain: startMarker ... endMarker)
+        .chartXScale(domain: state.startMarker ... state.endMarker)
         .chartXAxis(.hidden)
         .chartYAxis { mainChartYAxis }
         .chartYScale(
@@ -69,7 +69,7 @@ extension MainChartView {
             .id("DummyCobChart")
             .frame(minHeight: geo.size.height * 0.12)
             .frame(width: screenSize.width - 10)
-            .chartXScale(domain: startMarker ... endMarker)
+            .chartXScale(domain: state.startMarker ... state.endMarker)
             .chartXAxis { basalChartXAxis }
             .chartXAxis(.hidden)
             .chartYAxis { cobIobChartYAxis }

+ 2 - 2
FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/GlucoseChartView.swift

@@ -42,7 +42,7 @@ struct GlucoseChartView: ChartContent {
                 .symbol {
                     if item.isManual {
                         Image(systemName: "drop.fill")
-                            .font(.system(size: 10))
+                            .font(.caption2)
                             .symbolRenderingMode(.monochrome)
                             .bold()
                             .foregroundStyle(.red)
@@ -61,7 +61,7 @@ struct GlucoseChartView: ChartContent {
                 .symbol {
                     if item.isManual {
                         Image(systemName: "drop.fill")
-                            .font(.system(size: 10))
+                            .font(.caption2)
                             .symbolRenderingMode(.monochrome)
                             .bold()
                             .foregroundStyle(.red)

+ 35 - 0
FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/GlucoseTargetsView.swift

@@ -0,0 +1,35 @@
+import Charts
+import Foundation
+import SwiftUI
+
+struct GlucoseTargetsView: ChartContent {
+    let targetProfiles: [TargetProfile]
+
+    var body: some ChartContent {
+        drawGlucoseTargets(for: targetProfiles)
+    }
+
+    /**
+     Draws glucose target ranges on the chart
+
+     - Returns: A ChartContent containing line marks representing target glucose ranges
+     */
+    private func drawGlucoseTargets(for targetProfiles: [TargetProfile]) -> some ChartContent {
+        // Draw target lines for each profile
+        ForEach(targetProfiles, id: \.self) { profile in
+            LineMark(
+                x: .value("Time", Date(timeIntervalSinceReferenceDate: profile.startTime)),
+                y: .value("Target", profile.value)
+            )
+            .lineStyle(.init(lineWidth: 1))
+            .foregroundStyle(Color.green.gradient)
+
+            LineMark(
+                x: .value("Time", Date(timeIntervalSinceReferenceDate: profile.endTime)),
+                y: .value("Target", profile.value)
+            )
+            .lineStyle(.init(lineWidth: 1))
+            .foregroundStyle(Color.green.gradient)
+        }
+    }
+}

+ 1 - 1
FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/OverrideView.swift

@@ -24,7 +24,7 @@ struct OverrideView: ChartContent {
                 context: viewContext
             ) ?? 0
             let end: Date = duration != 0 ? start.addingTimeInterval(duration) : start
-                .addingTimeInterval(6 * 60 * 60) // handle infinite overrides
+                .addingTimeInterval(60 * 60 * 24 * 30) // handle infinite overrides -> 60s x 60m x 24h x 30d = 30 days duration
 
             let target = getOverrideTarget(override: override)
 

+ 242 - 0
FreeAPS/Sources/Modules/Home/View/Chart/ChartLegendView.swift

@@ -0,0 +1,242 @@
+import SwiftUI
+
+struct ChartLegendView: View {
+    @Environment(AppState.self) var appState
+    @Environment(\.colorScheme) var colorScheme
+
+    var state: Home.StateModel
+
+    @State var legendSheetDetent = PresentationDetent.large
+
+    var body: some View {
+        NavigationStack {
+            VStack(alignment: .leading) {
+                Text(
+                    "The main chart in Trio is made up of various elements and shapes. Find their meanings below."
+                )
+                .font(.subheadline)
+                .foregroundColor(.secondary)
+                .padding(.top, 50)
+
+                List {
+                    VStack(alignment: .leading) {
+                        Text("Forecasts").bold().padding(.bottom, 5).textCase(.uppercase)
+                        Text(
+                            "The oref algorithm determines insulin dosing based on a number of scenarios that it estimates with different types of forecasts."
+                        )
+                        .font(.subheadline)
+                        .foregroundColor(.primary)
+
+                        if state.forecastDisplayType == .lines {
+                            legendLinesView
+                        } else {
+                            legendConeOfUncertaintyView
+                        }
+                    }.listRowBackground(Color.gray.opacity(0.1))
+
+                    VStack(alignment: .leading) {
+                        Text("Other Elements & Shapes").bold().padding(.bottom, 5).textCase(.uppercase)
+
+                        DefinitionRow(
+                            term: "Scheduled Basal Rate",
+                            definition: VStack(alignment: .leading, spacing: 10) {
+                                Text("This dotted line represents the hourly insulin rate of your scheduled basal insulin.")
+                                Text("To review or change your scheduled basal rates, go to Settings > Therapy > Basal Rates.")
+                            },
+                            color: Color.insulin,
+                            iconString: "ellipsis"
+                        )
+
+                        DefinitionRow(
+                            term: "Temporary Basal Rate (TBR)",
+                            definition: Text(
+                                "Shows current or past TBRs, which can be set by the oref algorithm or manually."
+                            ),
+                            color: Color.insulin,
+                            iconString: "square"
+                        )
+
+                        DefinitionRow(
+                            term: "Pump Suspension",
+                            definition: Text("Indicates when insulin delivery was paused, i.e. pump is suspended."),
+                            color: Color.loopGray.opacity(colorScheme == .dark ? 0.3 : 0.8),
+                            iconString: "square.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "CGM Glucose Value",
+                            definition: VStack(alignment: .leading, spacing: 10) {
+                                if state.settingsManager.settings.smoothGlucose {
+                                    Text(
+                                        "Displays real-time glucose readings from your CGM that were smoothed using the Savatzky-Golay filter. The displayed glucose readings may not match the actual readings from your CGM."
+                                    )
+                                    Text(
+                                        "Depending on your user interface settings, this may be displayed in a static (red, green, orange) or dynamic (full color spectrum) coloring scheme."
+                                    )
+                                } else {
+                                    Text(
+                                        "Displays real-time glucose readings from your CGM. Depending on your user interface settings, this may be displayed in a static (red, green, orange) or dynamic (full color spectrum) coloring scheme."
+                                    )
+                                }
+                                Text(
+                                    "To modify how glucose readings are displayed, go to Settings > Features > User Interface > Glucose Color Scheme."
+                                )
+                                if state.settingsManager.settings.smoothGlucose {
+                                    Text(
+                                        "To disable smoothing, go to Settings > Devices > Continuous Glucose Monitor > Smooth Glucose Value and toggle off the setting."
+                                    )
+                                }
+                            },
+                            color: Color.green,
+                            iconString: state.settingsManager.settings.smoothGlucose ? "record.circle.fill" : "circle.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "Manual Glucose Measurement",
+                            definition: Text("Manually entered blood glucose, such as a fingerstick test."),
+                            color: Color.red,
+                            iconString: "drop.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "Bolus",
+                            definition: Text(
+                                "Shows an insulin dose, which can be a small automated dose (super-micro-bolus), a manually entered dose, or one given externally (e.g., a pen shot)."
+                            ),
+                            color: Color.insulin,
+                            iconString: "arrowtriangle.down.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "Carb Entry",
+                            definition: Text("Tracks the carbohydrates you eat, entered to guide insulin dosing."),
+                            color: Color.orange,
+                            iconString: "arrowtriangle.down.fill",
+                            shouldRotateIcon: true
+                        )
+
+                        DefinitionRow(
+                            term: "Fat-Protein Carb Equivalent",
+                            definition: VStack(alignment: .leading, spacing: 10) {
+                                Text(
+                                    "Represents carb equivalent for fat and protein, calculated using the Warsaw Method."
+                                )
+                                Text(
+                                    "To enable or configure Warsaw Method application in Trio, go to Settings > Features > Meal Settings."
+                                )
+                            },
+                            color: Color.brown,
+                            iconString: "circle.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "Override",
+                            definition: Text(
+                                "Indicates when an override is or was active, temporarily changing therapy settings (e.g., basal rate, insulin sensitivity, carb ratio, target glucose, or whether Trio can dose SMBs)."
+                            ),
+                            color: Color.purple.opacity(0.4),
+                            iconString: "button.horizontal.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "Temporary Target",
+                            definition: Text(
+                                "Marks when a short-term temporary glucose target is or was active, (potentially) altering when or how much insulin is delivered."
+                            ),
+                            color: Color.green.opacity(0.4),
+                            iconString: "button.horizontal.fill"
+                        )
+
+                        DefinitionRow(
+                            term: "Past Insulin-on-Board (IOB)",
+                            definition: Text(
+                                "Shows the IOB value calculated by the algorithm at a specific time in the past. These values are snapshots and won’t change if insulin is added or removed after the fact."
+                            ),
+                            color: Color.darkerBlue.opacity(0.8),
+                            iconString: "line.diagonal"
+                        )
+
+                        DefinitionRow(
+                            term: "Past Carbs-on-Board (COB)",
+                            definition: Text(
+                                "Shows the COB value calculated by the algorithm at a specific time in the past. These values are snapshots and won’t change if carbs are added or removed after the fact."
+                            ),
+                            color: Color.orange.opacity(0.8),
+                            iconString: "line.diagonal"
+                        )
+                    }.listRowBackground(Color.gray.opacity(0.1))
+                }
+                .scrollContentBackground(.hidden)
+                .navigationBarTitle("Chart Legend", displayMode: .inline)
+                .padding(.trailing, 10)
+                .padding(.bottom, 15)
+
+                Button {
+                    state.isLegendPresented.toggle()
+                } label: {
+                    Text("Got it!").bold().frame(maxWidth: .infinity, minHeight: 30, alignment: .center)
+                }
+                .buttonStyle(.bordered)
+                .padding(.top)
+            }
+            .padding([.horizontal, .bottom])
+            .listSectionSpacing(10)
+            .ignoresSafeArea(edges: .top)
+            .presentationDetents(
+                [.fraction(0.9), .large],
+                selection: $legendSheetDetent
+            )
+        }
+    }
+
+    var legendLinesView: some View {
+        Group {
+            DefinitionRow(
+                term: "IOB (Insulin on Board)",
+                definition: Text(
+                    "Forecasts future glucose readings based on the amount of insulin still active in the body."
+                ),
+                color: .insulin
+            )
+
+            DefinitionRow(
+                term: "ZT (Zero-Temp)",
+                definition: Text(
+                    "Forecasts the worst-case future glucose reading scenario if no carbs are absorbed and insulin delivery is stopped until glucose starts rising."
+                ),
+                color: .zt
+            )
+
+            DefinitionRow(
+                term: "COB (Carbs on Board)",
+                definition: Text(
+                    "Forecasts future glucose reading changes by considering the amount of carbohydrates still being absorbed in the body."
+                ),
+                color: .loopYellow
+            )
+
+            DefinitionRow(
+                term: "UAM (Unannounced Meal)",
+                definition: Text(
+                    "Forecasts future glucose levels and insulin dosing needs for unexpected meals or other causes of glucose reading increases without prior notice."
+                ),
+                color: .uam
+            )
+        }
+    }
+
+    var legendConeOfUncertaintyView: some View {
+        DefinitionRow(
+            term: "Cone of Uncertainty",
+            definition: VStack(alignment: .leading, spacing: 10) {
+                Text(
+                    "For simplicity reasons, oref's various forecast curves are displayed as a \"Cone of Uncertainty\" that depicts a possible, forecasted range of future glucose fluctuation based on the current data and the algothim's result."
+                )
+                Text(
+                    "To modify how the forecast is displayed, go to Settings > Features > User Interface > Forecast Display Type."
+                )
+            },
+            color: Color.blue.opacity(0.5)
+        )
+    }
+}

+ 7 - 8
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -10,7 +10,6 @@ struct MainChartView: View {
     var safeAreaSize: CGFloat
     var units: GlucoseUnits
     var hours: Int
-    var tempTargets: [TempTarget]
     var highGlucose: Decimal
     var lowGlucose: Decimal
     var currentGlucoseTarget: Decimal
@@ -23,10 +22,6 @@ struct MainChartView: View {
 
     @State var basalProfiles: [BasalProfile] = []
     @State var preparedTempBasals: [(start: Date, end: Date, rate: Double)] = []
-    @State var startMarker =
-        Date(timeIntervalSinceNow: TimeInterval(hours: -24))
-    @State var endMarker = Date(timeIntervalSinceNow: TimeInterval(hours: 3))
-
     @State var selection: Date? = nil
 
     @State var mainChartHasInitialized = false
@@ -88,7 +83,7 @@ struct MainChartView: View {
                         }
                         .onChange(of: state.glucoseFromPersistence.last?.glucose) {
                             scroller.scrollTo("MainChart", anchor: .trailing)
-                            updateStartEndMarkers()
+                            state.updateStartEndMarkers()
                         }
                         .onChange(of: state.enactedAndNonEnactedDeterminations.first?.deliverAt) {
                             scroller.scrollTo("MainChart", anchor: .trailing)
@@ -100,7 +95,7 @@ struct MainChartView: View {
                         .onAppear {
                             if !mainChartHasInitialized {
                                 scroller.scrollTo("MainChart", anchor: .trailing)
-                                updateStartEndMarkers()
+                                state.updateStartEndMarkers()
                                 calculateTempBasalsInBackground()
                                 mainChartHasInitialized = true
                             }
@@ -122,6 +117,10 @@ extension MainChartView {
                 drawEndRuleMark()
                 drawCurrentTimeMarker()
 
+                GlucoseTargetsView(
+                    targetProfiles: state.targetProfiles
+                )
+
                 OverrideView(
                     state: state,
                     overrides: state.overrides,
@@ -192,7 +191,7 @@ extension MainChartView {
                 minHeight: geo.size.height * (0.28 - safeAreaSize)
             )
             .frame(width: fullWidth(viewWidth: screenSize.width))
-            .chartXScale(domain: startMarker ... endMarker)
+            .chartXScale(domain: state.startMarker ... state.endMarker)
             .chartXAxis { mainChartXAxis }
             .chartYAxis { mainChartYAxis }
             .chartYAxis(.hidden)

+ 248 - 0
FreeAPS/Sources/Modules/Home/View/Header/LoopStatusHelpView.swift

@@ -0,0 +1,248 @@
+import SwiftUI
+
+struct LoopStatusHelpView: View {
+    @Environment(AppState.self) var appState
+    @Environment(\.colorScheme) var colorScheme
+
+    var state: Home.StateModel
+    var helpSheetDetent: Binding<PresentationDetent>
+    var isHelpSheetPresented: Binding<Bool>
+
+    var body: some View {
+        NavigationStack {
+            VStack(alignment: .leading) {
+                Text(
+                    "The oref algorithm provides recommendations, showing key variables, decisions on temporary basal rates or super-micro-boluses, and a 'reason' field explaining its actions. Find all key terms of this 'reason' explained below:"
+                )
+                .font(.subheadline)
+                .foregroundColor(.secondary)
+                .padding(.top, 50)
+
+                List {
+                    DefinitionRow(
+                        term: "Autosens Ratio",
+                        definition: Text(
+                            "The ratio of how sensitive or resistant to insulin you are in the current loop cycle. Baseline = 1.0, Sensitive < 1.0, Resistant > 1.0"
+                        ),
+                        color: .insulin
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "ISF",
+                        definition: Text(
+                            "The first value is your profile Insulin Sensitivity Factor (ISF). The second value, after the arrow, is your adjusted ISF used for the most recent automated dosing calculation."
+                        ),
+                        color: .insulin
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "COB",
+                        definition: Text(
+                            "Amount of Carbs on Board (COB) used in the most recent automated dosing calculation."
+                        ),
+                        color: .insulin
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "Dev",
+                        definition: Text(
+                            "Abbreviation for 'Deviation'. How much the actual glucose change deviated from the BGI."
+                        ),
+                        color: .insulin
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "BGI",
+                        definition: Text(
+                            "The degree to which your glucose should be rising or falling based solely on insulin activity."
+                        ),
+                        color: .insulin
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "CR",
+                        definition: Text(
+                            "The first value is your profile Carb Ratio (CR). The second value, after the arrow, is your adjusted CR used for the most recent automated dosing calculation."
+                        ),
+                        color: .insulin
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "Target",
+                        definition: Text(
+                            "The first value is your target glucose from your settings. The second value, after the arrow, is your adjusted target glucose used for the most recent automated dosing calculation. A second value is shown if you have a temp target, override, or one of the Target Behavior options enabled."
+                        ),
+                        color: .insulin
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "minPredBG",
+                        definition: Text(
+                            "The lowest forecasted value that Trio has estimated for your future glucose."
+                        ),
+                        color: .insulin
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "minGuardBG",
+                        definition: Text(
+                            "The lowest forecasted glucose during the remaining duration of insulin action (DIA)."
+                        ),
+                        color: .insulin
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "IOBpredBG",
+                        definition: Text(
+                            "The forecasted glucose value in 4 hours calculated based on IOB only."
+                        ),
+                        color: .insulin
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "COBpredBG",
+                        definition: Text(
+                            "The forecasted glucose value in 4 hours calculated based on current IOB and COB."
+                        ),
+                        color: .insulin
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "UAMpredBG",
+                        definition: Text(
+                            "The forecasted glucose value in 4 hours based on current deviations ramping down to zero at the same rate they have been recently."
+                        ),
+                        color: .insulin
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "TDD",
+                        definition: Text(
+                            "Abbreviation for 'Total Daily Dose'. Last 24 hours of total insulin administered, both basal and bolus."
+                        ),
+                        color: .zt
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "Bolus/Basal %",
+                        definition: Text(
+                            "Of the total insulin delivered in the past 24 hours, this indicates what percentage was administered through basals and what was given through bolus."
+                        ),
+                        color: .green
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "Dynamic ISF/CR",
+                        definition: Text(
+                            "A display of On/On indicates both Dynamic ISF and CR are enabled. On/Off indicates only Dynamic ISF is enabled. Dynamic CR cannot be enabled when Dynamic ISF is disabled."
+                        ),
+                        color: .zt
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "Sigmoid function",
+                        definition: Text("If shown, Sigmoid Dynamic ISF is enabled."),
+                        color: .zt
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "Logarithmic formula",
+                        definition: Text("If shown, Logarithmic Dynamic ISF is enabled."),
+                        color: .zt
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "AF",
+                        definition: Text(
+                            "Displays the Adjustment Factor (AF) for either Logathmic or Sigmoid Dynamic ISF in use."
+                        ),
+                        color: .zt
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "SMB Ratio",
+                        definition: Text(
+                            "SMB Delivery Ratio of calculated insulin required that is given as SMB."
+                        ),
+                        color: .zt
+                    ).listRowBackground(Color.gray.opacity(0.1))
+
+                    DefinitionRow(
+                        term: "Smoothing",
+                        definition: Text("Indicates glucose smoothing is enabled."),
+                        color: .gray
+                    ).listRowBackground(Color.gray.opacity(0.1))
+                }
+                .scrollContentBackground(.hidden)
+                .navigationBarTitle("Glossary", displayMode: .inline)
+                .padding(.bottom, 15)
+
+                Button {
+                    isHelpSheetPresented.wrappedValue.toggle()
+                } label: {
+                    Text("Got it!").bold().frame(maxWidth: .infinity, minHeight: 30, alignment: .center)
+                }
+                .buttonStyle(.bordered)
+                .padding(.top)
+            }
+            .padding([.horizontal, .bottom])
+            .listSectionSpacing(10)
+            .ignoresSafeArea(edges: .top)
+            .presentationDetents(
+                [.fraction(0.9), .large],
+                selection: helpSheetDetent
+            )
+        }
+    }
+
+    var legendLinesView: some View {
+        Group {
+            DefinitionRow(
+                term: "IOB (Insulin on Board)",
+                definition: Text(
+                    "Forecasts future glucose readings based on the amount of insulin still active in the body."
+                ),
+                color: .insulin
+            )
+
+            DefinitionRow(
+                term: "ZT (Zero-Temp)",
+                definition: Text(
+                    "Forecasts the worst-case future glucose reading scenario if no carbs are absorbed and insulin delivery is stopped until glucose starts rising."
+                ),
+                color: .zt
+            )
+
+            DefinitionRow(
+                term: "COB (Carbs on Board)",
+                definition: Text(
+                    "Forecasts future glucose reading changes by considering the amount of carbohydrates still being absorbed in the body."
+                ),
+                color: .loopYellow
+            )
+
+            DefinitionRow(
+                term: "UAM (Unannounced Meal)",
+                definition: Text(
+                    "Forecasts future glucose levels and insulin dosing needs for unexpected meals or other causes of glucose reading increases without prior notice."
+                ),
+                color: .uam
+            )
+        }
+    }
+
+    var legendConeOfUncertaintyView: some View {
+        DefinitionRow(
+            term: "Cone of Uncertainty",
+            definition: VStack(alignment: .leading, spacing: 10) {
+                Text(
+                    "For simplicity reasons, oref's various forecast curves are displayed as a \"Cone of Uncertainty\" that depicts a possible, forecasted range of future glucose fluctuation based on the current data and the algothim's result."
+                )
+                Text(
+                    "To modify how the forecast is displayed, go to Settings > Features > User Interface > Forecast Display Type."
+                )
+            },
+            color: Color.blue.opacity(0.5)
+        )
+    }
+}

+ 254 - 0
FreeAPS/Sources/Modules/Home/View/Header/LoopStatusView.swift

@@ -0,0 +1,254 @@
+import SwiftUI
+
+struct LoopStatusView: View {
+    @Environment(\.colorScheme) var colorScheme
+    @Environment(AppState.self) var appState
+
+    var state: Home.StateModel
+
+    @State var sheetDetent = PresentationDetent.fraction(0.8)
+    // Help Sheet
+    @State var isHelpSheetPresented: Bool = false
+    @State var helpSheetDetent = PresentationDetent.fraction(0.9)
+
+    @State private var statusTitle: String = ""
+
+    var body: some View {
+        NavigationStack {
+            VStack(alignment: .leading, spacing: 10) {
+                Text("Current Loop Status").bold().padding(.top, 20)
+
+                Text(statusTitle)
+                    .font(.headline)
+                    .bold()
+                    .padding(.horizontal, 12)
+                    .padding(.vertical, 6)
+                    .foregroundColor(statusBadgeTextColor)
+                    .background(statusBadgeColor)
+                    .clipShape(Capsule())
+
+                if let errorMessage = state.errorMessage, let date = state.errorDate {
+                    Group {
+                        Text("Error During Algorithm Run at \(Formatter.dateFormatter.string(from: date))").font(.headline)
+                        Text(errorMessage).font(.caption)
+                    }.foregroundColor(.loopRed)
+                }
+
+                if let determination = state.determinationsFromPersistence.first {
+                    if determination.glucose == 400 {
+                        Text("Invalid CGM reading (HIGH).")
+                            .bold()
+                            .padding(.top)
+                            .foregroundStyle(Color.loopRed)
+
+                        Text("SMBs and Non-Zero Temp. Basal Rates are disabled.")
+                            .font(.subheadline)
+
+                    } else {
+                        Text("Latest Raw Algorithm Output")
+                            .bold()
+                            .padding(.top)
+
+                        Text(
+                            "Trio is currently using these metrics and values as determined by the oref algorithm:"
+                        )
+                        .font(.subheadline)
+                        .lineLimit(nil)
+                        .multilineTextAlignment(.leading)
+                        .fixedSize(horizontal: false, vertical: true)
+
+                        let tags = !state.isSmoothingEnabled ? determination.reasonParts : determination
+                            .reasonParts + ["Smoothing: On"]
+                        TagCloudView(
+                            tags: tags,
+                            shouldParseToMmolL: state.units == .mmolL
+                        )
+
+                        Text("Current Algorithm Reasoning").bold().padding(.top)
+
+                        Text(
+                            self
+                                .parseReasonConclusion(
+                                    determination.reasonConclusion,
+                                    isMmolL: state.units == .mmolL
+                                )
+                        )
+                        .font(.subheadline)
+                        .lineLimit(nil)
+                        .multilineTextAlignment(.leading)
+                        .fixedSize(horizontal: false, vertical: true)
+                    }
+                } else {
+                    Text("No recent oref algorithm determination.")
+                }
+
+                Spacer()
+
+                Button {
+                    state.isLoopStatusPresented.toggle()
+                } label: {
+                    Text("Got it!").bold().frame(maxWidth: .infinity, minHeight: 30, alignment: .center)
+                }
+                .buttonStyle(.bordered)
+                .padding(.top)
+            }
+            .padding(.vertical)
+            .padding(.horizontal, 20)
+            .presentationDetents(
+                [.fraction(0.8), .large],
+                selection: $sheetDetent
+            )
+            .ignoresSafeArea(edges: .top)
+            .background(appState.trioBackgroundColor(for: colorScheme))
+            .toolbar(content: {
+                ToolbarItem(placement: .topBarTrailing) {
+                    Button(
+                        action: {
+                            isHelpSheetPresented.toggle()
+                        },
+                        label: {
+                            Image(systemName: "questionmark.circle")
+                        }
+                    )
+                }
+            })
+            .onAppear {
+                setStatusTitle()
+            }
+            .sheet(isPresented: $isHelpSheetPresented) {
+                LoopStatusHelpView(state: state, helpSheetDetent: $helpSheetDetent, isHelpSheetPresented: $isHelpSheetPresented)
+            }
+        }
+        .scrollContentBackground(.hidden)
+    }
+
+    private var statusBadgeColor: Color {
+        guard let determination = state.determinationsFromPersistence.first, determination.timestamp != nil
+        else {
+            // previously the .timestamp property was used here because this only gets updated when the reportenacted function in the aps manager gets called
+            return .secondary
+        }
+
+        let delta = state.timerDate.timeIntervalSince(state.lastLoopDate) - 30
+
+        if delta <= 5.minutes.timeInterval {
+            guard determination.timestamp != nil else {
+                return .loopYellow
+            }
+            return .loopGreen
+        } else if delta <= 10.minutes.timeInterval {
+            return .loopYellow
+        } else {
+            return .loopRed
+        }
+    }
+
+    private var statusBadgeTextColor: Color {
+        if statusBadgeColor == .secondary {
+            .black
+        } else {
+            colorScheme == .dark ? Color(red: 25.0 / 255.0, green: 39.0 / 255.0, blue: 53.0 / 255.0, opacity: 1.0) : .white
+        }
+    }
+
+    private func setStatusTitle() {
+        if let determination = state.determinationsFromPersistence.first {
+            statusTitle =
+                "Enacted at \(Formatter.dateFormatter.string(from: determination.deliverAt ?? Date()))"
+        } else {
+            statusTitle = "Not enacted."
+        }
+    }
+
+    // TODO: Consolidate all mmol parsing methods (in TagCloudView, NightscoutManager and HomeRootView) to one central func
+    private func parseReasonConclusion(_ reasonConclusion: String, isMmolL: Bool) -> String {
+        let patterns = [
+            "minGuardBG\\s*-?\\d+\\.?\\d*<-?\\d+\\.?\\d*", // minGuardBG x<y
+            "Eventual BG\\s*-?\\d+\\.?\\d*\\s*>=\\s*-?\\d+\\.?\\d*", // Eventual BG x >= target
+            "Eventual BG\\s*-?\\d+\\.?\\d*\\s*<\\s*-?\\d+\\.?\\d*", // Eventual BG x < target
+            "(\\S+)\\s+(-?\\d+\\.?\\d*)\\s*>\\s*(\\d+)%\\s+of\\s+BG\\s+(-?\\d+\\.?\\d*)" // maxDelta x > y% of BG z
+        ]
+        let pattern = patterns.joined(separator: "|")
+        let regex = try! NSRegularExpression(pattern: pattern)
+
+        func convertToMmolL(_ value: String) -> String {
+            if let glucoseValue = Double(value.replacingOccurrences(of: "[^\\d.-]", with: "", options: .regularExpression)) {
+                let mmolValue = Decimal(glucoseValue).asMmolL
+                return mmolValue.description
+            }
+            return value
+        }
+
+        let matches = regex.matches(
+            in: reasonConclusion,
+            range: NSRange(reasonConclusion.startIndex..., in: reasonConclusion)
+        )
+        var updatedConclusion = reasonConclusion
+
+        for match in matches.reversed() {
+            guard let range = Range(match.range, in: reasonConclusion) else { continue }
+            let matchedString = String(reasonConclusion[range])
+
+            if isMmolL {
+                if matchedString.contains("<"), matchedString.contains("Eventual BG"), !matchedString.contains("=") {
+                    // Handle "Eventual BG x < target" pattern
+                    let parts = matchedString.components(separatedBy: "<")
+                    if parts.count == 2 {
+                        let bgPart = parts[0].replacingOccurrences(of: "Eventual BG", with: "")
+                            .trimmingCharacters(in: .whitespaces)
+                        let targetValue = parts[1].trimmingCharacters(in: .whitespaces)
+                        let formattedBGPart = convertToMmolL(bgPart)
+                        let formattedTargetValue = convertToMmolL(targetValue)
+                        let formattedString = "Eventual BG \(formattedBGPart)<\(formattedTargetValue)"
+                        updatedConclusion.replaceSubrange(range, with: formattedString)
+                    }
+                } else if matchedString.contains("<"), matchedString.contains("minGuardBG") {
+                    // Handle "minGuardBG x<y" pattern
+                    let parts = matchedString.components(separatedBy: "<")
+                    if parts.count == 2 {
+                        let firstValue = parts[0].trimmingCharacters(in: .whitespaces)
+                        let secondValue = parts[1].trimmingCharacters(in: .whitespaces)
+                        let formattedFirstValue = convertToMmolL(firstValue)
+                        let formattedSecondValue = convertToMmolL(secondValue)
+                        let formattedString = "minGuardBG \(formattedFirstValue)<\(formattedSecondValue)"
+                        updatedConclusion.replaceSubrange(range, with: formattedString)
+                    }
+                } else if matchedString.contains(">=") {
+                    // Handle "Eventual BG x >= target" pattern
+                    let parts = matchedString.components(separatedBy: " >= ")
+                    if parts.count == 2 {
+                        let firstValue = parts[0].replacingOccurrences(of: "Eventual BG", with: "")
+                            .trimmingCharacters(in: .whitespaces)
+                        let secondValue = parts[1].trimmingCharacters(in: .whitespaces)
+                        let formattedFirstValue = convertToMmolL(firstValue)
+                        let formattedSecondValue = convertToMmolL(secondValue)
+                        let formattedString = "Eventual BG \(formattedFirstValue) >= \(formattedSecondValue)"
+                        updatedConclusion.replaceSubrange(range, with: formattedString)
+                    }
+                } else if let localMatch = regex.firstMatch(
+                    in: matchedString,
+                    range: NSRange(matchedString.startIndex..., in: matchedString)
+                ) {
+                    // Handle "maxDelta 37 > 20% of BG 95" style
+                    if match.numberOfRanges == 5 {
+                        let metric = String(matchedString[Range(localMatch.range(at: 1), in: matchedString)!])
+                        let firstValue = String(matchedString[Range(localMatch.range(at: 2), in: matchedString)!])
+                        let percentage = String(matchedString[Range(localMatch.range(at: 3), in: matchedString)!])
+                        let bgValue = String(matchedString[Range(localMatch.range(at: 4), in: matchedString)!])
+
+                        let formattedFirstValue = convertToMmolL(firstValue)
+                        let formattedBGValue = convertToMmolL(bgValue)
+
+                        let formattedString = "\(metric) \(formattedFirstValue) > \(percentage)% of BG \(formattedBGValue)"
+                        updatedConclusion.replaceSubrange(range, with: formattedString)
+                    }
+                }
+            } else {
+                // When isMmolL is false, ensure the original value is retained without duplication
+                updatedConclusion.replaceSubrange(range, with: matchedString)
+            }
+        }
+
+        return updatedConclusion.capitalizingFirstLetter()
+    }
+}

+ 14 - 12
FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift

@@ -4,6 +4,8 @@ import SwiftUI
 import UIKit
 
 struct LoopView: View {
+    @Environment(\.colorScheme) var colorScheme
+
     private enum Config {
         static let lag: TimeInterval = 30
     }
@@ -19,10 +21,19 @@ struct LoopView: View {
     private let rect = CGRect(x: 0, y: 0, width: 18, height: 18)
 
     var body: some View {
+        loopStatusWithMinutes
+            .padding(.vertical, 5)
+            .padding(.horizontal, 10)
+            .overlay(
+                Capsule()
+                    .stroke(color.opacity(0.4), lineWidth: 2)
+            )
+    }
+
+    private var loopStatusWithMinutes: some View {
         HStack(alignment: .center) {
             ZStack {
-                Image(systemName: "circle")
-                    .mask(mask(in: rect).fill(style: FillStyle(eoFill: true)))
+                Image(systemName: (!closedLoop || manualTempBasal) ? "circle.and.line.horizontal" : "circle")
                 if isLooping {
                     ProgressView()
                 }
@@ -41,8 +52,7 @@ struct LoopView: View {
                 Text("--")
             }
         }
-        .strikethrough(!closedLoop || manualTempBasal, pattern: .solid, color: color)
-        .font(.system(size: 16, weight: .bold, design: .rounded))
+        .font(.callout).fontWeight(.bold).fontDesign(.rounded)
         .foregroundColor(color)
     }
 
@@ -80,14 +90,6 @@ struct LoopView: View {
             return .loopRed
         }
     }
-
-    func mask(in rect: CGRect) -> Path {
-        var path = Rectangle().path(in: rect)
-        if !closedLoop || manualTempBasal {
-            path.addPath(Rectangle().path(in: CGRect(x: rect.minX, y: rect.midY - 4, width: rect.width, height: 8)))
-        }
-        return path
-    }
 }
 
 extension View {

+ 48 - 23
FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift

@@ -44,46 +44,71 @@ struct PumpView: View {
                 if let reservoir = reservoir {
                     HStack {
                         Image(systemName: "cross.vial.fill")
-                            .font(.system(size: 16))
-                            .foregroundColor(reservoirColor)
+                            .font(.callout)
+
                         if reservoir == 0xDEAD_BEEF {
                             Text("50+ " + NSLocalizedString("U", comment: "Insulin unit"))
-                                .font(.system(size: 15, design: .rounded))
+                                .font(.callout)
+                                .fontWeight(.bold)
+                                .fontDesign(.rounded)
                         } else {
                             Text(
                                 Formatter.integerFormatter
                                     .string(from: reservoir as NSNumber)! + NSLocalizedString(" U", comment: "Insulin unit")
                             )
-                            .font(.system(size: 16, design: .rounded))
+                            .font(.callout)
+                            .fontWeight(.bold)
+                            .fontDesign(.rounded)
                         }
                     }
+                    .padding(.vertical, 5)
+                    .padding(.horizontal, 10)
+                    .foregroundStyle(reservoirColor)
+                    .overlay(
+                        Capsule()
+                            .stroke(reservoirColor.opacity(0.4), lineWidth: 2)
+                    )
 
                     if let timeZone = timeZone, timeZone.secondsFromGMT() != TimeZone.current.secondsFromGMT() {
-                        Image(systemName: "clock.badge.exclamationmark.fill")
-                            .font(.system(size: 16))
-                            .symbolRenderingMode(.palette)
-                            .foregroundStyle(.red, Color(.warning))
+                        HStack {
+                            Image(systemName: "clock.badge.exclamationmark.fill")
+                                .font(.callout)
+                                .symbolRenderingMode(.palette)
+                                .foregroundStyle(.red, Color(.warning))
+
+                            Text("Timezone")
+                                .font(.callout)
+                                .fontWeight(.bold)
+                                .fontDesign(.rounded)
+                                .foregroundStyle(.red)
+                        }
+                        .padding(.leading, 12)
                     }
                 }
 
                 if (battery.first?.display) != nil, let shouldBatteryDisplay = battery.first?.display, shouldBatteryDisplay {
                     HStack {
                         Image(systemName: "battery.100")
-                            .font(.system(size: 16))
-                            .foregroundColor(batteryColor)
-                        Text("\(Int(battery.first?.percent ?? 100)) %").font(.system(size: 16, design: .rounded))
+                            .font(.callout)
+                            .foregroundStyle(batteryColor)
+                        Text("\(Int(battery.first?.percent ?? 100)) %")
+                            .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
                 }
 
                 if let date = expiresAtDate {
                     HStack {
                         Image(systemName: "stopwatch.fill")
-                            .font(.system(size: 16))
-                            .foregroundColor(timerColor)
+                            .font(.callout)
+                            .foregroundStyle(timerColor)
 
                         Text(remainingTimeString(time: date.timeIntervalSince(timerDate)))
-                            .font(.system(size: 16, design: .rounded))
+                            .font(!(date.timeIntervalSince(timerDate) > 0) ? .subheadline : .callout)
+                            .fontWeight(.bold)
+                            .fontDesign(.rounded)
                     }
+                    // aligns the stopwatch icon exactly with the first pixel of the reservoir icon
+                    .padding(.leading, 12)
                 }
             }
         }
@@ -120,11 +145,11 @@ struct PumpView: View {
 
         switch battery.percent {
         case ...10:
-            return .red
+            return Color.loopRed
         case ...20:
-            return .yellow
+            return Color.orange
         default:
-            return .green
+            return Color.loopGreen
         }
     }
 
@@ -135,11 +160,11 @@ struct PumpView: View {
 
         switch reservoir {
         case ...10:
-            return .red
+            return Color.loopRed
         case ...30:
-            return .yellow
+            return Color.orange
         default:
-            return .blue
+            return Color.insulin
         }
     }
 
@@ -152,11 +177,11 @@ struct PumpView: View {
 
         switch time {
         case ...8.hours.timeInterval:
-            return .red
+            return Color.loopRed
         case ...1.days.timeInterval:
-            return .yellow
+            return Color.orange
         default:
-            return .green
+            return Color.loopGreen
         }
     }
 }

+ 144 - 330
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -5,11 +5,9 @@ import SwiftUI
 import Swinject
 
 struct TimePicker: Identifiable {
-    let label: String
-    let number: String
     var active: Bool
     let hours: Int16
-    var id: String { label }
+    var id: String { hours.description }
 }
 
 extension Home {
@@ -33,19 +31,15 @@ extension Home {
         @State var isMenuPresented = false
         @State var showTreatments = false
         @State var selectedTab: Int = 0
-        @State private var statusTitle: String = ""
         @State var showPumpSelection: Bool = false
         @State var notificationsDisabled = false
         @State var timeButtons: [TimePicker] = [
-            TimePicker(label: "2 hours", number: "2", active: false, hours: 2),
-            TimePicker(label: "4 hours", number: "4", active: false, hours: 4),
-            TimePicker(label: "6 hours", number: "6", active: false, hours: 6),
-            TimePicker(label: "12 hours", number: "12", active: false, hours: 12),
-            TimePicker(label: "24 hours", number: "24", active: false, hours: 24)
+            TimePicker(active: false, hours: 4),
+            TimePicker(active: false, hours: 6),
+            TimePicker(active: false, hours: 12),
+            TimePicker(active: false, hours: 24)
         ]
 
-        let buttonFont = Font.custom("TimeButtonFont", size: 14)
-
         @FetchRequest(fetchRequest: OverrideStored.fetch(
             NSPredicate.lastActiveOverride,
             ascending: false,
@@ -117,7 +111,8 @@ extension Home {
                 timeZone: state.timeZone,
                 pumpStatusHighlightMessage: state.pumpStatusHighlightMessage,
                 battery: state.batteryFromPersistence
-            ).onTapGesture {
+            )
+            .onTapGesture {
                 if state.pumpDisplayState == nil {
                     // shows user confirmation dialog with pump model choices, then proceeds to setup
                     showPumpSelection.toggle()
@@ -246,45 +241,73 @@ extension Home {
             return components.isEmpty ? nil : components.joined(separator: ", ")
         }
 
-        var timeInterval: some View {
-            HStack(alignment: .center) {
+        var timeIntervalButtons: some View {
+            let buttonColor = (colorScheme == .dark ? Color.white : Color.black).opacity(0.8)
+
+            return HStack(alignment: .center) {
                 ForEach(timeButtons) { button in
-                    Text(button.active ? NSLocalizedString(button.label, comment: "") : button.number).onTapGesture {
+                    Button(action: {
                         state.hours = button.hours
+                    }) {
+                        Group {
+                            if button.active {
+                                Text(
+                                    NSLocalizedString(button.hours.description, comment: "") + " " +
+                                        NSLocalizedString("h", comment: "h")
+                                )
+                            } else {
+                                Text(NSLocalizedString(button.hours.description, comment: ""))
+                            }
+                        }
+                        .font(.footnote)
+                        .fontWeight(button.active ? .semibold : .regular)
+                        .padding(.vertical, 5)
+                        .padding(.horizontal, 10)
+                        .foregroundColor(
+                            button
+                                .active ? (colorScheme == .dark ? Color.bgDarkerDarkBlue : Color.white) : buttonColor
+                        )
+                        .background(button.active ? buttonColor.opacity(colorScheme == .dark ? 1 : 0.8) : Color.clear)
+                        .clipShape(Capsule())
+                        .overlay(
+                            Capsule()
+                                .stroke(button.active ? buttonColor.opacity(0.4) : Color.clear, lineWidth: 2)
+                        )
                     }
-                    .foregroundStyle(button.active ? (colorScheme == .dark ? Color.white : Color.black).opacity(0.9) : .secondary)
-                    .frame(maxHeight: 30).padding(.horizontal, 8)
-                    .background(
-                        button.active ?
-                            // RGB(30, 60, 95)
-                            (
-                                colorScheme == .dark ? Color(red: 0.1176470588, green: 0.2352941176, blue: 0.3725490196) :
-                                    Color.white
-                            ) :
-                            Color
-                            .clear
-                    )
-                    .cornerRadius(20)
                 }
-                Button(action: {
-                    state.isLegendPresented.toggle()
-                }) {
-                    Image(systemName: "info")
-                        .foregroundColor(colorScheme == .dark ? Color.white : Color.black).opacity(0.9)
-                        .frame(width: 20, height: 20)
-                        .background(
-                            colorScheme == .dark ? Color(red: 0.1176470588, green: 0.2352941176, blue: 0.3725490196) :
-                                Color.white
-                        )
-                        .clipShape(Circle())
+            }
+        }
+
+        var statsIconString: String {
+            if #available(iOS 18, *) {
+                return "chart.line.text.clipboard"
+            } else {
+                return "list.clipboard"
+            }
+        }
+
+        @ViewBuilder private func tappableButton(
+            buttonColor: Color,
+            label: String,
+            iconString: String,
+            action: @escaping () -> Void
+        ) -> some View {
+            Button(action: {
+                action()
+            }) {
+                HStack {
+                    Image(systemName: iconString)
+                    Text(label)
                 }
-                .padding([.top, .bottom])
+                .font(.footnote)
+                .padding(.vertical, 5)
+                .padding(.horizontal, 10)
+                .foregroundStyle(buttonColor)
+                .overlay(
+                    Capsule()
+                        .stroke(buttonColor.opacity(0.4), lineWidth: 2)
+                )
             }
-            .shadow(
-                color: Color.black.opacity(colorScheme == .dark ? 0.75 : 0.33),
-                radius: colorScheme == .dark ? 5 : 3
-            )
-            .font(buttonFont)
         }
 
         @ViewBuilder func mainChart(geo: GeometryProxy) -> some View {
@@ -294,7 +317,6 @@ extension Home {
                     safeAreaSize: notificationsDisabled == true ? safeAreaSize : 0,
                     units: state.units,
                     hours: state.filteredHours,
-                    tempTargets: state.tempTargets,
                     highGlucose: state.highGlucose,
                     lowGlucose: state.lowGlucose,
                     currentGlucoseTarget: state.currentGlucoseTarget,
@@ -325,10 +347,11 @@ extension Home {
                     lastLoopDate: state.lastLoopDate,
                     manualTempBasal: state.manualTempBasal,
                     determination: state.determinationsFromPersistence
-                ).onTapGesture {
-                    state.isStatusPopupPresented = true
-                    setStatusTitle()
-                }.onLongPressGesture {
+                )
+                .onTapGesture {
+                    state.isLoopStatusPresented = true
+                }
+                .onLongPressGesture {
                     let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
                     impactHeavy.impactOccurred()
                     state.runLoop()
@@ -339,7 +362,7 @@ extension Home {
                     let bg = eventualBG as Decimal
                     HStack {
                         Image(systemName: "arrow.right.circle")
-                            .font(.system(size: 16, weight: .bold))
+                            .font(.callout).fontWeight(.bold)
                         Text(
                             Formatter.decimalFormatterWithTwoFractionDigits.string(
                                 from: (
@@ -347,15 +370,16 @@ extension Home {
                                         .asMmolL : bg
                                 ) as NSNumber
                             )!
-                        )
-                        .font(.system(size: 16))
+                        ).font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
+                    // aligns the evBG icon exactly with the first pixel of loop status icon
+                    .padding(.leading, 12)
                 } else {
                     HStack {
                         Image(systemName: "arrow.right.circle")
-                            .font(.system(size: 16, weight: .bold))
+                            .font(.callout).fontWeight(.bold)
                         Text("--")
-                            .font(.system(size: 16))
+                            .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
                 }
             }
@@ -365,7 +389,7 @@ extension Home {
             HStack {
                 HStack {
                     Image(systemName: "syringe.fill")
-                        .font(.system(size: 16))
+                        .font(.callout)
                         .foregroundColor(Color.insulin)
                     Text(
                         (
@@ -374,14 +398,14 @@ extension Home {
                         ) +
                             NSLocalizedString(" U", comment: "Insulin unit")
                     )
-                    .font(.system(size: 16, weight: .bold, design: .rounded))
+                    .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                 }
 
                 Spacer()
 
                 HStack {
                     Image(systemName: "fork.knife")
-                        .font(.system(size: 16))
+                        .font(.callout)
                         .foregroundColor(.loopYellow)
                     Text(
                         (
@@ -391,7 +415,7 @@ extension Home {
                         ) +
                             NSLocalizedString(" g", comment: "gram of carbs")
                     )
-                    .font(.system(size: 16, weight: .bold, design: .rounded))
+                    .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                 }
 
                 Spacer()
@@ -399,19 +423,29 @@ extension Home {
                 HStack {
                     if state.pumpSuspended {
                         Text("Pump suspended")
-                            .font(.system(size: 12, weight: .bold, design: .rounded)).foregroundColor(.loopGray)
+                            .font(.callout).fontWeight(.bold).fontDesign(.rounded)
+                            .foregroundColor(.loopGray)
                     } else if let tempBasalString = tempBasalString {
                         Image(systemName: "drop.circle")
-                            .font(.system(size: 16))
+                            .font(.callout)
                             .foregroundColor(.insulinTintColor)
-                        Text(tempBasalString)
-                            .font(.system(size: 16, weight: .bold, design: .rounded))
+                        if tempBasalString.count > 5 {
+                            Text(tempBasalString)
+                                .font(.callout).fontWeight(.bold).fontDesign(.rounded)
+                                .lineLimit(1)
+                                .minimumScaleFactor(0.85)
+                                .truncationMode(.tail)
+                                .allowsTightening(true)
+                        } else {
+                            // Short strings can just display normally
+                            Text(tempBasalString).font(.callout).fontWeight(.bold).fontDesign(.rounded)
+                        }
                     } else {
                         Image(systemName: "drop.circle")
-                            .font(.system(size: 16))
+                            .font(.callout)
                             .foregroundColor(.insulinTintColor)
                         Text("No Data")
-                            .font(.system(size: 16, weight: .bold, design: .rounded))
+                            .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
                 }
                 if state.totalInsulinDisplayType == .totalDailyDose {
@@ -425,7 +459,7 @@ extension Home {
                             ) +
                             NSLocalizedString(" U", comment: "Insulin unit")
                     )
-                    .font(.system(size: 16, weight: .bold, design: .rounded))
+                    .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                 } else {
                     Spacer()
                     HStack {
@@ -433,7 +467,7 @@ extension Home {
                             "TINS: \(state.roundedTotalBolus)" +
                                 NSLocalizedString(" U", comment: "Unit in number of units delivered (keep the space character!)")
                         )
-                        .font(.system(size: 16, weight: .bold, design: .rounded))
+                        .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                         .onChange(of: state.hours) {
                             state.roundedTotalBolus = state.calculateTINS()
                         }
@@ -450,7 +484,7 @@ extension Home {
         @ViewBuilder func adjustmentsOverrideView(_ overrideString: String) -> some View {
             Group {
                 Image(systemName: "clock.arrow.2.circlepath")
-                    .font(.system(size: 20))
+                    .font(.title2)
                     .foregroundStyle(Color.primary, Color.purple)
                 VStack(alignment: .leading) {
                     Text(latestOverride.first?.name ?? "Custom Override")
@@ -469,7 +503,7 @@ extension Home {
         @ViewBuilder func adjustmentsTempTargetView(_ tempTargetString: String) -> some View {
             Group {
                 Image(systemName: "target")
-                    .font(.system(size: 20))
+                    .font(.title2)
                     .foregroundStyle(Color.loopGreen)
                 VStack(alignment: .leading) {
                     Text(latestTempTarget.first?.name ?? "Temp Target")
@@ -485,7 +519,7 @@ extension Home {
 
         @ViewBuilder func adjustmentsCancelView(_ cancelAction: @escaping () -> Void) -> some View {
             Image(systemName: "xmark.app")
-                .font(.system(size: 24))
+                .font(.title)
                 .onTapGesture {
                     cancelAction()
                 }
@@ -493,7 +527,7 @@ extension Home {
 
         @ViewBuilder func adjustmentsCancelTempTargetView() -> some View {
             Image(systemName: "xmark.app")
-                .font(.system(size: 24))
+                .font(.title)
                 .confirmationDialog(
                     "Stop the Temp Target \"\(latestTempTarget.first?.name ?? "")\"?",
                     isPresented: $isConfirmStopTempTargetShown,
@@ -517,7 +551,7 @@ extension Home {
 
         @ViewBuilder func adjustmentsCancelOverrideView() -> some View {
             Image(systemName: "xmark.app")
-                .font(.system(size: 24))
+                .font(.title)
                 .confirmationDialog(
                     "Stop the Override \"\(latestOverride.first?.name ?? "")\"?",
                     isPresented: $isConfirmStopOverridePresented,
@@ -554,7 +588,7 @@ extension Home {
 
                 /// to ensure the same position....
                 Image(systemName: "xmark.app")
-                    .font(.system(size: 25))
+                    .font(.title)
                     // clear color for the icon
                     .foregroundStyle(Color.clear)
             }.onTapGesture {
@@ -563,6 +597,8 @@ extension Home {
         }
 
         @ViewBuilder func adjustmentView(geo: GeometryProxy) -> some View {
+//            let background = colorScheme == .dark ? Material.ultraThinMaterial.opacity(0.5) : Color.black.opacity(0.2)
+
             ZStack {
                 /// rectangle as background
                 RoundedRectangle(cornerRadius: 15)
@@ -574,7 +610,7 @@ extension Home {
                                     Color.insulin.opacity(0.1)
                             ) : Color.clear // Use clear and add the Material in the background
                     )
-                    .background(.ultraThinMaterial.opacity(colorScheme == .dark ? 0.35 : 0))
+                    .background(colorScheme == .dark ? Color.chart.opacity(0.25) : Color.black.opacity(0.075))
                     .clipShape(RoundedRectangle(cornerRadius: 15))
                     .frame(height: geo.size.height * 0.08)
                     .shadow(
@@ -732,7 +768,10 @@ extension Home {
 
                 }.padding(.horizontal, 10).padding(.bottom, UIDevice.adjustPadding(min: nil, max: 10))
                     .overlay(alignment: .bottom) {
-                        bolusProgressBar(progress).padding(.horizontal, 18).offset(y: 48)
+                        // Use a geo-based offset here to position progress bar independent of device size
+                        let offset = geo.size.height * 0.0725
+                        bolusProgressBar(progress).padding(.horizontal, 18)
+                            .offset(y: offset)
                     }.clipShape(RoundedRectangle(cornerRadius: 15))
             }
         }
@@ -812,8 +851,28 @@ extension Home {
 
                 mainChart(geo: geo)
 
-                timeInterval.padding(.top, UIDevice.adjustPadding(min: 0, max: 12))
-                    .padding(.bottom, UIDevice.adjustPadding(min: 0, max: 12))
+                HStack {
+                    tappableButton(
+                        buttonColor: (colorScheme == .dark ? Color.white : Color.black).opacity(0.8),
+                        label: "Stats",
+                        iconString: statsIconString,
+                        action: { state.showModal(for: .statistics) }
+                    )
+
+                    Spacer()
+
+                    timeIntervalButtons.padding(.top, UIDevice.adjustPadding(min: 0, max: 10))
+                        .padding(.bottom, UIDevice.adjustPadding(min: 0, max: 10))
+
+                    Spacer()
+
+                    tappableButton(
+                        buttonColor: (colorScheme == .dark ? Color.white : Color.black).opacity(0.8),
+                        label: "Info",
+                        iconString: "info",
+                        action: { state.isLegendPresented.toggle() }
+                    )
+                }.padding([.horizontal, .top, .bottom])
 
                 if let progress = state.bolusProgress {
                     bolusView(geo: geo, progress)
@@ -851,26 +910,9 @@ extension Home {
             .navigationTitle("Home")
             .navigationBarHidden(true)
             .ignoresSafeArea(.keyboard)
-            .popup(isPresented: state.isStatusPopupPresented, alignment: .top, direction: .top) {
-                popup
-                    .padding()
-                    .background(
-                        RoundedRectangle(cornerRadius: 8, style: .continuous)
-                            .fill(colorScheme == .dark ? Color(
-                                "Chart"
-                            ) : Color(UIColor.darkGray))
-                    )
-                    .onTapGesture {
-                        state.isStatusPopupPresented = false
-                    }
-                    .gesture(
-                        DragGesture(minimumDistance: 10, coordinateSpace: .local)
-                            .onEnded { value in
-                                if value.translation.height < 0 {
-                                    state.isStatusPopupPresented = false
-                                }
-                            }
-                    )
+            .blur(radius: state.isLoopStatusPresented ? 3 : 0)
+            .sheet(isPresented: $state.isLoopStatusPresented) {
+                LoopStatusView(state: state)
             }
             .confirmationDialog("Pump Model", isPresented: $showPumpSelection) {
                 Button("Medtronic") { state.addPump(.minimed) }
@@ -898,94 +940,8 @@ extension Home {
                 }
             }
             .sheet(isPresented: $state.isLegendPresented) {
-                legendSheetView()
-            }
-        }
-
-        @ViewBuilder func legendSheetView() -> some View {
-            NavigationStack {
-                VStack(alignment: .leading, spacing: 16) {
-                    Text(
-                        "The oref algorithm determines insulin dosing based on a number of scenarios that it estimates with different types of forecasts."
-                    )
-                    .font(.subheadline)
-                    .foregroundColor(.secondary)
-
-                    if state.forecastDisplayType == .lines {
-                        legendLinesView()
-                    } else {
-                        legendConeOfUncertaintyView()
-                    }
-
-                    Button {
-                        state.isLegendPresented.toggle()
-                    } label: {
-                        Text("Got it!")
-                            .frame(maxWidth: .infinity, alignment: .center)
-                    }
-                    .buttonStyle(.bordered)
-                    .padding(.top)
-                }
-                .padding()
-                .presentationDetents(
-                    [.fraction(0.9), .large],
-                    selection: $state.legendSheetDetent
-                )
-            }
-        }
-
-        @ViewBuilder func legendLinesView() -> some View {
-            List {
-                DefinitionRow(
-                    term: "IOB (Insulin on Board)",
-                    definition: Text(
-                        "Forecasts future glucose readings based on the amount of insulin still active in the body."
-                    ),
-                    color: .insulin
-                )
-                DefinitionRow(
-                    term: "ZT (Zero-Temp)",
-                    definition: Text(
-                        "Forecasts the worst-case future glucose reading scenario if no carbs are absorbed and insulin delivery is stopped until glucose starts rising."
-                    ),
-                    color: .zt
-                )
-                DefinitionRow(
-                    term: "COB (Carbs on Board)",
-                    definition: Text(
-                        "Forecasts future glucose reading changes by considering the amount of carbohydrates still being absorbed in the body."
-                    ),
-                    color: .loopYellow
-                )
-                DefinitionRow(
-                    term: "UAM (Unannounced Meal)",
-                    definition: Text(
-                        "Forecasts future glucose levels and insulin dosing needs for unexpected meals or other causes of glucose reading increases without prior notice."
-                    ),
-                    color: .uam
-                )
+                ChartLegendView(state: state)
             }
-            .padding(.trailing, 10)
-            .navigationBarTitle("Legend", displayMode: .inline)
-        }
-
-        @ViewBuilder func legendConeOfUncertaintyView() -> some View {
-            List {
-                DefinitionRow(
-                    term: "Cone of Uncertainty",
-                    definition: VStack {
-                        Text(
-                            "For simplicity reasons, oref's various forecast curves are displayed as a \"Cone of Uncertainty\" that depicts a possible, forecasted range of future glucose fluctuation based on the current data and the algothim's result."
-                        )
-                        Text(
-                            "Note: To modify the forecast display type, go to Trio Settings > Features > User Interface > Forecast Display Type."
-                        )
-                    },
-                    color: Color.blue.opacity(0.5)
-                )
-            }
-            .padding(.trailing, 10)
-            .navigationBarTitle("Legend", displayMode: .inline)
         }
 
         @ViewBuilder func tabBar() -> some View {
@@ -1038,13 +994,14 @@ extension Home {
                             .font(.system(size: 40))
                             .foregroundStyle(Color.tabBar)
                             .padding(.bottom, 1)
-                            .padding(.horizontal, 20)
+                            .padding(.horizontal, 22.5)
                     }
                 )
             }.ignoresSafeArea(.keyboard, edges: .bottom).blur(radius: state.waitForSuggestion ? 8 : 0)
                 .onChange(of: selectedTab) {
-                    print("current path is empty: \(settingsPath.isEmpty)")
-                    settingsPath = NavigationPath()
+                    if !settingsPath.isEmpty {
+                        settingsPath = NavigationPath()
+                    }
                 }
         }
 
@@ -1057,149 +1014,6 @@ extension Home {
                 }
             }
         }
-
-        //TODO: Consolidate all mmol parsing methods (in TagCloudView, NightscoutManager and HomeRootView) to one central func
-        private func parseReasonConclusion(_ reasonConclusion: String, isMmolL _: Bool) -> String {
-            let patterns = [
-                "minGuardBG\\s*-?\\d+\\.?\\d*<-?\\d+\\.?\\d*",
-                "Eventual BG\\s*-?\\d+\\.?\\d*\\s*>=\\s*-?\\d+\\.?\\d*",
-                "\\S+\\s+-?\\d+\\.?\\d*\\s*>\\s*\\d+%\\s+of\\s+BG\\s+-?\\d+\\.?\\d*"
-            ]
-            let pattern = patterns.joined(separator: "|")
-            let regex = try! NSRegularExpression(pattern: pattern)
-
-            func convertToMmolL(_ value: String) -> String {
-                if let glucoseValue = Double(value.replacingOccurrences(of: "[^\\d.-]", with: "", options: .regularExpression)) {
-                    let mmolValue = Decimal(glucoseValue).asMmolL
-                    return mmolValue.description
-                }
-                return value
-            }
-
-            let matches = regex.matches(
-                in: reasonConclusion,
-                range: NSRange(reasonConclusion.startIndex..., in: reasonConclusion)
-            )
-            var updatedConclusion = reasonConclusion
-
-            for match in matches.reversed() {
-                guard let range = Range(match.range, in: reasonConclusion) else { continue }
-                let matchedString = String(reasonConclusion[range])
-
-                if matchedString.contains("<") {
-                    // Handle "minGuardBG x<y" pattern
-                    let parts = matchedString.components(separatedBy: "<")
-                    if parts.count == 2,
-                       let firstValue = Double(
-                           parts[0]
-                               .components(separatedBy: CharacterSet(charactersIn: "0123456789.-").inverted).joined()
-                       ),
-                       let secondValue = Double(
-                           parts[1]
-                               .components(separatedBy: CharacterSet(charactersIn: "0123456789.-").inverted).joined()
-                       )
-                    {
-                        let formattedFirstValue = convertToMmolL(String(firstValue))
-                        let formattedSecondValue = convertToMmolL(String(secondValue))
-                        let formattedString = "minGuardBG \(formattedFirstValue)<\(formattedSecondValue)"
-                        updatedConclusion.replaceSubrange(range, with: formattedString)
-                    }
-                } else if matchedString.contains(">=") {
-                    // Handle "Eventual BG x >= target" pattern
-                    let parts = matchedString.components(separatedBy: " >= ")
-                    if parts.count == 2,
-                       let firstValue = Double(
-                           parts[0]
-                               .components(separatedBy: CharacterSet(charactersIn: "0123456789.-").inverted).joined()
-                       ),
-                       let secondValue = Double(
-                           parts[1]
-                               .components(separatedBy: CharacterSet(charactersIn: "0123456789.-").inverted).joined()
-                       )
-                    {
-                        let formattedFirstValue = convertToMmolL(String(firstValue))
-                        let formattedSecondValue = convertToMmolL(String(secondValue))
-                        let formattedString = "Eventual BG \(formattedFirstValue) >= \(formattedSecondValue)"
-                        updatedConclusion.replaceSubrange(range, with: formattedString)
-                    }
-                } else if matchedString.contains(">") {
-                    // Handle "maxDelta 37 > 20% of BG 95" style
-                    let pattern = "(\\S+)\\s+(-?\\d+\\.?\\d*)\\s*>\\s*(\\d+)%\\s+of\\s+BG\\s+(-?\\d+\\.?\\d*)"
-                    let localRegex = try! NSRegularExpression(pattern: pattern)
-                    if let localMatch = localRegex.firstMatch(
-                        in: matchedString,
-                        range: NSRange(matchedString.startIndex..., in: matchedString)
-                    ) {
-                        let metric = String(matchedString[Range(localMatch.range(at: 1), in: matchedString)!])
-                        let firstValue = String(matchedString[Range(localMatch.range(at: 2), in: matchedString)!])
-                        let percentage = String(matchedString[Range(localMatch.range(at: 3), in: matchedString)!])
-                        let bgValue = String(matchedString[Range(localMatch.range(at: 4), in: matchedString)!])
-
-                        let formattedFirstValue = convertToMmolL(firstValue)
-                        let formattedBGValue = convertToMmolL(bgValue)
-
-                        let formattedString = "\(metric) \(formattedFirstValue) > \(percentage)% of BG \(formattedBGValue)"
-                        updatedConclusion.replaceSubrange(range, with: formattedString)
-                    }
-                }
-            }
-
-            return updatedConclusion.capitalizingFirstLetter()
-        }
-
-        private var popup: some View {
-            VStack(alignment: .leading, spacing: 4) {
-                Text(statusTitle).font(.headline).foregroundColor(.white)
-                    .padding(.bottom, 4)
-                if let determination = state.determinationsFromPersistence.first {
-                    if determination.glucose == 400 {
-                        Text("Invalid CGM reading (HIGH).").font(.callout).bold().foregroundColor(.loopRed).padding(.top, 8)
-                        Text("SMBs and High Temps Disabled.").font(.caption).foregroundColor(.white).padding(.bottom, 4)
-                    } else {
-                        let tags = !state.isSmoothingEnabled ? determination.reasonParts : determination
-                            .reasonParts + ["Smoothing: On"]
-                        TagCloudView(
-                            tags: tags,
-                            shouldParseToMmolL: state.units == .mmolL
-                        )
-                        .animation(.none, value: false)
-
-                        Text(
-                            self
-                                .parseReasonConclusion(
-                                    determination.reasonConclusion,
-                                    isMmolL: state.units == .mmolL
-                                )
-                        ).font(.caption).foregroundColor(.white)
-                    }
-                } else {
-                    Text("No determination found").font(.body).foregroundColor(.white)
-                }
-
-                if let errorMessage = state.errorMessage, let date = state.errorDate {
-                    Text(NSLocalizedString("Error at", comment: "") + " " + Formatter.dateFormatter.string(from: date))
-                        .foregroundColor(.white)
-                        .font(.headline)
-                        .padding(.bottom, 4)
-                        .padding(.top, 8)
-                    Text(errorMessage).font(.caption).foregroundColor(.loopRed)
-                }
-            }
-        }
-
-        private func setStatusTitle() {
-            if let determination = state.determinationsFromPersistence.first {
-                let dateFormatter = DateFormatter()
-                dateFormatter.timeStyle = .short
-                statusTitle = NSLocalizedString("Oref Determination enacted at", comment: "Headline in enacted pop up") +
-                    " " +
-                    dateFormatter
-                    .string(from: determination.deliverAt ?? Date())
-            } else {
-                statusTitle = "No Oref determination"
-                return
-            }
-        }
     }
 }
 

+ 0 - 2
FreeAPS/Sources/Modules/ISFEditor/ISFEditorDataFlow.swift

@@ -27,6 +27,4 @@ enum ISFEditor {
 protocol ISFEditorProvider: Provider {
     var profile: InsulinSensitivities { get }
     func saveProfile(_ profile: InsulinSensitivities)
-    var autosense: Autosens { get }
-    var autotune: Autotune? { get }
 }

+ 0 - 10
FreeAPS/Sources/Modules/ISFEditor/ISFEditorProvider.swift

@@ -34,15 +34,5 @@ extension ISFEditor {
         func saveProfile(_ profile: InsulinSensitivities) {
             storage.save(profile, as: OpenAPS.Settings.insulinSensitivities)
         }
-
-        var autosense: Autosens {
-            storage.retrieve(OpenAPS.Settings.autosense, as: Autosens.self)
-                ?? Autosens(from: OpenAPS.defaults(for: OpenAPS.Settings.autosense))
-                ?? Autosens(ratio: 1, newisf: nil, timestamp: nil)
-        }
-
-        var autotune: Autotune? {
-            storage.retrieve(OpenAPS.Settings.autotune, as: Autotune.self)
-        }
     }
 }

+ 0 - 37
FreeAPS/Sources/Modules/ISFEditor/ISFEditorStateModel.swift

@@ -10,13 +10,8 @@ extension ISFEditor {
         var items: [Item] = []
         var initialItems: [Item] = []
         var shouldDisplaySaving: Bool = false
-        private(set) var autosensISF: Decimal?
-        private(set) var autosensRatio: Decimal = 0
-        var autotune: Autotune?
-        var determinationsFromPersistence: [OrefDetermination] = []
 
         let context = CoreDataStack.shared.newTaskContext()
-        let viewContext = CoreDataStack.shared.persistentContainer.viewContext
 
         let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
 
@@ -53,15 +48,6 @@ extension ISFEditor {
             }
 
             initialItems = items.map { Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
-
-            autotune = provider.autotune
-
-            if let newISF = provider.autosense.newisf {
-                autosensISF = newISF
-            }
-
-            autosensRatio = provider.autosense.ratio
-            setupDeterminationsArray()
         }
 
         func add() {
@@ -119,29 +105,6 @@ extension ISFEditor {
                 }
             }
         }
-
-        private func setupDeterminationsArray() {
-            Task {
-                let ids = await determinationStorage.fetchLastDeterminationObjectID(
-                    predicate: NSPredicate.enactedDetermination
-                )
-                await updateDeterminationsArray(with: ids)
-            }
-        }
-
-        @MainActor private func updateDeterminationsArray(with IDs: [NSManagedObjectID]) {
-            do {
-                let objects = try IDs.compactMap { id in
-                    try viewContext.existingObject(with: id) as? OrefDetermination
-                }
-                determinationsFromPersistence = objects
-
-            } catch {
-                debugPrint(
-                    "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the glucose array: \(error.localizedDescription)"
-                )
-            }
-        }
     }
 }
 

+ 0 - 0
FreeAPS/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift


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