Przeglądaj źródła

Pull in latest 'Core-Data-Sync-Trio'

polscm32 aka Marvout 1 rok temu
rodzic
commit
9026de534f
56 zmienionych plików z 961 dodań i 675 usunięć
  1. 16 40
      Trio.xcodeproj/project.pbxproj
  2. 0 2
      Trio/Resources/json/defaults/freeaps/freeaps_settings.json
  3. 3 40
      Trio/Sources/APS/APSManager.swift
  4. 5 126
      Trio/Sources/APS/OpenAPS/OpenAPS.swift
  5. 4 0
      Trio/Sources/Models/BGTargets.swift
  6. 0 7
      Trio/Sources/Models/DecimalPickerSettings.swift
  7. 0 6
      Trio/Sources/Models/Preferences.swift
  8. 0 10
      Trio/Sources/Models/TrioSettings.swift
  9. 1 1
      Trio/Sources/Modules/Adjustments/AdjustmentsProvider.swift
  10. 1 1
      Trio/Sources/Modules/Adjustments/AdjustmentsStateModel.swift
  11. 3 3
      Trio/Sources/Modules/Adjustments/View/Overrides/OverrideHelpView.swift
  12. 1 1
      Trio/Sources/Modules/Adjustments/View/TempTargets/TempTargetHelpView.swift
  13. 0 3
      Trio/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift
  14. 0 27
      Trio/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift
  15. 1 1
      Trio/Sources/Modules/Calibrations/View/CalibrationsChart.swift
  16. 0 1
      Trio/Sources/Modules/CarbRatioEditor/CarbRatioEditorDataFlow.swift
  17. 0 4
      Trio/Sources/Modules/CarbRatioEditor/CarbRatioEditorProvider.swift
  18. 0 3
      Trio/Sources/Modules/CarbRatioEditor/CarbRatioEditorStateModel.swift
  19. 0 11
      Trio/Sources/Modules/CarbRatioEditor/View/CarbRatioEditorRootView.swift
  20. 1 17
      Trio/Sources/Modules/ContactImage/View/AddContactImageSheet.swift
  21. 1 17
      Trio/Sources/Modules/ContactImage/View/ContactImageDetailView.swift
  22. 75 0
      Trio/Sources/Modules/ContactImage/View/ContactImageHelpView.swift
  23. 9 0
      Trio/Sources/Modules/ContactImage/View/ContactImageRootView.swift
  24. 1 1
      Trio/Sources/Modules/DataTable/View/DataTableRootView.swift
  25. 4 5
      Trio/Sources/Modules/Home/HomeDataFlow.swift
  26. 9 14
      Trio/Sources/Modules/Home/HomeProvider.swift
  27. 43 20
      Trio/Sources/Modules/Home/HomeStateModel.swift
  28. 2 2
      Trio/Sources/Modules/Home/View/Chart/ChartElements/GlucoseChartView.swift
  29. 140 0
      Trio/Sources/Modules/Home/View/Chart/ChartElements/GlucoseTargetsView.swift
  30. 19 6
      Trio/Sources/Modules/Home/View/Chart/ChartLegendView.swift
  31. 6 0
      Trio/Sources/Modules/Home/View/Chart/MainChartView.swift
  32. 248 0
      Trio/Sources/Modules/Home/View/Header/LoopStatusHelpView.swift
  33. 254 0
      Trio/Sources/Modules/Home/View/Header/LoopStatusView.swift
  34. 1 1
      Trio/Sources/Modules/Home/View/Header/LoopView.swift
  35. 11 8
      Trio/Sources/Modules/Home/View/Header/PumpView.swift
  36. 32 194
      Trio/Sources/Modules/Home/View/HomeRootView.swift
  37. 0 1
      Trio/Sources/Modules/ISFEditor/ISFEditorDataFlow.swift
  38. 0 4
      Trio/Sources/Modules/ISFEditor/ISFEditorProvider.swift
  39. 0 3
      Trio/Sources/Modules/ISFEditor/ISFEditorStateModel.swift
  40. 0 15
      Trio/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift
  41. 1 1
      Trio/Sources/Modules/LiveActivitySettings/View/LiveActivityWidgetConfiguration.swift
  42. 1 1
      Trio/Sources/Modules/PumpConfig/PumpConfigDataFlow.swift
  43. 4 3
      Trio/Sources/Modules/PumpConfig/PumpConfigProvider.swift
  44. 15 11
      Trio/Sources/Modules/PumpConfig/PumpConfigStateModel.swift
  45. 0 2
      Trio/Sources/Modules/Settings/SettingItems.swift
  46. 17 25
      Trio/Sources/Modules/Settings/View/SettingsRootView.swift
  47. 0 8
      Trio/Sources/Modules/Settings/View/Subviews/FeatureSettingsView.swift
  48. 1 1
      Trio/Sources/Modules/Treatments/TreatmentsDataFlow.swift
  49. 1 1
      Trio/Sources/Modules/Treatments/TreatmentsProvider.swift
  50. 1 1
      Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift
  51. 2 2
      Trio/Sources/Modules/Treatments/View/MealPreset/MealPresetView.swift
  52. 1 1
      Trio/Sources/Modules/Treatments/View/PopupView.swift
  53. 0 3
      Trio/Sources/Router/Screen.swift
  54. 1 2
      Trio/Sources/Views/SettingInputHintView.swift
  55. 0 2
      Trio/Sources/Views/SettingInputSection.swift
  56. 25 16
      Trio/Sources/Views/TagCloudView.swift

+ 16 - 40
Trio.xcodeproj/project.pbxproj

@@ -60,7 +60,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 */; };
@@ -137,7 +136,6 @@
 		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 */; };
@@ -235,7 +233,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 */; };
@@ -279,12 +276,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; };
@@ -405,7 +400,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 */; };
@@ -441,7 +435,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 */; };
@@ -470,8 +466,10 @@
 		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 */; };
 		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 */; };
@@ -830,7 +828,6 @@
 		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>"; };
@@ -933,7 +930,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>"; };
@@ -973,8 +969,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>"; };
@@ -986,7 +980,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>"; };
@@ -1112,7 +1105,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>"; };
@@ -1148,7 +1140,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>"; };
@@ -1177,8 +1171,10 @@
 		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>"; };
 		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>"; };
@@ -1557,7 +1553,6 @@
 				DDD163032C4C67B400CD525A /* Adjustments */,
 				DD1745382C55BF8B00211FAC /* AlgorithmAdvancedSettings */,
 				DD1745422C55C5C400211FAC /* AutosensSettings */,
-				672F63EEAE27400625E14BAD /* AutotuneConfig */,
 				A42F1FEDFFD0DDE00AAD54D3 /* BasalProfileEditor */,
 				3811DE0425C9D32E00A708ED /* Base */,
 				BD7DA9A32AE06DBA00601B20 /* BolusCalculatorConfig */,
@@ -1896,6 +1891,8 @@
 		3833B51F260264B6003021B3 /* Header */ = {
 			isa = PBXGroup;
 			children = (
+				DD1E53582D273F20008F32A4 /* LoopStatusHelpView.swift */,
+				DDA6E2842D2361F800C2988C /* LoopStatusView.swift */,
 				383420D525FFE38C002D46C1 /* LoopView.swift */,
 				38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */,
 				38DAB27F260CBB7F00F74C1A /* PumpView.swift */,
@@ -2003,7 +2000,6 @@
 				DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */,
 				DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */,
 				388E5A5F25B6F2310019842D /* Autosens.swift */,
-				38A00B1E25FC00F7006BC0B0 /* Autotune.swift */,
 				388358C725EEF6D200E024B2 /* BasalProfileEntry.swift */,
 				38D0B3B525EBE24900CB6E88 /* Battery.swift */,
 				382C134A25F14E3700715CE1 /* BGTargets.swift */,
@@ -2080,7 +2076,6 @@
 				FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */,
 				FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */,
 				CEA4F62229BE10F70011ADF7 /* SavitzkyGolayFilter.swift */,
-				587DA1F52B77F3DD00B28F8A /* SettingsRowView.swift */,
 				BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */,
 				BD1661302B82ADAB00256551 /* CustomProgressView.swift */,
 				581516A32BCED84A00BF67D7 /* DebuggingIdentifiers.swift */,
@@ -2281,14 +2276,6 @@
 			path = View;
 			sourceTree = "<group>";
 		};
-		55DE731ACE8289FAF3819077 /* View */ = {
-			isa = PBXGroup;
-			children = (
-				8CF5ACEE1F0859670E71B2C0 /* AutotuneConfigRootView.swift */,
-			);
-			path = View;
-			sourceTree = "<group>";
-		};
 		5825D1622BD405AE00F36E9B /* Helper */ = {
 			isa = PBXGroup;
 			children = (
@@ -2351,17 +2338,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 = (
@@ -2492,6 +2468,7 @@
 		BDDAF9F12D0055CC00B34E7A /* ChartElements */ = {
 			isa = PBXGroup;
 			children = (
+				DD2CC85B2D25D9CE00445446 /* GlucoseTargetsView.swift */,
 				BDDAF9EE2D00553E00B34E7A /* SelectionPopoverView.swift */,
 				582DF9742C8CDB92001F516D /* GlucoseChartView.swift */,
 				582DF9762C8CDBE7001F516D /* InsulinView.swift */,
@@ -3000,6 +2977,7 @@
 		E592A3722CEEC038009A472C /* View */ = {
 			isa = PBXGroup;
 			children = (
+				DDA6E3562D25988500C2988C /* ContactImageHelpView.swift */,
 				E592A3712CEEC038009A472C /* ContactImageRootView.swift */,
 				BDC531112D1060FA00088832 /* ContactImageDetailView.swift */,
 				BDC531132D10611D00088832 /* AddContactImageSheet.swift */,
@@ -3582,10 +3560,12 @@
 				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 */,
@@ -3685,13 +3665,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 */,
@@ -3762,6 +3742,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 */,
@@ -3832,7 +3813,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 */,
@@ -3863,11 +3843,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 - 2
Trio/Resources/json/defaults/freeaps/freeaps_settings.json

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

+ 3 - 40
Trio/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,7 +20,6 @@ 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?
@@ -68,7 +66,6 @@ final class BaseAPSManager: APSManager, Injectable {
     @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 {
@@ -133,7 +130,7 @@ final class BaseAPSManager: APSManager, Injectable {
             let wasParsed = storage.parseOnFileSettingsToMgdL()
             if wasParsed {
                 Task {
-                    try await makeProfiles()
+                    await openAPS.createInitialProfiles()
                 }
             }
         }
@@ -389,11 +386,9 @@ final class BaseAPSManager: APSManager, Injectable {
             let now = Date()
 
             // Start fetching asynchronously
-            let (currentTemp, _, _, _) = try await (
+            let (currentTemp, _) = try await (
                 fetchCurrentTempBasal(date: now),
-                makeProfiles(),
-                autosense(),
-                dailyAutotune()
+                autosense()
             )
 
             // Determine basal using the fetched temp and current time
@@ -431,18 +426,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)))
@@ -535,26 +518,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()
-    }
-
     private func fetchCurrentTempBasal(date: Date) async -> TempBasal {
         let results = await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: PumpEventStored.self,

+ 5 - 126
Trio/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 createInitialProfiles() 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 getTrioSettingDefaults = loadFileFromStorageAsync(name: Trio.settings)
 
-        let (pumpSettings, bgTargets, basalProfile, isf, cr, tempTargets, model, autotune, trioSettings) = await (
+        let (pumpSettings, bgTargets, basalProfile, isf, cr, tempTargets, model, trioSettings) = await (
             getPumpSettings,
             getBGTargets,
             getBasalProfile,
@@ -542,7 +483,6 @@ final class OpenAPS {
             getCR,
             getTempTargets,
             getModel,
-            getAutotune,
             getTrioSettingDefaults
         )
 
@@ -588,27 +528,18 @@ final class OpenAPS {
                 carbRatio: cr,
                 tempTargets: tempTargets,
                 model: model,
-                autotune: autotune.isEmpty ? .null : autotune,
+                autotune: RawJSON.null,
                 trioData: trioSettings
             )
 
             // 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,

+ 4 - 0
Trio/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

+ 0 - 7
Trio/Sources/Models/DecimalPickerSettings.swift

@@ -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,

+ 0 - 6
Trio/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
         }

+ 0 - 10
Trio/Sources/Models/TrioSettings.swift

@@ -18,7 +18,6 @@ enum BolusShortcutLimit: String, JSON, CaseIterable, Identifiable {
 struct TrioSettings: JSON, Equatable {
     var units: GlucoseUnits = .mgdL
     var closedLoop: Bool = false
-    var useAutotune: Bool = false
     var isUploadEnabled: Bool = false
     var isDownloadEnabled: Bool = false
     var useLocalGlucoseSource: Bool = false
@@ -67,7 +66,6 @@ struct TrioSettings: 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
@@ -93,10 +91,6 @@ extension TrioSettings: Decodable {
             settings.closedLoop = closedLoop
         }
 
-        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
         }
@@ -314,10 +308,6 @@ extension TrioSettings: 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
         }

+ 1 - 1
Trio/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: [])

+ 1 - 1
Trio/Sources/Modules/Adjustments/AdjustmentsStateModel.swift

@@ -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() {

+ 3 - 3
Trio/Sources/Modules/Adjustments/View/Overrides/OverrideHelpView.swift

@@ -14,8 +14,8 @@ struct OverrideHelpView: View {
                         )
                         .fixedSize(horizontal: false, vertical: true)
                         Text("• Basal Rate")
-                        Text("•  Insulin Sensitivity")
-                        Text("•  Carb Ratio")
+                        Text("• Insulin Sensitivity")
+                        Text("• Carb Ratio")
                         Text("• Glucose Target")
                     }
                     Text(
@@ -38,7 +38,7 @@ struct OverrideHelpView: View {
             .navigationBarTitle("Help", displayMode: .inline)
 
             Button { state.isHelpSheetPresented.toggle() }
-            label: { Text("Got it!").frame(maxWidth: .infinity, alignment: .center) }
+            label: { Text("Got it!").bold().frame(maxWidth: .infinity, minHeight: 30, alignment: .center) }
                 .buttonStyle(.bordered)
                 .padding(.top)
         }

+ 1 - 1
Trio/Sources/Modules/Adjustments/View/TempTargets/TempTargetHelpView.swift

@@ -25,7 +25,7 @@ struct TempTargetHelpView: View {
             .navigationBarTitle("Help", displayMode: .inline)
 
             Button { state.isHelpSheetPresented.toggle() }
-            label: { Text("Got it!").frame(maxWidth: .infinity, alignment: .center) }
+            label: { Text("Got it!").bold().frame(maxWidth: .infinity, minHeight: 30, alignment: .center) }
                 .buttonStyle(.bordered)
                 .padding(.top)
         }

+ 0 - 3
Trio/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
Trio/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,

+ 1 - 1
Trio/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
Trio/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
Trio/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
Trio/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
Trio/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
Trio/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
Trio/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
Trio/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
Trio/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 - 1
Trio/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -141,7 +141,7 @@ extension DataTable {
                     HStack {
                         Text("Add Glucose")
                         Image(systemName: "plus")
-                            .font(.system(size: 20))
+                            .font(.title2)
                     }
                 }
             )

+ 4 - 5
Trio/Sources/Modules/Home/HomeDataFlow.swift

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

+ 9 - 14
Trio/Sources/Modules/Home/HomeProvider.swift

@@ -26,28 +26,23 @@ extension Home {
             tempTargetsStorage.current()
         }
 
-        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 pumpReservoir() async -> Decimal? {
+            await storage.retrieveAsync(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 getBasalProfile() async -> [BasalProfileEntry] {
+            await storage.retrieveAsync(OpenAPS.Settings.basalProfile, as: [BasalProfileEntry].self)
+                ?? [BasalProfileEntry](from: OpenAPS.defaults(for: OpenAPS.Settings.basalProfile))
+                ?? []
         }
 
-        func basalProfile() -> [BasalProfileEntry] {
-            storage.retrieve(OpenAPS.Settings.pumpProfile, as: Autotune.self)?.basalProfile
-                ?? [BasalProfileEntry(start: "00:00", minutes: 0, rate: 1)]
-        }
-
-        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: [])

+ 43 - 20
Trio/Sources/Modules/Home/HomeStateModel.swift

@@ -23,8 +23,9 @@ extension Home {
         var uploadStats = false
         var recentGlucose: BloodGlucose?
         var maxBasal: Decimal = 2
-        var autotunedBasalProfile: [BasalProfileEntry] = []
         var basalProfile: [BasalProfileEntry] = []
+        var bgTargets = BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
+            ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])
         var tempTargets: [TempTarget] = []
         var timerDate = Date()
         var closedLoop = false
@@ -65,7 +66,7 @@ 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 totalInsulinDisplayType: TotalInsulinDisplayType = .totalDailyDose
         var roundedTotalBolus: String = ""
@@ -161,10 +162,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()
@@ -269,6 +273,7 @@ extension Home {
             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)
 
@@ -458,25 +463,33 @@ 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()
+            await MainActor.run {
+                self.bgTargets = bgTargets
             }
         }
 
         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
+                }
             }
         }
 
@@ -494,7 +507,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() {
@@ -548,6 +560,7 @@ extension Home.StateModel:
     PreferencesObserver,
     PumpSettingsObserver,
     BasalProfileObserver,
+    BGTargetsObserver,
     PumpReservoirObserver,
     PumpTimeZoneObserver,
     PumpDeactivatedObserver
@@ -599,12 +612,22 @@ extension Home.StateModel:
     }
 
     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) {

+ 2 - 2
Trio/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)

+ 140 - 0
Trio/Sources/Modules/Home/View/Chart/ChartElements/GlucoseTargetsView.swift

@@ -0,0 +1,140 @@
+import Charts
+import Foundation
+import SwiftUI
+
+struct GlucoseTargetsView: ChartContent {
+    let startMarker: Date
+    let units: GlucoseUnits
+    let bgTargets: BGTargets
+
+    var body: some ChartContent {
+        drawGlucoseTargets()
+    }
+
+    /**
+     Draws glucose target ranges on the chart
+
+     - Returns: A ChartContent containing line marks representing target glucose ranges
+
+     The function:
+     - Creates target profiles for two consecutive days
+     - Converts values between mg/dL and mmol/L based on user settings
+     - Draws green lines to visualize the target ranges
+     */
+    private func drawGlucoseTargets() -> some ChartContent {
+        // Array to store target profiles for visualization
+        let targetProfiles: [TargetProfile] = processFetchedTargets(bgTargets)
+
+        // Draw target lines for each profile
+        return 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)
+        }
+    }
+
+    /**
+     Processes raw glucose target data into a list of target profiles for visualization.
+
+     - Parameter rawTargets: The raw glucose target data containing offset and glucose values.
+     - Returns: An array of `TargetProfile` objects, each representing a glucose target range for today and tomorrow.
+
+     The function:
+     - Converts glucose targets into profiles covering two consecutive days (today and tomorrow).
+     - 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.
+
+     Example:
+     For a target at offset 0 (midnight) with low glucose value 70 mg/dL, the function generates two profiles:
+     - One for today from midnight to the next target offset or end of the day.
+     - Another for tomorrow covering the same time range.
+     */
+    private func processFetchedTargets(_ rawTargets: BGTargets) -> [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 twice: once for today and once for tomorrow
+        for index in 0 ..< (targets.count * 2) {
+            // Calculate the day offset (0 for today, 1 for tomorrow)
+            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)
+    }
+}

+ 19 - 6
Trio/Sources/Modules/Home/View/Chart/ChartLegendView.swift

@@ -66,15 +66,29 @@ struct ChartLegendView: View {
                         DefinitionRow(
                             term: "CGM Glucose Value",
                             definition: VStack(alignment: .leading, spacing: 10) {
-                                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."
-                                )
+                                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 ? "circle.fill" : "record.circle.fill"
+                            iconString: state.settingsManager.settings.smoothGlucose ? "record.circle.fill" : "circle.fill"
                         )
 
                         DefinitionRow(
@@ -160,8 +174,7 @@ struct ChartLegendView: View {
                 Button {
                     state.isLegendPresented.toggle()
                 } label: {
-                    Text("Got it!")
-                        .frame(maxWidth: .infinity, alignment: .center)
+                    Text("Got it!").bold().frame(maxWidth: .infinity, minHeight: 30, alignment: .center)
                 }
                 .buttonStyle(.bordered)
                 .padding(.top)

+ 6 - 0
Trio/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -137,6 +137,12 @@ extension MainChartView {
                     viewContext: context
                 )
 
+                GlucoseTargetsView(
+                    startMarker: startMarker,
+                    units: state.units,
+                    bgTargets: state.bgTargets
+                )
+
                 GlucoseChartView(
                     glucoseData: state.glucoseFromPersistence,
                     units: state.units,

+ 248 - 0
Trio/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
Trio/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()
+    }
+}

+ 1 - 1
Trio/Sources/Modules/Home/View/Header/LoopView.swift

@@ -42,7 +42,7 @@ struct LoopView: View {
             }
         }
         .strikethrough(!closedLoop || manualTempBasal, pattern: .solid, color: color)
-        .font(.system(size: 16, weight: .bold, design: .rounded))
+        .font(.callout).fontWeight(.bold).fontDesign(.rounded)
         .foregroundColor(color)
     }
 

+ 11 - 8
Trio/Sources/Modules/Home/View/Header/PumpView.swift

@@ -44,23 +44,23 @@ struct PumpView: View {
                 if let reservoir = reservoir {
                     HStack {
                         Image(systemName: "cross.vial.fill")
-                            .font(.system(size: 16))
+                            .font(.callout)
                             .foregroundColor(reservoirColor)
                         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)
                         }
                     }
 
                     if let timeZone = timeZone, timeZone.secondsFromGMT() != TimeZone.current.secondsFromGMT() {
                         Image(systemName: "clock.badge.exclamationmark.fill")
-                            .font(.system(size: 16))
+                            .font(.callout)
                             .symbolRenderingMode(.palette)
                             .foregroundStyle(.red, Color(.warning))
                     }
@@ -69,20 +69,23 @@ struct PumpView: View {
                 if (battery.first?.display) != nil, let shouldBatteryDisplay = battery.first?.display, shouldBatteryDisplay {
                     HStack {
                         Image(systemName: "battery.100")
-                            .font(.system(size: 16))
+                            .font(.callout)
                             .foregroundColor(batteryColor)
-                        Text("\(Int(battery.first?.percent ?? 100)) %").font(.system(size: 16, design: .rounded))
+                        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))
+                            .font(.callout)
                             .foregroundColor(timerColor)
 
                         Text(remainingTimeString(time: date.timeIntervalSince(timerDate)))
-                            .font(.system(size: 16, design: .rounded))
+                            .font(!(date.timeIntervalSince(timerDate) > 0) ? .subheadline : .callout)
+                            .fontWeight(.bold)
+                            .fontDesign(.rounded)
                     }
                 }
             }

+ 32 - 194
Trio/Sources/Modules/Home/View/HomeRootView.swift

@@ -33,7 +33,6 @@ 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] = [
@@ -326,8 +325,7 @@ extension Home {
                     manualTempBasal: state.manualTempBasal,
                     determination: state.determinationsFromPersistence
                 ).onTapGesture {
-                    state.isStatusPopupPresented = true
-                    setStatusTitle()
+                    state.isLoopStatusPresented = true
                 }.onLongPressGesture {
                     let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
                     impactHeavy.impactOccurred()
@@ -339,7 +337,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 +345,14 @@ extension Home {
                                         .asMmolL : bg
                                 ) as NSNumber
                             )!
-                        )
-                        .font(.system(size: 16))
+                        ).font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
                 } 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 +362,7 @@ extension Home {
             HStack {
                 HStack {
                     Image(systemName: "syringe.fill")
-                        .font(.system(size: 16))
+                        .font(.callout)
                         .foregroundColor(Color.insulin)
                     Text(
                         (
@@ -374,14 +371,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 +388,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 +396,20 @@ 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))
+                            .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 +423,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 +431,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 +448,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 +467,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 +483,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 +491,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 +515,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 +552,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 +561,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 +574,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(
@@ -854,26 +854,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) }
@@ -960,8 +943,9 @@ extension Home {
                 )
             }.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()
+                    }
                 }
         }
 
@@ -974,152 +958,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*", // 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()
-        }
-
-        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 - 1
Trio/Sources/Modules/ISFEditor/ISFEditorDataFlow.swift

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

+ 0 - 4
Trio/Sources/Modules/ISFEditor/ISFEditorProvider.swift

@@ -34,9 +34,5 @@ extension ISFEditor {
         func saveProfile(_ profile: InsulinSensitivities) {
             storage.save(profile, as: OpenAPS.Settings.insulinSensitivities)
         }
-
-        var autotune: Autotune? {
-            storage.retrieve(OpenAPS.Settings.autotune, as: Autotune.self)
-        }
     }
 }

+ 0 - 3
Trio/Sources/Modules/ISFEditor/ISFEditorStateModel.swift

@@ -10,7 +10,6 @@ extension ISFEditor {
         var items: [Item] = []
         var initialItems: [Item] = []
         var shouldDisplaySaving: Bool = false
-        var autotune: Autotune?
 
         let context = CoreDataStack.shared.newTaskContext()
 
@@ -49,8 +48,6 @@ extension ISFEditor {
             }
 
             initialItems = items.map { Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
-
-            autotune = provider.autotune
         }
 
         func add() {

+ 0 - 15
Trio/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift

@@ -61,21 +61,6 @@ extension ISFEditor {
 
         var body: some View {
             Form {
-                if let autotune = state.autotune, !state.settingsManager.settings.onlyAutotuneBasals {
-                    Section(header: Text("Autotune")) {
-                        HStack {
-                            Text("Calculated Sensitivity")
-                            Spacer()
-                            if state.units == .mgdL {
-                                Text(autotune.sensitivity.description)
-                            } else {
-                                Text(autotune.sensitivity.formattedAsMmolL)
-                            }
-                            Text(state.units.rawValue + "/U").foregroundColor(.secondary)
-                        }
-                    }.listRowBackground(Color.chart)
-                }
-
                 if !state.canAdd {
                     Section {
                         VStack(alignment: .leading) {

+ 1 - 1
Trio/Sources/Modules/LiveActivitySettings/View/LiveActivityWidgetConfiguration.swift

@@ -163,7 +163,7 @@ struct LiveActivityWidgetConfiguration: BaseView {
                         .foregroundColor(Color(UIColor.systemGray2))
                         .background(Color.white)
                         .clipShape(Circle())
-                        .font(.system(size: 20))
+                        .font(.title3)
                 }
                 .offset(x: 10, y: -10)
                 .confirmationDialog("Remove Widget", isPresented: $isRemovalConfirmationPresented, titleVisibility: .hidden) {

+ 1 - 1
Trio/Sources/Modules/PumpConfig/PumpConfigDataFlow.swift

@@ -35,5 +35,5 @@ protocol PumpConfigProvider: Provider {
     func setPumpManager(_: PumpManagerUI)
     var pumpDisplayState: AnyPublisher<PumpDisplayState?, Never> { get }
     func pumpSettings() -> PumpSettings
-    func basalProfile() -> [BasalProfileEntry]
+    func getBasalProfile() async -> [BasalProfileEntry]
 }

+ 4 - 3
Trio/Sources/Modules/PumpConfig/PumpConfigProvider.swift

@@ -14,9 +14,10 @@ extension PumpConfig {
             apsManager.pumpDisplayState.eraseToAnyPublisher()
         }
 
-        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 pumpSettings() -> PumpSettings {

+ 15 - 11
Trio/Sources/Modules/PumpConfig/PumpConfigStateModel.swift

@@ -23,19 +23,23 @@ extension PumpConfig {
                 .assign(to: \.alertNotAck, on: self)
                 .store(in: &lifetime)
 
-            let basalSchedule = BasalRateSchedule(
-                dailyItems: provider.basalProfile().map {
-                    RepeatingScheduleValue(startTime: $0.minutes.minutes.timeInterval, value: Double($0.rate))
-                }
-            )
+            Task {
+                let basalSchedule = BasalRateSchedule(
+                    dailyItems: await provider.getBasalProfile().map {
+                        RepeatingScheduleValue(startTime: $0.minutes.minutes.timeInterval, value: Double($0.rate))
+                    }
+                )
 
-            let pumpSettings = provider.pumpSettings()
+                let pumpSettings = provider.pumpSettings()
 
-            initialSettings = PumpInitialSettings(
-                maxBolusUnits: Double(pumpSettings.maxBolus),
-                maxBasalRateUnitsPerHour: Double(pumpSettings.maxBasal),
-                basalSchedule: basalSchedule!
-            )
+                await MainActor.run {
+                    initialSettings = PumpInitialSettings(
+                        maxBolusUnits: Double(pumpSettings.maxBolus),
+                        maxBasalRateUnitsPerHour: Double(pumpSettings.maxBasal),
+                        basalSchedule: basalSchedule!
+                    )
+                }
+            }
         }
 
         func addPump(_ type: PumpType) {

+ 0 - 2
Trio/Sources/Modules/Settings/SettingItems.swift

@@ -136,7 +136,6 @@ enum SettingItems {
                 "Unsuspend If No Temp",
                 "Suspend Zeros IOB",
                 "Min 5m Carbimpact",
-                // "Autotune ISF Adjustment Fraction",
                 "Remaining Carbs Fraction",
                 "Remaining Carbs Cap",
                 "Noisy CGM Target Multiplier"
@@ -212,7 +211,6 @@ enum SettingItems {
             path: ["Features", "User Interface"]
         ),
         SettingItem(title: "App Icons", view: .iconConfig)
-        // SettingItem(title: "Autotune", view: .autotuneConfig)
     ]
 
     static let notificationItems = [

+ 17 - 25
Trio/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -192,34 +192,28 @@ extension Settings {
                     Section(
                         header: Text("Search Results"),
                         content: {
-                            ForEach(filteredItems) { filteredItem in
-                                VStack(alignment: .leading) {
-                                    Text(filteredItem.matchedContent).bold()
-                                    if let path = filteredItem.settingItem.path {
-                                        Text(path.map(\.stringValue).joined(separator: " > "))
-                                            .font(.caption)
-                                            .foregroundColor(.secondary)
-                                    }
-                                }.navigationLink(to: filteredItem.settingItem.view, from: self)
+                            if filteredItems.isNotEmpty {
+                                ForEach(filteredItems) { filteredItem in
+                                    VStack(alignment: .leading) {
+                                        Text(filteredItem.matchedContent).bold()
+                                        if let path = filteredItem.settingItem.path {
+                                            Text(path.map(\.stringValue).joined(separator: " > "))
+                                                .font(.caption)
+                                                .foregroundColor(.secondary)
+                                        }
+                                    }.navigationLink(to: filteredItem.settingItem.view, from: self)
+                                }
+                            } else {
+                                Text("No settings matching your search query")
+                                    +
+                                    Text(" »\(searchText)« ").bold()
+                                    +
+                                    Text("found.")
                             }
                         }
                     ).listRowBackground(Color.chart)
                 }
 
-//                Section {
-//                    Text("Targets")
-//                        .navigationLink(to: .configEditor(file: OpenAPS.Settings.bgTargets), from: self)
-//                    Text("Sensitivities")
-//                        .navigationLink(to: .configEditor(file: OpenAPS.Settings.insulinSensitivities), from: self)
-//                    Text("Profile")
-//                        .navigationLink(to: .configEditor(file: OpenAPS.Settings.profile), from: self)
-//                    Text("Preferences")
-//                        .navigationLink(
-//                            to: .configEditor(file: OpenAPS.Settings.preferences),
-//                            from: self
-//                        )
-//                }.listRowBackground(Color.chart)
-
                 // TODO: remove this more or less entirely; add build-time flag to enable Middleware; add settings export feature
 //                Section {
 //                    Toggle("Developer Options", isOn: $state.debugOptions)
@@ -266,8 +260,6 @@ extension Settings {
 //                                .navigationLink(to: .configEditor(file: OpenAPS.Settings.profile), from: self)
 //                            //                            Text("Carbs")
 //                            //                                .navigationLink(to: .configEditor(file: OpenAPS.Monitor.carbHistory), from: self)
-//                            Text("Autotune")
-//                                .navigationLink(to: .configEditor(file: OpenAPS.Settings.autotune), from: self)
 //                        }
 //
 //                        Group {

+ 0 - 8
Trio/Sources/Modules/Settings/View/Subviews/FeatureSettingsView.swift

@@ -37,14 +37,6 @@ struct FeatureSettingsView: BaseView {
                 }
             )
             .listRowBackground(Color.chart)
-
-            // Section(
-            // header: Text("Data-Driven Settings Tuning"),
-            // content: {
-            // Text("Autotune").navigationLink(to: .autotuneConfig, from: self)
-            // }
-            // )
-            .listRowBackground(Color.chart)
         }
         .scrollContentBackground(.hidden)
         .background(appState.trioBackgroundColor(for: colorScheme))

+ 1 - 1
Trio/Sources/Modules/Treatments/TreatmentsDataFlow.swift

@@ -6,6 +6,6 @@ protocol TreatmentsProvider: Provider {
     func getPumpSettings() async -> PumpSettings
     func getBasalProfile() async -> [BasalProfileEntry]
     func getCarbRatios() async -> CarbRatios
-    func getBGTarget() async -> BGTargets
+    func getBGTargets() async -> BGTargets
     func getISFValues() async -> InsulinSensitivities
 }

+ 1 - 1
Trio/Sources/Modules/Treatments/TreatmentsProvider.swift

@@ -18,7 +18,7 @@ extension Treatments {
                 ?? CarbRatios(units: .grams, schedule: [])
         }
 
-        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: [])

+ 1 - 1
Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift

@@ -303,7 +303,7 @@ extension Treatments {
                 let carbRatios = await provider.getCarbRatios()
                 entries = carbRatios.schedule.map { ($0.start, $0.ratio) }
             case .bgTarget:
-                let bgTargets = await provider.getBGTarget()
+                let bgTargets = await provider.getBGTargets()
                 entries = bgTargets.targets.map { ($0.start, $0.low) }
             case .isf:
                 let isfValues = await provider.getISFValues()

+ 2 - 2
Trio/Sources/Modules/Treatments/View/MealPreset/MealPresetView.swift

@@ -290,7 +290,7 @@ struct MealPresetView: View {
             if carbs == 0, fat == 0, protein == 0 { state.summation = [] }
         }
         label: { Image(systemName: "minus.circle.fill")
-            .font(.system(size: 20))
+            .font(.title3)
         }
         .disabled(
             state
@@ -315,7 +315,7 @@ struct MealPresetView: View {
             state.addPresetToNewMeal()
         }
         label: { Image(systemName: "plus.circle.fill")
-            .font(.system(size: 20))
+            .font(.title3)
         }
         .disabled(state.selection == nil)
         .buttonStyle(.borderless)

+ 1 - 1
Trio/Sources/Modules/Treatments/View/PopupView.swift

@@ -101,7 +101,7 @@ struct PopupView: View {
                 Spacer()
 
                 Button { state.showInfo = false }
-                label: { Text("Got it!").frame(maxWidth: .infinity, alignment: .center) }
+                label: { Text("Got it!").bold().frame(maxWidth: .infinity, minHeight: 30, alignment: .center) }
                     .buttonStyle(.bordered)
                     .padding(.top)
             }

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

@@ -17,7 +17,6 @@ enum Screen: Identifiable, Hashable {
     case targetsEditor
     case bolus
     case manualTempBasal
-    case autotuneConfig
     case dataTable
     case cgm
     case cgmDirect
@@ -87,8 +86,6 @@ extension Screen {
             Treatments.RootView(resolver: resolver)
         case .manualTempBasal:
             ManualTempBasal.RootView(resolver: resolver)
-        case .autotuneConfig:
-            AutotuneConfig.RootView(resolver: resolver)
         case .dataTable:
             DataTable.RootView(resolver: resolver)
         case .cgm:

+ 1 - 2
Trio/Sources/Views/SettingInputHintView.swift

@@ -25,8 +25,7 @@ struct SettingInputHintView<HintView: View>: View {
             Button {
                 shouldDisplayHint.toggle()
             } label: {
-                Text("Got it!")
-                    .frame(maxWidth: .infinity, alignment: .center)
+                Text("Got it!").bold().frame(maxWidth: .infinity, minHeight: 30, alignment: .center)
             }
             .buttonStyle(.bordered)
             .padding(.top)

+ 0 - 2
Trio/Sources/Views/SettingInputSection.swift

@@ -135,8 +135,6 @@ struct SettingInputSection<VerboseHint: View>: View {
             return pickerSettingsProvider.settings.maxCOB
         case "min5mCarbimpact":
             return pickerSettingsProvider.settings.min5mCarbimpact
-        case "autotuneISFAdjustmentFraction":
-            return pickerSettingsProvider.settings.autotuneISFAdjustmentFraction
         case "remainingCarbsFraction":
             return pickerSettingsProvider.settings.remainingCarbsFraction
         case "remainingCarbsCap":

+ 25 - 16
Trio/Sources/Views/TagCloudView.swift

@@ -7,7 +7,10 @@ struct TagCloudView: View {
     var tags: [String]
     var shouldParseToMmolL: Bool
 
-    @State private var totalHeight = CGFloat.infinity // << variant for VStack
+    @Environment(\.colorScheme) var colorScheme
+
+    @State private var totalHeight = CGFloat.zero // << variant for ScrollView/List
+//    = CGFloat.infinity // << variant for VStack
 
     var body: some View {
         VStack {
@@ -15,27 +18,28 @@ struct TagCloudView: View {
                 self.generateContent(in: geometry)
             }
         }
-        .frame(maxHeight: totalHeight) // << variant for VStack
+        .frame(height: totalHeight) // << variant for ScrollView/List
+//        .frame(maxHeight: totalHeight) // << variant for VStack
     }
 
-    private func generateContent(in g: GeometryProxy) -> some View {
+    private func generateContent(in geometry: GeometryProxy) -> some View {
         var width = CGFloat.zero
         var height = CGFloat.zero
 
         return ZStack(alignment: .topLeading) {
             ForEach(self.tags, id: \.self) { tag in
-                self.item(for: tag, isMmolL: shouldParseToMmolL)
-                    .padding([.horizontal, .vertical], 2)
-                    .alignmentGuide(.leading, computeValue: { d in
-                        if abs(width - d.width) > g.size.width {
+                self.drawTag(for: tag, isMmolL: shouldParseToMmolL)
+                    .padding([.horizontal, .vertical], 3)
+                    .alignmentGuide(.leading, computeValue: { dimensions in
+                        if abs(width - dimensions.width) > geometry.size.width {
                             width = 0
-                            height -= d.height
+                            height -= dimensions.height
                         }
                         let result = width
                         if tag == self.tags.last! {
                             width = 0 // last item
                         } else {
-                            width -= d.width
+                            width -= dimensions.width
                         }
                         return result
                     })
@@ -50,7 +54,7 @@ struct TagCloudView: View {
         }.background(viewHeightReader($totalHeight))
     }
 
-    private func item(for textTag: String, isMmolL: Bool) -> some View {
+    private func drawTag(for textTag: String, isMmolL: Bool) -> some View {
         var colorOfTag: Color {
             switch textTag {
             case textTag where textTag.contains("SMB Delivery Ratio:"):
@@ -72,7 +76,7 @@ struct TagCloudView: View {
             case textTag where textTag.contains("SMB Ratio"):
                 return .orange
             case textTag where textTag.contains("Smoothing: On"):
-                return .white
+                return .gray
             default:
                 return .insulin
             }
@@ -82,12 +86,17 @@ struct TagCloudView: View {
 
         return ZStack {
             Text(formattedTextTag)
-                .padding(.vertical, 2)
-                .padding(.horizontal, 4)
+                .padding(.horizontal, 10)
+                .padding(.vertical, 5)
                 .font(.subheadline)
-                .background(colorOfTag.opacity(0.8))
-                .foregroundColor(textTag.contains("Smoothing: On") ? Color.black : Color.white)
-                .cornerRadius(2)
+                .fontWeight(.semibold)
+                .background(colorOfTag.opacity(colorScheme == .dark ? 0.15 : 0.25))
+                .foregroundColor(colorOfTag)
+                .clipShape(Capsule())
+                .overlay(
+                    Capsule()
+                        .stroke(colorOfTag.opacity(0.4), lineWidth: 2)
+                )
         }
     }