Parcourir la source

Merge remote-tracking branch 'origin' into feat/move-autosens

Andreas Stokholm il y a 1 an
Parent
commit
8525777d77
100 fichiers modifiés avec 1943 ajouts et 1040 suppressions
  1. 3 0
      .gitmodules
  2. 1 0
      Config.xcconfig
  3. 1 0
      DanaKit
  4. 62 49
      FreeAPS.xcodeproj/project.pbxproj
  5. 14 0
      FreeAPS.xcodeproj/xcshareddata/xcschemes/Trio.xcscheme
  6. 0 23
      FreeAPS/Resources/Assets.xcassets/app_icon_images/trioLoop.imageset/Contents.json
  7. BIN
      FreeAPS/Resources/Assets.xcassets/app_icon_images/trioLoop.imageset/imageLoop 1.png
  8. BIN
      FreeAPS/Resources/Assets.xcassets/app_icon_images/trioLoop.imageset/imageLoop 2.png
  9. BIN
      FreeAPS/Resources/Assets.xcassets/app_icon_images/trioLoop.imageset/imageLoop 3.png
  10. 0 20
      FreeAPS/Resources/Assets.xcassets/app_icons/trioLoop.appiconset/Contents.json
  11. BIN
      FreeAPS/Resources/Assets.xcassets/app_icons/trioLoop.appiconset/trioLoop watch.png
  12. BIN
      FreeAPS/Resources/Assets.xcassets/app_icons/trioLoop.appiconset/trioLoop.png
  13. 1 1
      FreeAPS/Resources/javascript/bundle/determine-basal.js
  14. 3 3
      FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json
  15. 1 1
      FreeAPS/Resources/json/defaults/preferences.json
  16. 1 1
      FreeAPS/Resources/json/defaults/settings/settings.json
  17. 16 17
      FreeAPS/Sources/APS/APSManager.swift
  18. 3 0
      FreeAPS/Sources/APS/DeviceDataManager.swift
  19. 0 1
      FreeAPS/Sources/APS/OpenAPS/Constants.swift
  20. 150 0
      FreeAPS/Sources/APS/Storage/ContactImageStorage.swift
  21. 0 150
      FreeAPS/Sources/APS/Storage/ContactTrickStorage.swift
  22. 1 1
      FreeAPS/Sources/Assemblies/ServiceAssembly.swift
  23. 1 1
      FreeAPS/Sources/Assemblies/StorageAssembly.swift
  24. 4 0
      FreeAPS/Sources/Helpers/ConstantValues.swift
  25. 1 1
      FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings
  26. 1 1
      FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings
  27. 1 1
      FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings
  28. 1 1
      FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings
  29. 1 1
      FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings
  30. 1 1
      FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings
  31. 1 1
      FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings
  32. 1 1
      FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings
  33. 1 1
      FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings
  34. 1 1
      FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings
  35. 1 1
      FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings
  36. 1 1
      FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings
  37. 1 1
      FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings
  38. 1 1
      FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings
  39. 1 1
      FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings
  40. 1 1
      FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings
  41. 1 1
      FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings
  42. 1 1
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  43. 1 1
      FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings
  44. 1 1
      FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings
  45. 1 1
      FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings
  46. 1 1
      FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings
  47. 15 15
      FreeAPS/Sources/Models/ContactTrickEntry.swift
  48. 17 17
      FreeAPS/Sources/Models/DecimalPickerSettings.swift
  49. 7 7
      FreeAPS/Sources/Models/FreeAPSSettings.swift
  50. 16 0
      FreeAPS/Sources/Models/HbA1cDisplayUnit.swift
  51. 0 1
      FreeAPS/Sources/Models/Icons.swift
  52. 1 1
      FreeAPS/Sources/Models/Preferences.swift
  53. 16 0
      FreeAPS/Sources/Models/TimeInRangeChartStyle.swift
  54. 2 2
      FreeAPS/Sources/Models/TotalInsulinDisplayType.swift
  55. 1 1
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsProvider.swift
  56. 1 1
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift
  57. 175 96
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift
  58. 50 20
      FreeAPS/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift
  59. 23 18
      FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift
  60. 67 21
      FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift
  61. 33 12
      FreeAPS/Sources/Modules/CGM/View/CGMRootView.swift
  62. 47 11
      FreeAPS/Sources/Modules/CalendarEventSettings/View/CalendarEventSettingsRootView.swift
  63. 1 1
      FreeAPS/Sources/Modules/CarbRatioEditor/CarbRatioEditorStateModel.swift
  64. 8 0
      FreeAPS/Sources/Modules/ContactImage/ContactImageDataFlow.swift
  65. 6 0
      FreeAPS/Sources/Modules/ContactImage/ContactImageProvider.swift
  66. 49 49
      FreeAPS/Sources/Modules/ContactTrick/ContactTrickStateModel.swift
  67. 42 35
      FreeAPS/Sources/Modules/ContactTrick/View/AddContactTrickSheet.swift
  68. 50 41
      FreeAPS/Sources/Modules/ContactTrick/View/ContactTrickDetailView.swift
  69. 10 10
      FreeAPS/Sources/Modules/ContactTrick/View/ContactTrickRootView.swift
  70. 0 8
      FreeAPS/Sources/Modules/ContactTrick/ContactTrickDataFlow.swift
  71. 0 6
      FreeAPS/Sources/Modules/ContactTrick/ContactTrickProvider.swift
  72. 1 1
      FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift
  73. 154 33
      FreeAPS/Sources/Modules/DynamicSettings/View/DynamicSettingsRootView.swift
  74. 1 1
      FreeAPS/Sources/Modules/GeneralSettings/UnitsLimitsSettingsProvider.swift
  75. 57 20
      FreeAPS/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift
  76. 89 32
      FreeAPS/Sources/Modules/GlucoseNotificationSettings/View/GlucoseNotificationSettingsRootView.swift
  77. 21 16
      FreeAPS/Sources/Modules/HealthKit/View/AppleHealthKitRootView.swift
  78. 1 1
      FreeAPS/Sources/Modules/Home/HomeProvider.swift
  79. 3 3
      FreeAPS/Sources/Modules/Home/HomeStateModel.swift
  80. 100 37
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  81. 48 8
      FreeAPS/Sources/Modules/LiveActivitySettings/View/LiveActivitySettingsRootView.swift
  82. 121 30
      FreeAPS/Sources/Modules/MealSettings/View/MealSettingsRootView.swift
  83. 1 1
      FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigProvider.swift
  84. 36 14
      FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift
  85. 2 1
      FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConnectView.swift
  86. 24 9
      FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutFetchView.swift
  87. 26 9
      FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutUploadView.swift
  88. 12 5
      FreeAPS/Sources/Modules/NightscoutConfig/View/ProfileImport/ReviewInsulinActionView.swift
  89. 1 0
      FreeAPS/Sources/Modules/PumpConfig/PumpConfigDataFlow.swift
  90. 1 1
      FreeAPS/Sources/Modules/PumpConfig/PumpConfigProvider.swift
  91. 21 5
      FreeAPS/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift
  92. 10 0
      FreeAPS/Sources/Modules/PumpConfig/View/PumpSetupView.swift
  93. 15 6
      FreeAPS/Sources/Modules/RemoteControlConfig/View/RemoteControlConfig.swift
  94. 189 78
      FreeAPS/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift
  95. 10 8
      FreeAPS/Sources/Modules/Settings/SettingItems.swift
  96. 46 39
      FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift
  97. 1 1
      FreeAPS/Sources/Modules/Settings/View/Subviews/AlgorithmSettings.swift
  98. 6 6
      FreeAPS/Sources/Modules/Settings/View/Subviews/FeatureSettingsView.swift
  99. 25 12
      FreeAPS/Sources/Modules/Settings/View/Subviews/NotificationsView.swift
  100. 0 0
      FreeAPS/Sources/Modules/Settings/View/Subviews/TherapySettingsView.swift

+ 3 - 0
.gitmodules

@@ -38,3 +38,6 @@
 	path = TidepoolService
 	url = https://github.com/loopandlearn/TidepoolService.git
 	branch = trio
+[submodule "DanaKit"]
+	path = DanaKit
+	url = https://github.com/bastiaanv/DanaKit

+ 1 - 0
Config.xcconfig

@@ -8,5 +8,6 @@ APP_ICON = trioBlack
 APP_URL_SCHEME = Trio
 
 // Optional overrides
+#include? "../../ConfigOverride.xcconfig"
 #include? "../ConfigOverride.xcconfig"
 #include? "ConfigOverride.xcconfig"

+ 1 - 0
DanaKit

@@ -0,0 +1 @@
+Subproject commit b07f236677b205d31d7ecf6144970348e8d5a3fe

+ 62 - 49
FreeAPS.xcodeproj/project.pbxproj

@@ -342,11 +342,11 @@
 		BDBAACFA2C2D439700370AAE /* OverrideData.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBAACF92C2D439700370AAE /* OverrideData.swift */; };
 		BDC2EA452C3043B000E5BBD0 /* OverrideStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC2EA442C3043B000E5BBD0 /* OverrideStorage.swift */; };
 		BDC2EA472C3045AD00E5BBD0 /* Override.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC2EA462C3045AD00E5BBD0 /* Override.swift */; };
-		BDC530FF2D0F6BE300088832 /* ContactTrickManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC530FE2D0F6BE300088832 /* ContactTrickManager.swift */; };
-		BDC531122D1060FA00088832 /* ContactTrickDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC531112D1060FA00088832 /* ContactTrickDetailView.swift */; };
-		BDC531142D10611D00088832 /* AddContactTrickSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC531132D10611D00088832 /* AddContactTrickSheet.swift */; };
-		BDC531162D10629000088832 /* ContactTrickPicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC531152D10629000088832 /* ContactTrickPicture.swift */; };
-		BDC531182D1062F200088832 /* ContactTrickState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC531172D1062F200088832 /* ContactTrickState.swift */; };
+		BDC530FF2D0F6BE300088832 /* ContactImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC530FE2D0F6BE300088832 /* ContactImageManager.swift */; };
+		BDC531122D1060FA00088832 /* ContactImageDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC531112D1060FA00088832 /* ContactImageDetailView.swift */; };
+		BDC531142D10611D00088832 /* AddContactImageSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC531132D10611D00088832 /* AddContactImageSheet.swift */; };
+		BDC531162D10629000088832 /* ContactPicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC531152D10629000088832 /* ContactPicture.swift */; };
+		BDC531182D1062F200088832 /* ContactImageState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC531172D1062F200088832 /* ContactImageState.swift */; };
 		BDCAF2382C639F35002DC907 /* SettingItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCAF2372C639F35002DC907 /* SettingItems.swift */; };
 		BDCD47AF2C1F3F1700F8BCD5 /* OverrideStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCD47AE2C1F3F1700F8BCD5 /* OverrideStored+helper.swift */; };
 		BDDAF9EF2D00554500B34E7A /* SelectionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDDAF9EE2D00553E00B34E7A /* SelectionPopoverView.swift */; };
@@ -359,6 +359,7 @@
 		BDF530D82B40F8AC002CAF43 /* LockScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF530D72B40F8AC002CAF43 /* LockScreenView.swift */; };
 		BDFD165A2AE40438007F0DDA /* TreatmentsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFD16592AE40438007F0DDA /* TreatmentsRootView.swift */; };
 		BF1667ADE69E4B5B111CECAE /* ManualTempBasalProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680C4420C9A345D46D90D06C /* ManualTempBasalProvider.swift */; };
+		C2A0A42F2CE03131003B98E8 /* ConstantValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */; };
 		C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */; };
 		CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */; };
 		CC6C406E2ACDD69E009B8058 /* RawFetchedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */; };
@@ -423,6 +424,7 @@
 		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 */; };
 		DD09D47D2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD09D47C2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift */; };
 		DD09D47F2C5986E5003FEA5D /* CalendarEventSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD09D47E2C5986E5003FEA5D /* CalendarEventSettingsStateModel.swift */; };
@@ -484,7 +486,7 @@
 		DD9ECB712CA9A0BA00AA7C45 /* RemoteControlConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB6E2CA9A0BA00AA7C45 /* RemoteControlConfigProvider.swift */; };
 		DD9ECB722CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB6F2CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift */; };
 		DD9ECB742CA9A0C300AA7C45 /* RemoteControlConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */; };
-		DDB37CC52D05048F00D99BF4 /* ContactTrickStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC42D05048F00D99BF4 /* ContactTrickStorage.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 */; };
 		DDD163122C4C689900CD525A /* AdjustmentsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163112C4C689900CD525A /* AdjustmentsStateModel.swift */; };
@@ -494,6 +496,7 @@
 		DDD1631A2C4C695E00CD525A /* EditOverrideForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163192C4C695E00CD525A /* EditOverrideForm.swift */; };
 		DDD1631C2C4C697400CD525A /* AddOverrideForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD1631B2C4C697400CD525A /* AddOverrideForm.swift */; };
 		DDD1631F2C4C6F6900CD525A /* TrioCoreDataPersistentContainer.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DDD1631D2C4C6F6900CD525A /* TrioCoreDataPersistentContainer.xcdatamodeld */; };
+		DDD6D4D32CDE90720029439A /* HbA1cDisplayUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD6D4D22CDE90720029439A /* HbA1cDisplayUnit.swift */; };
 		DDE179522C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179322C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift */; };
 		DDE179532C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179332C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift */; };
 		DDE179542C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE179342C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift */; };
@@ -543,10 +546,10 @@
 		E39E418C56A5A46B61D960EE /* ConfigEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5B4F8B4194BB7E260EF251 /* ConfigEditorStateModel.swift */; };
 		E3A08AAE59538BC8A8ABE477 /* GlucoseNotificationSettingsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3260468377DA9DB4DEE9AF6D /* GlucoseNotificationSettingsDataFlow.swift */; };
 		E592A3702CEEC01E009A472C /* ContactTrickEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A36F2CEEC01E009A472C /* ContactTrickEntry.swift */; };
-		E592A3772CEEC038009A472C /* ContactTrickStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3752CEEC038009A472C /* ContactTrickStateModel.swift */; };
-		E592A3782CEEC038009A472C /* ContactTrickDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3732CEEC038009A472C /* ContactTrickDataFlow.swift */; };
-		E592A3792CEEC038009A472C /* ContactTrickRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3712CEEC038009A472C /* ContactTrickRootView.swift */; };
-		E592A37A2CEEC038009A472C /* ContactTrickProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3742CEEC038009A472C /* ContactTrickProvider.swift */; };
+		E592A3772CEEC038009A472C /* ContactImageStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3752CEEC038009A472C /* ContactImageStateModel.swift */; };
+		E592A3782CEEC038009A472C /* ContactImageDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3732CEEC038009A472C /* ContactImageDataFlow.swift */; };
+		E592A3792CEEC038009A472C /* ContactImageRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3712CEEC038009A472C /* ContactImageRootView.swift */; };
+		E592A37A2CEEC038009A472C /* ContactImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3742CEEC038009A472C /* ContactImageProvider.swift */; };
 		E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD22C985B79A2F0D2EA3D9D /* PumpConfigRootView.swift */; };
 		F5CA3DB1F9DC8B05792BBFAA /* CGMDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B5C0607505A38F256BF99A /* CGMDataFlow.swift */; };
 		F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */; };
@@ -1042,11 +1045,11 @@
 		BDBAACF92C2D439700370AAE /* OverrideData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideData.swift; sourceTree = "<group>"; };
 		BDC2EA442C3043B000E5BBD0 /* OverrideStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideStorage.swift; sourceTree = "<group>"; };
 		BDC2EA462C3045AD00E5BBD0 /* Override.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Override.swift; sourceTree = "<group>"; };
-		BDC530FE2D0F6BE300088832 /* ContactTrickManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickManager.swift; sourceTree = "<group>"; };
-		BDC531112D1060FA00088832 /* ContactTrickDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickDetailView.swift; sourceTree = "<group>"; };
-		BDC531132D10611D00088832 /* AddContactTrickSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactTrickSheet.swift; sourceTree = "<group>"; };
-		BDC531152D10629000088832 /* ContactTrickPicture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickPicture.swift; sourceTree = "<group>"; };
-		BDC531172D1062F200088832 /* ContactTrickState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickState.swift; sourceTree = "<group>"; };
+		BDC530FE2D0F6BE300088832 /* ContactImageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageManager.swift; sourceTree = "<group>"; };
+		BDC531112D1060FA00088832 /* ContactImageDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageDetailView.swift; sourceTree = "<group>"; };
+		BDC531132D10611D00088832 /* AddContactImageSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactImageSheet.swift; sourceTree = "<group>"; };
+		BDC531152D10629000088832 /* ContactPicture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPicture.swift; sourceTree = "<group>"; };
+		BDC531172D1062F200088832 /* ContactImageState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageState.swift; sourceTree = "<group>"; };
 		BDCAF2372C639F35002DC907 /* SettingItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingItems.swift; sourceTree = "<group>"; };
 		BDCD47AE2C1F3F1700F8BCD5 /* OverrideStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideStored+helper.swift"; sourceTree = "<group>"; };
 		BDDAF9EE2D00553E00B34E7A /* SelectionPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionPopoverView.swift; sourceTree = "<group>"; };
@@ -1059,6 +1062,8 @@
 		BDF530D72B40F8AC002CAF43 /* LockScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenView.swift; sourceTree = "<group>"; };
 		BDFD16592AE40438007F0DDA /* TreatmentsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreatmentsRootView.swift; sourceTree = "<group>"; };
 		BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorRootView.swift; sourceTree = "<group>"; };
+		C19984D62EFC0035A9E9644D /* BolusProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusProvider.swift; sourceTree = "<group>"; };
+		C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantValues.swift; sourceTree = "<group>"; };
 		C19984D62EFC0035A9E9644D /* TreatmentsProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TreatmentsProvider.swift; sourceTree = "<group>"; };
 		C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalRootView.swift; sourceTree = "<group>"; };
 		C8D1A7CA8C10C4403D4BBFA7 /* TreatmentsDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TreatmentsDataFlow.swift; sourceTree = "<group>"; };
@@ -1125,6 +1130,7 @@
 		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>"; };
 		DD09D47C2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarEventSettingsProvider.swift; sourceTree = "<group>"; };
 		DD09D47E2C5986E5003FEA5D /* CalendarEventSettingsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarEventSettingsStateModel.swift; sourceTree = "<group>"; };
@@ -1188,7 +1194,7 @@
 		DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfig.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 /* ContactTrickStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickStorage.swift; sourceTree = "<group>"; };
+		DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageStorage.swift; sourceTree = "<group>"; };
 		DDB37CC62D05127500D99BF4 /* FontExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontExtensions.swift; sourceTree = "<group>"; };
 		DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LiveActivity+Helper.swift"; sourceTree = "<group>"; };
 		DDD163112C4C689900CD525A /* AdjustmentsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdjustmentsStateModel.swift; sourceTree = "<group>"; };
@@ -1198,6 +1204,7 @@
 		DDD163192C4C695E00CD525A /* EditOverrideForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditOverrideForm.swift; sourceTree = "<group>"; };
 		DDD1631B2C4C697400CD525A /* AddOverrideForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddOverrideForm.swift; sourceTree = "<group>"; };
 		DDD1631E2C4C6F6900CD525A /* TrioCoreDataPersistentContainer.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = TrioCoreDataPersistentContainer.xcdatamodel; sourceTree = "<group>"; };
+		DDD6D4D22CDE90720029439A /* HbA1cDisplayUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HbA1cDisplayUnit.swift; sourceTree = "<group>"; };
 		DDE179322C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MealPresetStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDE179332C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MealPresetStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDE179342C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoopStatRecord+CoreDataClass.swift"; sourceTree = "<group>"; };
@@ -1245,10 +1252,10 @@
 		E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitSample.swift; sourceTree = "<group>"; };
 		E26904AACA8D9C15D229D675 /* SnoozeStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeStateModel.swift; sourceTree = "<group>"; };
 		E592A36F2CEEC01E009A472C /* ContactTrickEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickEntry.swift; sourceTree = "<group>"; };
-		E592A3712CEEC038009A472C /* ContactTrickRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickRootView.swift; sourceTree = "<group>"; };
-		E592A3732CEEC038009A472C /* ContactTrickDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickDataFlow.swift; sourceTree = "<group>"; };
-		E592A3742CEEC038009A472C /* ContactTrickProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickProvider.swift; sourceTree = "<group>"; };
-		E592A3752CEEC038009A472C /* ContactTrickStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickStateModel.swift; sourceTree = "<group>"; };
+		E592A3712CEEC038009A472C /* ContactImageRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageRootView.swift; sourceTree = "<group>"; };
+		E592A3732CEEC038009A472C /* ContactImageDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageDataFlow.swift; sourceTree = "<group>"; };
+		E592A3742CEEC038009A472C /* ContactImageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageProvider.swift; sourceTree = "<group>"; };
+		E592A3752CEEC038009A472C /* ContactImageStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageStateModel.swift; sourceTree = "<group>"; };
 		E625985B47742D498CB1681A /* GlucoseNotificationSettingsProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationSettingsProvider.swift; sourceTree = "<group>"; };
 		F816825D28DB441200054060 /* HeartBeatManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeartBeatManager.swift; sourceTree = "<group>"; };
 		F816825F28DB441800054060 /* BluetoothTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothTransmitter.swift; sourceTree = "<group>"; };
@@ -1560,7 +1567,7 @@
 				E42231DBF0DBE2B4B92D1B15 /* CarbRatioEditor */,
 				F75CB57ED6971B46F8756083 /* CGM */,
 				0610F7D6D2EC00E3BA1569F0 /* ConfigEditor */,
-				E592A3762CEEC038009A472C /* ContactTrick */,
+				E592A3762CEEC038009A472C /* ContactImage */,
 				9E56E3626FAD933385101B76 /* DataTable */,
 				195D80B22AF696EE00D25097 /* DynamicSettings */,
 				DD17454C2C55CA0200211FAC /* GeneralSettings */,
@@ -1700,7 +1707,7 @@
 				3811DE9225C9D88200A708ED /* Appearance */,
 				CEB434E128B8F9BC00B70274 /* Bluetooth */,
 				3862CC2C2743F9DC00BF832C /* Calendar */,
-				E592A37E2CEEC046009A472C /* ContactTrick */,
+				E592A37E2CEEC046009A472C /* ContactImage */,
 				F90692A8274B7A980037068D /* HealthKit */,
 				6B1A8D2C2B156EC100E76752 /* LiveActivity */,
 				3811DE9425C9D88200A708ED /* Network */,
@@ -1990,6 +1997,7 @@
 		388E5A5925B6F0250019842D /* Models */ = {
 			isa = PBXGroup;
 			children = (
+				DD07CA132CE80B73002D45A9 /* TimeInRangeChartStyle.swift */,
 				DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */,
 				DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */,
 				385CEAC025F2EA52002D6D5B /* Announcement.swift */,
@@ -2037,6 +2045,7 @@
 				DD6B7CB32C7B71F700B75029 /* ForecastDisplayType.swift */,
 				DD6B7CB52C7B748B00B75029 /* TotalInsulinDisplayType.swift */,
 				DD9ECB692CA99F6C00AA7C45 /* PushMessage.swift */,
+				DDD6D4D22CDE90720029439A /* HbA1cDisplayUnit.swift */,
 			);
 			path = Models;
 			sourceTree = "<group>";
@@ -2044,6 +2053,7 @@
 		388E5A5A25B6F05F0019842D /* Helpers */ = {
 			isa = PBXGroup;
 			children = (
+				C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */,
 				DD940BAB2CA75889000830A5 /* DynamicGlucoseColor.swift */,
 				38F37827261260DC009DB701 /* Color+Extensions.swift */,
 				389ECE042601144100D86C4F /* ConcurrentMap.swift */,
@@ -2083,15 +2093,15 @@
 		38A0362725ECF05300FCBB52 /* Storage */ = {
 			isa = PBXGroup;
 			children = (
-				DDB37CC42D05048F00D99BF4 /* ContactTrickStorage.swift */,
+				CE82E02428E867BA00473A9C /* AlertStorage.swift */,
 				385CEAC325F2F154002D6D5B /* AnnouncementsStorage.swift */,
 				38AEE75625F0F18E0013F05B /* CarbsStorage.swift */,
+				DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */,
+				5864E8582C42CFAE00294306 /* DeterminationStorage.swift */,
 				38A0363A25ECF07E00FCBB52 /* GlucoseStorage.swift */,
+				BDC2EA442C3043B000E5BBD0 /* OverrideStorage.swift */,
 				38FCF3FC25E997A80078B0D1 /* PumpHistoryStorage.swift */,
 				38F3B2EE25ED8E2A005C48AA /* TempTargetsStorage.swift */,
-				CE82E02428E867BA00473A9C /* AlertStorage.swift */,
-				BDC2EA442C3043B000E5BBD0 /* OverrideStorage.swift */,
-				5864E8582C42CFAE00294306 /* DeterminationStorage.swift */,
 			);
 			path = Storage;
 			sourceTree = "<group>";
@@ -2994,32 +3004,32 @@
 		E592A3722CEEC038009A472C /* View */ = {
 			isa = PBXGroup;
 			children = (
-				E592A3712CEEC038009A472C /* ContactTrickRootView.swift */,
-				BDC531112D1060FA00088832 /* ContactTrickDetailView.swift */,
-				BDC531132D10611D00088832 /* AddContactTrickSheet.swift */,
+				E592A3712CEEC038009A472C /* ContactImageRootView.swift */,
+				BDC531112D1060FA00088832 /* ContactImageDetailView.swift */,
+				BDC531132D10611D00088832 /* AddContactImageSheet.swift */,
 			);
 			path = View;
 			sourceTree = "<group>";
 		};
-		E592A3762CEEC038009A472C /* ContactTrick */ = {
+		E592A3762CEEC038009A472C /* ContactImage */ = {
 			isa = PBXGroup;
 			children = (
 				E592A3722CEEC038009A472C /* View */,
-				E592A3732CEEC038009A472C /* ContactTrickDataFlow.swift */,
-				E592A3742CEEC038009A472C /* ContactTrickProvider.swift */,
-				E592A3752CEEC038009A472C /* ContactTrickStateModel.swift */,
+				E592A3732CEEC038009A472C /* ContactImageDataFlow.swift */,
+				E592A3742CEEC038009A472C /* ContactImageProvider.swift */,
+				E592A3752CEEC038009A472C /* ContactImageStateModel.swift */,
 			);
-			path = ContactTrick;
+			path = ContactImage;
 			sourceTree = "<group>";
 		};
-		E592A37E2CEEC046009A472C /* ContactTrick */ = {
+		E592A37E2CEEC046009A472C /* ContactImage */ = {
 			isa = PBXGroup;
 			children = (
-				BDC530FE2D0F6BE300088832 /* ContactTrickManager.swift */,
-				BDC531152D10629000088832 /* ContactTrickPicture.swift */,
-				BDC531172D1062F200088832 /* ContactTrickState.swift */,
+				BDC530FE2D0F6BE300088832 /* ContactImageManager.swift */,
+				BDC531152D10629000088832 /* ContactPicture.swift */,
+				BDC531172D1062F200088832 /* ContactImageState.swift */,
 			);
-			path = ContactTrick;
+			path = ContactImage;
 			sourceTree = "<group>";
 		};
 		EEC747824D6593B5CD87E195 /* View */ = {
@@ -3411,6 +3421,7 @@
 			files = (
 				DD5DC9F12CF3D97C00AB8703 /* AdjustmentsStateModel+Overrides.swift in Sources */,
 				3811DE2325C9D48300A708ED /* MainDataFlow.swift in Sources */,
+				C2A0A42F2CE03131003B98E8 /* ConstantValues.swift in Sources */,
 				BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */,
 				3811DEEB25CA063400A708ED /* PersistedProperty.swift in Sources */,
 				38E44537274E411700EC9A94 /* Disk+Helpers.swift in Sources */,
@@ -3441,6 +3452,7 @@
 				382C134B25F14E3700715CE1 /* BGTargets.swift in Sources */,
 				38AEE75725F0F18E0013F05B /* CarbsStorage.swift in Sources */,
 				38B4F3CA25E502E200E76A18 /* SwiftNotificationCenter.swift in Sources */,
+				DD07CA142CE80B73002D45A9 /* TimeInRangeChartStyle.swift in Sources */,
 				38AEE75225F022080013F05B /* SettingsManager.swift in Sources */,
 				3894873A2614928B004DF424 /* DispatchTimer.swift in Sources */,
 				3895E4C625B9E00D00214B37 /* Preferences.swift in Sources */,
@@ -3541,6 +3553,7 @@
 				DD1745482C55C61D00211FAC /* AutosensSettingsStateModel.swift in Sources */,
 				DD1745462C55C61500211FAC /* AutosensSettingsProvider.swift in Sources */,
 				3811DEAF25C9D88300A708ED /* KeyValueStorage.swift in Sources */,
+				DDD6D4D32CDE90720029439A /* HbA1cDisplayUnit.swift in Sources */,
 				38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */,
 				388358C825EEF6D200E024B2 /* BasalProfileEntry.swift in Sources */,
 				3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */,
@@ -3725,7 +3738,7 @@
 				6632A0DC746872439A858B44 /* ISFEditorDataFlow.swift in Sources */,
 				DD17454B2C55C62800211FAC /* AutosensSettingsRootView.swift in Sources */,
 				DDF847DF2C5C28780049BB3B /* LiveActivitySettingsProvider.swift in Sources */,
-				DDB37CC52D05048F00D99BF4 /* ContactTrickStorage.swift in Sources */,
+				DDB37CC52D05048F00D99BF4 /* ContactImageStorage.swift in Sources */,
 				DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */,
 				BDF34EBE2C0A31D100D51995 /* CustomNotification.swift in Sources */,
 				BDC2EA472C3045AD00E5BBD0 /* Override.swift in Sources */,
@@ -3778,11 +3791,11 @@
 				DD5DC9F92CF3DAA900AB8703 /* RadioButton.swift in Sources */,
 				38E44522274E3DDC00EC9A94 /* NetworkReachabilityManager.swift in Sources */,
 				CE7CA34F2A064973004BE681 /* BaseIntentsRequest.swift in Sources */,
-				E592A3772CEEC038009A472C /* ContactTrickStateModel.swift in Sources */,
-				E592A3782CEEC038009A472C /* ContactTrickDataFlow.swift in Sources */,
-				E592A3792CEEC038009A472C /* ContactTrickRootView.swift in Sources */,
-				BDC531182D1062F200088832 /* ContactTrickState.swift in Sources */,
-				E592A37A2CEEC038009A472C /* ContactTrickProvider.swift in Sources */,
+				E592A3772CEEC038009A472C /* ContactImageStateModel.swift in Sources */,
+				E592A3782CEEC038009A472C /* ContactImageDataFlow.swift in Sources */,
+				E592A3792CEEC038009A472C /* ContactImageRootView.swift in Sources */,
+				BDC531182D1062F200088832 /* ContactImageState.swift in Sources */,
+				E592A37A2CEEC038009A472C /* ContactImageProvider.swift in Sources */,
 				CE82E02728E869DF00473A9C /* AlertEntry.swift in Sources */,
 				38E4451E274DB04600EC9A94 /* AppDelegate.swift in Sources */,
 				BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */,
@@ -3801,7 +3814,7 @@
 				69A31254F2451C20361D172F /* TreatmentsStateModel.swift in Sources */,
 				1967DFC029D053AC00759F30 /* IconSelection.swift in Sources */,
 				19D4E4EB29FC6A9F00351451 /* Charts.swift in Sources */,
-				BDC531162D10629000088832 /* ContactTrickPicture.swift in Sources */,
+				BDC531162D10629000088832 /* ContactPicture.swift in Sources */,
 				FEFFA7A22929FE49007B8193 /* UIDevice+Extensions.swift in Sources */,
 				F90692D3274B9A130037068D /* AppleHealthKitRootView.swift in Sources */,
 				BDF34F852C10C62E00D51995 /* GlucoseData.swift in Sources */,
@@ -3859,8 +3872,8 @@
 				DDE179522C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift in Sources */,
 				DDE179532C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift in Sources */,
 				DDE179542C910127003CDDB7 /* LoopStatRecord+CoreDataClass.swift in Sources */,
-				BDC530FF2D0F6BE300088832 /* ContactTrickManager.swift in Sources */,
-				BDC531122D1060FA00088832 /* ContactTrickDetailView.swift in Sources */,
+				BDC530FF2D0F6BE300088832 /* ContactImageManager.swift in Sources */,
+				BDC531122D1060FA00088832 /* ContactImageDetailView.swift in Sources */,
 				DDE179552C910127003CDDB7 /* LoopStatRecord+CoreDataProperties.swift in Sources */,
 				DDE179562C910127003CDDB7 /* BolusStored+CoreDataClass.swift in Sources */,
 				DDE179572C910127003CDDB7 /* BolusStored+CoreDataProperties.swift in Sources */,
@@ -3877,7 +3890,7 @@
 				DDE179632C910127003CDDB7 /* Forecast+CoreDataProperties.swift in Sources */,
 				DDE179642C910127003CDDB7 /* GlucoseStored+CoreDataClass.swift in Sources */,
 				DDE179652C910127003CDDB7 /* GlucoseStored+CoreDataProperties.swift in Sources */,
-				BDC531142D10611D00088832 /* AddContactTrickSheet.swift in Sources */,
+				BDC531142D10611D00088832 /* AddContactImageSheet.swift in Sources */,
 				DDE179662C910127003CDDB7 /* OpenAPS_Battery+CoreDataClass.swift in Sources */,
 				DDE179672C910127003CDDB7 /* OpenAPS_Battery+CoreDataProperties.swift in Sources */,
 				DDE179682C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift in Sources */,

+ 14 - 0
FreeAPS.xcodeproj/xcshareddata/xcschemes/Trio.xcscheme

@@ -168,6 +168,20 @@
             buildForAnalyzing = "YES">
             <BuildableReference
                BuildableIdentifier = "primary"
+               BlueprintIdentifier = "3E6007862D0C5D0C00B186D1"
+               BuildableName = "DanaKitPlugin.loopplugin"
+               BlueprintName = "DanaKitPlugin"
+               ReferencedContainer = "container:DanaKit/DanaKit.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
                BlueprintIdentifier = "C17F511C291EACCD00555EB5"
                BuildableName = "G7SensorPlugin.loopplugin"
                BlueprintName = "G7SensorPlugin"

+ 0 - 23
FreeAPS/Resources/Assets.xcassets/app_icon_images/trioLoop.imageset/Contents.json

@@ -1,23 +0,0 @@
-{
-  "images" : [
-    {
-      "filename" : "imageLoop 1.png",
-      "idiom" : "universal",
-      "scale" : "1x"
-    },
-    {
-      "filename" : "imageLoop 2.png",
-      "idiom" : "universal",
-      "scale" : "2x"
-    },
-    {
-      "filename" : "imageLoop 3.png",
-      "idiom" : "universal",
-      "scale" : "3x"
-    }
-  ],
-  "info" : {
-    "author" : "xcode",
-    "version" : 1
-  }
-}

BIN
FreeAPS/Resources/Assets.xcassets/app_icon_images/trioLoop.imageset/imageLoop 1.png


BIN
FreeAPS/Resources/Assets.xcassets/app_icon_images/trioLoop.imageset/imageLoop 2.png


BIN
FreeAPS/Resources/Assets.xcassets/app_icon_images/trioLoop.imageset/imageLoop 3.png


+ 0 - 20
FreeAPS/Resources/Assets.xcassets/app_icons/trioLoop.appiconset/Contents.json

@@ -1,20 +0,0 @@
-{
-  "images" : [
-    {
-      "filename" : "trioLoop.png",
-      "idiom" : "universal",
-      "platform" : "ios",
-      "size" : "1024x1024"
-    },
-    {
-      "filename" : "trioLoop watch.png",
-      "idiom" : "universal",
-      "platform" : "watchos",
-      "size" : "1024x1024"
-    }
-  ],
-  "info" : {
-    "author" : "xcode",
-    "version" : 1
-  }
-}

BIN
FreeAPS/Resources/Assets.xcassets/app_icons/trioLoop.appiconset/trioLoop watch.png


BIN
FreeAPS/Resources/Assets.xcassets/app_icons/trioLoop.appiconset/trioLoop.png


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Resources/javascript/bundle/determine-basal.js


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

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

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

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

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

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

+ 16 - 17
FreeAPS/Sources/APS/APSManager.swift

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

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

@@ -1,6 +1,7 @@
 import Algorithms
 import Combine
 import CoreData
+import DanaKit
 import Foundation
 import LoopKit
 import LoopKitUI
@@ -34,6 +35,7 @@ private let staticPumpManagers: [PumpManagerUI.Type] = [
     MinimedPumpManager.self,
     OmnipodPumpManager.self,
     OmniBLEPumpManager.self,
+    DanaKitPumpManager.self,
     MockPumpManager.self
 ]
 
@@ -41,6 +43,7 @@ private let staticPumpManagersByIdentifier: [String: PumpManagerUI.Type] = [
     MinimedPumpManager.pluginIdentifier: MinimedPumpManager.self,
     OmnipodPumpManager.pluginIdentifier: OmnipodPumpManager.self,
     OmniBLEPumpManager.pluginIdentifier: OmniBLEPumpManager.self,
+    DanaKitPumpManager.pluginIdentifier: DanaKitPumpManager.self,
     MockPumpManager.pluginIdentifier: MockPumpManager.self
 ]
 

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

@@ -39,7 +39,6 @@ extension OpenAPS {
         static let carbRatios = "settings/carb_ratios.json"
         static let tempTargets = "settings/temptargets.json"
         static let model = "settings/model.json"
-        static let contactTrick = "settings/contact_trick.json"
     }
 
     enum Monitor {

+ 150 - 0
FreeAPS/Sources/APS/Storage/ContactImageStorage.swift

@@ -0,0 +1,150 @@
+import CoreData
+import Foundation
+import SwiftUI
+import Swinject
+
+protocol ContactImageStorage {
+    func fetchContactImageEntries() async -> [ContactImageEntry]
+    func storeContactImageEntry(_ entry: ContactImageEntry) async
+    func updateContactImageEntry(_ contactImageEntry: ContactImageEntry) async
+    func deleteContactImageEntry(_ objectID: NSManagedObjectID) async
+}
+
+final class BaseContactImageStorage: ContactImageStorage, Injectable {
+    @Injected() private var settingsManager: SettingsManager!
+
+    private let backgroundContext = CoreDataStack.shared.newTaskContext()
+
+    init(resolver: Resolver) {
+        injectServices(resolver)
+    }
+
+    /// Fetches all stored Contact Trick entries.
+    ///
+    /// The method retrieves `ContactImageEntryStored` objects from Core Data, maps them to
+    /// `ContactImageEntry` objects, and returns the results.
+    ///
+    /// - Returns: An array of `ContactImageEntry` objects.
+    func fetchContactImageEntries() async -> [ContactImageEntry] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: ContactImageEntryStored.self,
+            onContext: backgroundContext,
+            predicate: NSPredicate.all,
+            key: "hasHighContrast",
+            ascending: false
+        )
+
+        return await backgroundContext.perform {
+            guard let fetchedContactImageEntries = results as? [ContactImageEntryStored] else { return [] }
+
+            return fetchedContactImageEntries.compactMap { entry in
+                ContactImageEntry(
+                    name: entry.name ?? "No name provided",
+                    layout: ContactImageLayout(rawValue: entry.layout ?? "Default") ?? .default,
+                    ring: ContactImageLargeRing(rawValue: entry.ring ?? "Hidden") ?? .none,
+                    primary: ContactImageValue(rawValue: entry.primary ?? "Glucose Reading") ?? .glucose,
+                    top: ContactImageValue(rawValue: entry.top ?? "None") ?? .none,
+                    bottom: ContactImageValue(rawValue: entry.bottom ?? "None") ?? .none,
+                    contactId: entry.contactId?.string,
+                    hasHighContrast: entry.hasHighContrast,
+                    ringWidth: ContactImageEntry.RingWidth(rawValue: Int(entry.ringWidth)) ?? .regular,
+                    ringGap: ContactImageEntry.RingGap(rawValue: Int(entry.ringGap)) ?? .small,
+                    fontSize: ContactImageEntry.FontSize(rawValue: Int(entry.fontSize)) ?? .regular,
+                    secondaryFontSize: ContactImageEntry.FontSize(rawValue: Int(entry.fontSizeSecondary)) ?? .small,
+                    fontWeight: Font.Weight.fromString(entry.fontWeight ?? "regular"),
+                    fontWidth: Font.Width.fromString(entry.fontWidth ?? "standard"),
+                    managedObjectID: entry.objectID
+                )
+            }
+        }
+    }
+
+    /// Stores a new Contact Trick entry.
+    ///
+    /// This method creates a new `ContactImageEntryStored` object in the background context,
+    /// populates its properties with the values from the provided `ContactImageEntry`, and
+    /// saves the context if changes exist.
+    ///
+    /// - Parameter contactImageEntry: The `ContactImageEntry` object to be stored.
+    func storeContactImageEntry(_ contactImageEntry: ContactImageEntry) async {
+        await backgroundContext.perform {
+            let newContactImageEntry = ContactImageEntryStored(context: self.backgroundContext)
+
+            newContactImageEntry.id = UUID()
+            newContactImageEntry.name = contactImageEntry.name
+            newContactImageEntry.contactId = contactImageEntry.contactId
+            newContactImageEntry.layout = contactImageEntry.layout.rawValue
+            newContactImageEntry.ring = contactImageEntry.ring.rawValue
+            newContactImageEntry.primary = contactImageEntry.primary.rawValue
+            newContactImageEntry.top = contactImageEntry.top.rawValue
+            newContactImageEntry.bottom = contactImageEntry.bottom.rawValue
+            newContactImageEntry.hasHighContrast = contactImageEntry.hasHighContrast
+            newContactImageEntry.ringWidth = Int16(contactImageEntry.ringWidth.rawValue)
+            newContactImageEntry.ringGap = Int16(contactImageEntry.ringGap.rawValue)
+            newContactImageEntry.fontSize = Int16(contactImageEntry.fontSize.rawValue)
+            newContactImageEntry.fontSizeSecondary = Int16(contactImageEntry.secondaryFontSize.rawValue)
+            newContactImageEntry.fontWidth = contactImageEntry.fontWeight.asString
+            newContactImageEntry.fontWeight = contactImageEntry.fontWidth.asString
+
+            do {
+                guard self.backgroundContext.hasChanges else { return }
+                try self.backgroundContext.save()
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Contact Trick Entry to Core Data with error: \(error.userInfo)"
+                )
+            }
+        }
+    }
+
+    /// Updates an existing Contact Trick entry in Core Data.
+    ///
+    /// This method finds the existing `ContactImageEntryStored` object by its `contactId` and updates
+    /// its properties with the values from the provided `ContactImageEntry`. If no matching entry exists,
+    /// it does nothing.
+    ///
+    /// - Parameter contactImageEntry: The `ContactImageEntry` object with updated values.
+    func updateContactImageEntry(_ contactImageEntry: ContactImageEntry) async {
+        await backgroundContext.perform {
+            let fetchRequest: NSFetchRequest<ContactImageEntryStored> = ContactImageEntryStored.fetchRequest()
+            fetchRequest.predicate = NSPredicate(format: "contactId == %@", contactImageEntry.contactId ?? "")
+
+            do {
+                if let existingEntry = try self.backgroundContext.fetch(fetchRequest).first {
+                    // Update the properties of the existing entry
+                    existingEntry.name = contactImageEntry.name
+                    existingEntry.layout = contactImageEntry.layout.rawValue
+                    existingEntry.ring = contactImageEntry.ring.rawValue
+                    existingEntry.primary = contactImageEntry.primary.rawValue
+                    existingEntry.top = contactImageEntry.top.rawValue
+                    existingEntry.bottom = contactImageEntry.bottom.rawValue
+                    existingEntry.hasHighContrast = contactImageEntry.hasHighContrast
+                    existingEntry.ringWidth = Int16(contactImageEntry.ringWidth.rawValue)
+                    existingEntry.ringGap = Int16(contactImageEntry.ringGap.rawValue)
+                    existingEntry.fontSize = Int16(contactImageEntry.fontSize.rawValue)
+                    existingEntry.fontSizeSecondary = Int16(contactImageEntry.secondaryFontSize.rawValue)
+                    existingEntry.fontWeight = contactImageEntry.fontWeight.asString
+                    existingEntry.fontWidth = contactImageEntry.fontWidth.asString
+
+                    guard self.backgroundContext.hasChanges else { return }
+                    try self.backgroundContext.save()
+                } else {
+                    debugPrint(
+                        "\(DebuggingIdentifiers.failed) \(#file) \(#function) No matching Contact Trick Entry found to update."
+                    )
+                }
+            } catch let error as NSError {
+                debugPrint(
+                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update Contact Trick Entry with error: \(error.userInfo)"
+                )
+            }
+        }
+    }
+
+    /// Deletes a Contact Trick entry from Core Data.
+    ///
+    /// - Parameter objectID: The `NSManagedObjectID` of the object to delete.
+    func deleteContactImageEntry(_ objectID: NSManagedObjectID) async {
+        await CoreDataStack.shared.deleteObject(identifiedBy: objectID)
+    }
+}

+ 0 - 150
FreeAPS/Sources/APS/Storage/ContactTrickStorage.swift

@@ -1,150 +0,0 @@
-import CoreData
-import Foundation
-import SwiftUI
-import Swinject
-
-protocol ContactTrickStorage {
-    func fetchContactTrickEntries() async -> [ContactTrickEntry]
-    func storeContactTrickEntry(_ entry: ContactTrickEntry) async
-    func updateContactTrickEntry(_ contactTrickEntry: ContactTrickEntry) async
-    func deleteContactTrickEntry(_ objectID: NSManagedObjectID) async
-}
-
-final class BaseContactTrickStorage: ContactTrickStorage, Injectable {
-    @Injected() private var settingsManager: SettingsManager!
-
-    private let backgroundContext = CoreDataStack.shared.newTaskContext()
-
-    init(resolver: Resolver) {
-        injectServices(resolver)
-    }
-
-    /// Fetches all stored Contact Trick entries.
-    ///
-    /// The method retrieves `ContactTrickEntryStored` objects from Core Data, maps them to
-    /// `ContactTrickEntry` objects, and returns the results.
-    ///
-    /// - Returns: An array of `ContactTrickEntry` objects.
-    func fetchContactTrickEntries() async -> [ContactTrickEntry] {
-        let results = await CoreDataStack.shared.fetchEntitiesAsync(
-            ofType: ContactTrickEntryStored.self,
-            onContext: backgroundContext,
-            predicate: NSPredicate.all,
-            key: "hasHighContrast",
-            ascending: false
-        )
-
-        return await backgroundContext.perform {
-            guard let fetchedContactTrickEntries = results as? [ContactTrickEntryStored] else { return [] }
-
-            return fetchedContactTrickEntries.compactMap { entry in
-                ContactTrickEntry(
-                    name: entry.name ?? "No name provided",
-                    layout: ContactTrickLayout(rawValue: entry.layout ?? "Single") ?? .single,
-                    ring: ContactTrickLargeRing(rawValue: entry.ring ?? "Hidden") ?? .none,
-                    primary: ContactTrickValue(rawValue: entry.primary ?? "Glucose Reading") ?? .glucose,
-                    top: ContactTrickValue(rawValue: entry.top ?? "None") ?? .none,
-                    bottom: ContactTrickValue(rawValue: entry.bottom ?? "None") ?? .none,
-                    contactId: entry.contactId?.string,
-                    hasHighContrast: entry.hasHighContrast,
-                    ringWidth: ContactTrickEntry.RingWidth(rawValue: Int(entry.ringWidth)) ?? .regular,
-                    ringGap: ContactTrickEntry.RingGap(rawValue: Int(entry.ringGap)) ?? .small,
-                    fontSize: ContactTrickEntry.FontSize(rawValue: Int(entry.fontSize)) ?? .regular,
-                    secondaryFontSize: ContactTrickEntry.FontSize(rawValue: Int(entry.fontSizeSecondary)) ?? .small,
-                    fontWeight: Font.Weight.fromString(entry.fontWeight ?? "regular"),
-                    fontWidth: Font.Width.fromString(entry.fontWidth ?? "standard"),
-                    managedObjectID: entry.objectID
-                )
-            }
-        }
-    }
-
-    /// Stores a new Contact Trick entry.
-    ///
-    /// This method creates a new `ContactTrickEntryStored` object in the background context,
-    /// populates its properties with the values from the provided `ContactTrickEntry`, and
-    /// saves the context if changes exist.
-    ///
-    /// - Parameter contactTrickEntry: The `ContactTrickEntry` object to be stored.
-    func storeContactTrickEntry(_ contactTrickEntry: ContactTrickEntry) async {
-        await backgroundContext.perform {
-            let newContactTrickEntry = ContactTrickEntryStored(context: self.backgroundContext)
-
-            newContactTrickEntry.id = UUID()
-            newContactTrickEntry.name = contactTrickEntry.name
-            newContactTrickEntry.contactId = contactTrickEntry.contactId
-            newContactTrickEntry.layout = contactTrickEntry.layout.rawValue
-            newContactTrickEntry.ring = contactTrickEntry.ring.rawValue
-            newContactTrickEntry.primary = contactTrickEntry.primary.rawValue
-            newContactTrickEntry.top = contactTrickEntry.top.rawValue
-            newContactTrickEntry.bottom = contactTrickEntry.bottom.rawValue
-            newContactTrickEntry.hasHighContrast = contactTrickEntry.hasHighContrast
-            newContactTrickEntry.ringWidth = Int16(contactTrickEntry.ringWidth.rawValue)
-            newContactTrickEntry.ringGap = Int16(contactTrickEntry.ringGap.rawValue)
-            newContactTrickEntry.fontSize = Int16(contactTrickEntry.fontSize.rawValue)
-            newContactTrickEntry.fontSizeSecondary = Int16(contactTrickEntry.secondaryFontSize.rawValue)
-            newContactTrickEntry.fontWidth = contactTrickEntry.fontWeight.asString
-            newContactTrickEntry.fontWeight = contactTrickEntry.fontWidth.asString
-
-            do {
-                guard self.backgroundContext.hasChanges else { return }
-                try self.backgroundContext.save()
-            } catch let error as NSError {
-                debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Contact Trick Entry to Core Data with error: \(error.userInfo)"
-                )
-            }
-        }
-    }
-
-    /// Updates an existing Contact Trick entry in Core Data.
-    ///
-    /// This method finds the existing `ContactTrickEntryStored` object by its `contactId` and updates
-    /// its properties with the values from the provided `ContactTrickEntry`. If no matching entry exists,
-    /// it does nothing.
-    ///
-    /// - Parameter contactTrickEntry: The `ContactTrickEntry` object with updated values.
-    func updateContactTrickEntry(_ contactTrickEntry: ContactTrickEntry) async {
-        await backgroundContext.perform {
-            let fetchRequest: NSFetchRequest<ContactTrickEntryStored> = ContactTrickEntryStored.fetchRequest()
-            fetchRequest.predicate = NSPredicate(format: "contactId == %@", contactTrickEntry.contactId ?? "")
-
-            do {
-                if let existingEntry = try self.backgroundContext.fetch(fetchRequest).first {
-                    // Update the properties of the existing entry
-                    existingEntry.name = contactTrickEntry.name
-                    existingEntry.layout = contactTrickEntry.layout.rawValue
-                    existingEntry.ring = contactTrickEntry.ring.rawValue
-                    existingEntry.primary = contactTrickEntry.primary.rawValue
-                    existingEntry.top = contactTrickEntry.top.rawValue
-                    existingEntry.bottom = contactTrickEntry.bottom.rawValue
-                    existingEntry.hasHighContrast = contactTrickEntry.hasHighContrast
-                    existingEntry.ringWidth = Int16(contactTrickEntry.ringWidth.rawValue)
-                    existingEntry.ringGap = Int16(contactTrickEntry.ringGap.rawValue)
-                    existingEntry.fontSize = Int16(contactTrickEntry.fontSize.rawValue)
-                    existingEntry.fontSizeSecondary = Int16(contactTrickEntry.secondaryFontSize.rawValue)
-                    existingEntry.fontWeight = contactTrickEntry.fontWeight.asString
-                    existingEntry.fontWidth = contactTrickEntry.fontWidth.asString
-
-                    guard self.backgroundContext.hasChanges else { return }
-                    try self.backgroundContext.save()
-                } else {
-                    debugPrint(
-                        "\(DebuggingIdentifiers.failed) \(#file) \(#function) No matching Contact Trick Entry found to update."
-                    )
-                }
-            } catch let error as NSError {
-                debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to update Contact Trick Entry with error: \(error.userInfo)"
-                )
-            }
-        }
-    }
-
-    /// Deletes a Contact Trick entry from Core Data.
-    ///
-    /// - Parameter objectID: The `NSManagedObjectID` of the object to delete.
-    func deleteContactTrickEntry(_ objectID: NSManagedObjectID) async {
-        await CoreDataStack.shared.deleteObject(identifiedBy: objectID)
-    }
-}

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

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

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

@@ -13,7 +13,7 @@ final class StorageAssembly: Assembly {
         container.register(GlucoseStorage.self) { r in BaseGlucoseStorage(resolver: r) }
         container.register(TempTargetsStorage.self) { r in BaseTempTargetsStorage(resolver: r) }
         container.register(CarbsStorage.self) { r in BaseCarbsStorage(resolver: r) }
-        container.register(ContactTrickStorage.self) { r in BaseContactTrickStorage(resolver: r) }
+        container.register(ContactImageStorage.self) { r in BaseContactImageStorage(resolver: r) }
         container.register(AnnouncementsStorage.self) { r in BaseAnnouncementsStorage(resolver: r) }
         container.register(SettingsManager.self) { r in BaseSettingsManager(resolver: r) }
         container.register(Keychain.self) { _ in BaseKeychain() }

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

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

Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 1
FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings


+ 15 - 15
FreeAPS/Sources/Models/ContactTrickEntry.swift

@@ -1,14 +1,14 @@
 import CoreData
 import SwiftUI
 
-struct ContactTrickEntry: Hashable, Equatable, Sendable {
+struct ContactImageEntry: Hashable, Equatable, Sendable {
     var id = UUID()
     var name: String = ""
-    var layout: ContactTrickLayout = .single
-    var ring: ContactTrickLargeRing = .none
-    var primary: ContactTrickValue = .glucose
-    var top: ContactTrickValue = .none
-    var bottom: ContactTrickValue = .none
+    var layout: ContactImageLayout = .default
+    var ring: ContactImageLargeRing = .none
+    var primary: ContactImageValue = .glucose
+    var top: ContactImageValue = .none
+    var bottom: ContactImageValue = .none
     var contactId: String? = nil
     var hasHighContrast: Bool = true
     var ringWidth: RingWidth = .regular
@@ -19,7 +19,7 @@ struct ContactTrickEntry: Hashable, Equatable, Sendable {
     var fontWidth: Font.Width = .standard
     var managedObjectID: NSManagedObjectID?
 
-    static func == (lhs: ContactTrickEntry, rhs: ContactTrickEntry) -> Bool {
+    static func == (lhs: ContactImageEntry, rhs: ContactImageEntry) -> Bool {
         lhs.id == rhs.id &&
             lhs.name == rhs.name &&
             lhs.layout == rhs.layout &&
@@ -110,12 +110,12 @@ struct ContactTrickEntry: Hashable, Equatable, Sendable {
     }
 }
 
-protocol ContactTrickObserver: Sendable {
+protocol ContactImageObserver: Sendable {
     // TODO: is this required?
-//    func basalProfileDidChange(_ entry: [ContactTrickEntry])
+//    func basalProfileDidChange(_ entry: [ContactImageEntry])
 }
 
-enum ContactTrickValue: String, JSON, CaseIterable, Identifiable, Codable {
+enum ContactImageValue: String, JSON, CaseIterable, Identifiable, Codable {
     var id: String { rawValue }
     case none
     case glucose
@@ -151,22 +151,22 @@ enum ContactTrickValue: String, JSON, CaseIterable, Identifiable, Codable {
     }
 }
 
-enum ContactTrickLayout: String, JSON, CaseIterable, Identifiable, Codable {
+enum ContactImageLayout: String, JSON, CaseIterable, Identifiable, Codable {
     var id: String { rawValue }
-    case single
+    case `default`
     case split
 
     var displayName: String {
         switch self {
-        case .single:
-            return NSLocalizedString("Single", comment: "")
+        case .default:
+            return NSLocalizedString("Default", comment: "")
         case .split:
             return NSLocalizedString("Split", comment: "")
         }
     }
 }
 
-enum ContactTrickLargeRing: String, JSON, CaseIterable, Identifiable, Codable {
+enum ContactImageLargeRing: String, JSON, CaseIterable, Identifiable, Codable {
     // TODO: revisit rings for iob, cob and combined iob+cob with more user feedback
     var id: String { rawValue }
     case none

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

@@ -35,7 +35,7 @@ class PickerSettingsProvider: ObservableObject {
 struct DecimalPickerSettings {
     var lowGlucose = PickerSetting(value: 70, step: 5, min: 40, max: 100, type: PickerSetting.PickerSettingType.glucose)
     var highGlucose = PickerSetting(value: 180, step: 5, min: 100, max: 400, type: PickerSetting.PickerSettingType.glucose)
-    var carbsRequiredThreshold = PickerSetting(value: 10, step: 1, min: 0, max: 100, type: PickerSetting.PickerSettingType.gramms)
+    var carbsRequiredThreshold = PickerSetting(value: 10, step: 1, min: 0, max: 100, type: PickerSetting.PickerSettingType.gram)
     var individualAdjustmentFactor = PickerSetting(
         value: 0.5,
         step: 0.05,
@@ -45,12 +45,12 @@ struct DecimalPickerSettings {
     )
     var high = PickerSetting(value: 180, step: 1, min: 100, max: 500, type: PickerSetting.PickerSettingType.glucose)
     var low = PickerSetting(value: 70, step: 1, min: 40, max: 100, type: PickerSetting.PickerSettingType.glucose)
-    var maxCarbs = PickerSetting(value: 250, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gramms)
-    var maxFat = PickerSetting(value: 250, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gramms)
-    var maxProtein = PickerSetting(value: 250, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gramms)
+    var maxCarbs = PickerSetting(value: 250, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
+    var maxFat = PickerSetting(value: 250, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
+    var maxProtein = PickerSetting(value: 250, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
     var overrideFactor = PickerSetting(value: 0.8, step: 0.05, min: 0.5, max: 1.5, type: PickerSetting.PickerSettingType.factor)
-    var fattyMealFactor = PickerSetting(value: 0.7, step: 0.05, min: 0.5, max: 2, type: PickerSetting.PickerSettingType.factor)
-    var sweetMealFactor = PickerSetting(value: 2, step: 0.05, min: 1, max: 3, type: PickerSetting.PickerSettingType.factor)
+    var fattyMealFactor = PickerSetting(value: 0.7, step: 0.05, min: 0.05, max: 1, type: PickerSetting.PickerSettingType.factor)
+    var sweetMealFactor = PickerSetting(value: 1, step: 0.05, min: 0.05, max: 2, type: PickerSetting.PickerSettingType.factor)
     var maxIOB = PickerSetting(value: 0, step: 1, min: 0, max: 20, type: PickerSetting.PickerSettingType.insulinUnit)
     var maxDailySafetyMultiplier = PickerSetting(
         value: 3,
@@ -76,8 +76,8 @@ struct DecimalPickerSettings {
         max: 300,
         type: PickerSetting.PickerSettingType.glucose
     )
-    var maxCOB = PickerSetting(value: 120, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gramms)
-    var min5mCarbimpact = PickerSetting(value: 8, step: 1, min: 0, max: 20, type: PickerSetting.PickerSettingType.gramms)
+    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,
@@ -92,7 +92,7 @@ struct DecimalPickerSettings {
         max: 1,
         type: PickerSetting.PickerSettingType.factor
     )
-    var remainingCarbsCap = PickerSetting(value: 90, step: 5, min: 0, max: 200, type: PickerSetting.PickerSettingType.gramms)
+    var remainingCarbsCap = PickerSetting(value: 90, step: 5, min: 0, max: 200, type: PickerSetting.PickerSettingType.gram)
     var maxSMBBasalMinutes = PickerSetting(value: 30, step: 5, min: 30, max: 180, type: PickerSetting.PickerSettingType.minute)
     var maxUAMSMBBasalMinutes = PickerSetting(value: 30, step: 5, min: 30, max: 180, type: PickerSetting.PickerSettingType.minute)
     var smbInterval = PickerSetting(value: 3, step: 1, min: 1, max: 10, type: PickerSetting.PickerSettingType.minute)
@@ -103,8 +103,8 @@ struct DecimalPickerSettings {
         max: 1,
         type: PickerSetting.PickerSettingType.insulinUnit
     )
-    var insulinPeakTime = PickerSetting(value: 75, step: 5, min: 35, max: 120, type: PickerSetting.PickerSettingType.minute)
-    var carbsReqThreshold = PickerSetting(value: 1.0, step: 0.1, min: 0, max: 10, type: PickerSetting.PickerSettingType.gramms)
+    var insulinPeakTime = PickerSetting(value: 75, step: 1, min: 35, max: 120, type: PickerSetting.PickerSettingType.minute)
+    var carbsReqThreshold = PickerSetting(value: 1.0, step: 0.1, min: 0, max: 10, type: PickerSetting.PickerSettingType.gram)
     var noisyCGMTargetMultiplier = PickerSetting(
         value: 1.3,
         step: 0.05,
@@ -119,15 +119,15 @@ struct DecimalPickerSettings {
         max: 0.4,
         type: PickerSetting.PickerSettingType.factor
     )
-    var adjustmentFactor = PickerSetting(value: 0.8, step: 0.1, min: 0.5, max: 1.5, type: PickerSetting.PickerSettingType.factor)
+    var adjustmentFactor = PickerSetting(value: 0.8, step: 0.05, min: 0.3, max: 1.5, type: PickerSetting.PickerSettingType.factor)
     var adjustmentFactorSigmoid = PickerSetting(
         value: 0.5,
-        step: 0.1,
-        min: 0.5,
+        step: 0.05,
+        min: 0.1,
         max: 2,
         type: PickerSetting.PickerSettingType.factor
     )
-    var weightPercentage = PickerSetting(value: 0.65, step: 0.1, min: 0.1, max: 1, type: PickerSetting.PickerSettingType.factor)
+    var weightPercentage = PickerSetting(value: 0.35, step: 0.05, min: 0.05, max: 1, type: PickerSetting.PickerSettingType.factor)
     var enableSMB_high_bg_target = PickerSetting(
         value: 110,
         step: 1,
@@ -141,7 +141,7 @@ struct DecimalPickerSettings {
     var minuteInterval = PickerSetting(value: 20, step: 5, min: 5, max: 60, type: PickerSetting.PickerSettingType.minute)
     var timeCap = PickerSetting(value: 8, step: 1, min: 5, max: 12, type: PickerSetting.PickerSettingType.hour)
     var hours = PickerSetting(value: 6, step: 0.5, min: 2, max: 24, type: PickerSetting.PickerSettingType.hour)
-    var dia = PickerSetting(value: 6, step: 0.5, min: 4, max: 10, type: PickerSetting.PickerSettingType.hour)
+    var dia = PickerSetting(value: 10, step: 0.5, min: 5, max: 10, type: PickerSetting.PickerSettingType.hour)
     var maxBolus = PickerSetting(value: 10, step: 0.5, min: 1, max: 30, type: PickerSetting.PickerSettingType.insulinUnit)
     var maxBasal = PickerSetting(value: 10, step: 0.5, min: 1, max: 30, type: PickerSetting.PickerSettingType.insulinUnit)
 }
@@ -156,7 +156,7 @@ struct PickerSetting {
     enum PickerSettingType {
         case glucose
         case factor
-        case gramms
+        case gram
         case insulinUnit
         case minute
         case hour

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

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

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

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

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

@@ -11,7 +11,6 @@ enum Icon_: String, CaseIterable, Identifiable {
     case wilford = "diabeetus"
     case catWithPod
     case catWithPodWhite = "catWithPodWhiteBG"
-    case loop = "trioLoop"
     var id: String { rawValue }
 }
 

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

@@ -48,7 +48,7 @@ struct Preferences: JSON, Equatable {
     var enableDynamicCR: Bool = false
     var useNewFormula: Bool = false
     var useWeightedAverage: Bool = false
-    var weightPercentage: Decimal = 0.65
+    var weightPercentage: Decimal = 0.35
     var tddAdjBasal: Bool = false
     var enableSMB_high_bg: Bool = false
     var enableSMB_high_bg_target: Decimal = 110

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

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

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

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

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

@@ -12,7 +12,7 @@ extension AlgorithmAdvancedSettings {
         func settings() -> PumpSettings {
             storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self)
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
-                ?? PumpSettings(insulinActionCurve: 6.0, maxBolus: 10, maxBasal: 2)
+                ?? PumpSettings(insulinActionCurve: 10.0, maxBolus: 10, maxBasal: 2)
         }
 
         func savePreferences(_ preferences: Preferences) {

+ 1 - 1
FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift

@@ -23,7 +23,7 @@ extension AlgorithmAdvancedSettings {
         @Published var remainingCarbsCap: Decimal = 90
         @Published var noisyCGMTargetMultiplier: Decimal = 1.3
 
-        var insulinActionCurve: Decimal = 6
+        var insulinActionCurve: Decimal = 10
 
         var pumpSettings: PumpSettings {
             provider.settings()

+ 175 - 96
FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift

@@ -7,7 +7,7 @@ extension AlgorithmAdvancedSettings {
         @StateObject var state = StateModel()
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: String?
+        @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
@@ -23,7 +23,7 @@ extension AlgorithmAdvancedSettings {
                     content: {
                         VStack(alignment: .leading) {
                             Text(
-                                "The settings in this section are designed for advanced expert users and typically do not require ANY modifications."
+                                "The settings in this section typically do not require ANY modifications. Do not alter them without a solid understanding of what you are changing and the full impact it will have on the algorithm."
                             ).bold()
                         }
                     }
@@ -37,18 +37,23 @@ extension AlgorithmAdvancedSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = NSLocalizedString("Max Daily Safety Multiplier", comment: "Max Daily Safety Multiplier")
                         }
                     ),
                     units: state.units,
                     type: .decimal("maxDailySafetyMultiplier"),
                     label: NSLocalizedString("Max Daily Safety Multiplier", comment: "Max Daily Safety Multiplier"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "This is an important OpenAPS safety limit. The default setting (which is unlikely to need adjusting) is 3. This means that OpenAPS will never be allowed to set a temporary basal rate that is more than 3x the highest hourly basal rate programmed in a user’s pump, or, if enabled, determined by autotune.",
-                        comment: "Max Daily Safety Multiplier"
-                    )
+                    miniHint: "Limits temporary basal rates to this percentage of your largest basal rate.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: 300%").bold()
+                        Text(
+                            "This setting restricts the maximum temporary basal rate Trio can set. At the default of 300%, it caps it at 3 times your highest programmed basal rate."
+                        )
+                        Text("It serves as a safety limit, ensuring no temporary basal rates exceed safe levels.")
+                        Text("Warning: Increasing this setting is not advised.").bold()
+                    }
                 )
 
                 SettingInputSection(
@@ -58,7 +63,7 @@ extension AlgorithmAdvancedSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = NSLocalizedString(
                                 "Current Basal Safety Multiplier",
                                 comment: "Current Basal Safety Multiplier"
@@ -68,11 +73,18 @@ extension AlgorithmAdvancedSettings {
                     units: state.units,
                     type: .decimal("currentBasalSafetyMultiplier"),
                     label: NSLocalizedString("Current Basal Safety Multiplier", comment: "Current Basal Safety Multiplier"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "This is another important OpenAPS safety limit. The default setting (which is also unlikely to need adjusting) is 4. This means that OpenAPS will never be allowed to set a temporary basal rate that is more than 4x the current hourly basal rate programmed in a user’s pump, or, if enabled, determined by autotune.",
-                        comment: "Current Basal Safety Multiplier"
-                    )
+                    miniHint: "Limits temporary basal rates to this percentage of the current basal rate.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: 400%").bold()
+                        Text(
+                            "This limits the automatic adjustment of the temporary basal rate to this percentage of the current hourly profile basal rate at the time of the loop cycle."
+                        )
+                        Text(
+                            "This prevents excessive dosing, especially during times of variable insulin sensitivity, enhancing safety."
+                        )
+                        Text("Warning: Increasing this setting is not advised.").bold()
+                    }
                 )
 
                 SettingInputSection(
@@ -82,15 +94,27 @@ extension AlgorithmAdvancedSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = "Duration of Insulin Action"
                         }
                     ),
                     units: state.units,
                     type: .decimal("dia"),
                     label: "Duration of Insulin Action",
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: "Duration of Insulin Action… bla bla bla"
+                    miniHint: "Number of hours insulin is active in your body.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: 10 hours").bold()
+                        Text(
+                            "The Duration of Insulin Action (DIA) defines how long your insulin continues to lower glucose readings after a dose."
+                        )
+                        Text(
+                            "This helps the system accurately track Insulin on Board (IOB), avoiding over- or under-corrections by considering the tail end of insulin's effect."
+                        )
+                        Text(
+                            "Tip: It is better to use Custom Peak Time rather than adjust your Duration of Insulin Action (DIA)."
+                        )
+                    }
                 )
 
                 SettingInputSection(
@@ -100,7 +124,7 @@ extension AlgorithmAdvancedSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = NSLocalizedString("Use Custom Peak Time", comment: "Use Custom Peak Time")
                         }
                     ),
@@ -108,13 +132,20 @@ extension AlgorithmAdvancedSettings {
                     type: .conditionalDecimal("insulinPeakTime"),
                     label: NSLocalizedString("Use Custom Peak Time", comment: "Use Custom Peak Time"),
                     conditionalLabel: NSLocalizedString("Insulin Peak Time", comment: "Insulin Peak Time"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "Defaults to false. Setting to true allows changing insulinPeakTime", comment: "Use Custom Peak Time"
-                    ) + NSLocalizedString(
-                        "Time of maximum blood glucose lowering effect of insulin, in minutes. Beware: Oref assumes for ultra-rapid (Lyumjev) & rapid-acting (Fiasp) curves minimal (35 & 50 min) and maximal (100 & 120 min) applicable insulinPeakTimes. Using a custom insulinPeakTime outside these bounds will result in issues with Trio, longer loop calculations and possible red loops.",
-                        comment: "Insulin Peak Time"
-                    )
+                    miniHint: "Set a custom time for peak insulin effect.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: Set by Insulin Type").bold()
+                        Text(
+                            "Insulin Peak Time defines when insulin is most effective in lowering glucose, set in minutes after dosing."
+                        )
+                        Text(
+                            "This peak informs the system when to expect the most potent glucose-lowering effect, helping it predict glucose trends more accurately."
+                        )
+                        Text("System-Determined Defaults:").bold()
+                        Text("Ultra-Rapid: 55 minutes (permitted range 35-100 minutes)")
+                        Text("Rapid-Acting: 75 minutes (permitted range 50-120 minutes)")
+                    }
                 )
 
                 SettingInputSection(
@@ -124,18 +155,24 @@ extension AlgorithmAdvancedSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = NSLocalizedString("Skip Neutral Temps", comment: "Skip Neutral Temps")
                         }
                     ),
                     units: state.units,
                     type: .boolean,
                     label: NSLocalizedString("Skip Neutral Temps", comment: "Skip Neutral Temps"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "Defaults to false, so that Trio will set temps whenever it can, so it will be easier to see if the system is working, even when you are offline. This means Trio will set a “neutral” temp (same as your default basal) if no adjustments are needed. This is an old setting for OpenAPS to have the options to minimise sounds and notifications from the 'rig', that may wake you up during the night.",
-                        comment: "Skip Neutral Temps"
-                    )
+                    miniHint: "Skip neutral temporary basal rates to reduce MDT pump alerts.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        Text(
+                            "When Skip Neutral Temps is enabled, Trio will not set neutral basal rates shortly before the hour, minimizing hourly pump alerts on MDT pumps. This can help light sleepers avoid alerts but will delay basal adjustments. This will also only come into effect if SMB's are disabled for whatever reason."
+                        )
+                        Text(
+                            "For most users, leaving this OFF is recommended to ensure consistent basal delivery and loop calculation. If this option is effective, loops will be skipped during the last 5 minutes of the hour."
+                        )
+                    }
                 )
 
                 SettingInputSection(
@@ -145,18 +182,22 @@ extension AlgorithmAdvancedSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = NSLocalizedString("Unsuspend If No Temp", comment: "Unsuspend If No Temp")
                         }
                     ),
                     units: state.units,
                     type: .boolean,
                     label: NSLocalizedString("Unsuspend If No Temp", comment: "Unsuspend If No Temp"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "Many people occasionally forget to resume / unsuspend their pump after reconnecting it. If you’re one of them, and you are willing to reliably set a zero temp basal whenever suspending and disconnecting your pump, this feature has your back. If enabled, it will automatically resume / unsuspend the pump if you forget to do so before your zero temp expires. As long as the zero temp is still running, it will leave the pump suspended.",
-                        comment: "Unsuspend If No Temp"
-                    )
+                    miniHint: "Resume pump automatically after suspension.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        Text(
+                            "Enabling Unsuspend If No Temp allows Trio to resume your pump if you forget, as long as a zero temp basal was set first. This feature ensures insulin delivery restarts if you forget to manually unsuspend, adding a safeguard for pump reconnections."
+                        )
+                        Text("Note: Applies only to pumps with on-pump suspend options")
+                    }
                 )
 
                 SettingInputSection(
@@ -166,43 +207,53 @@ extension AlgorithmAdvancedSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = NSLocalizedString("Suspend Zeros IOB", comment: "Suspend Zeros IOB")
                         }
                     ),
                     units: state.units,
                     type: .boolean,
                     label: NSLocalizedString("Suspend Zeros IOB", comment: "Suspend Zeros IOB"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "Default is false. Any existing temp basals during times the pump was suspended will be deleted and 0 temp basals to negate the profile basal rates during times pump is suspended will be added.",
-                        comment: "Suspend Zeros IOB"
-                    )
+                    miniHint: "Clear temporary basal rates and reset IOB when suspended.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        Text(
+                            "When Suspend Zeros IOB is enabled, any active temporary basal rates during a pump suspension are reset, with new 0 U/hr temporary basal rates added to counteract those done during suspension."
+                        )
+                        Text(
+                            "This prevents lingering insulin effects when your pump is suspended, ensuring safer management of insulin on board."
+                        )
+                        Text("Note: Applies only to pumps with on-pump suspend options.")
+                    }
                 )
 
-                SettingInputSection(
-                    decimalValue: $state.autotuneISFAdjustmentFraction,
-                    booleanValue: $booleanPlaceholder,
-                    shouldDisplayHint: $shouldDisplayHint,
-                    selectedVerboseHint: Binding(
-                        get: { selectedVerboseHint },
-                        set: {
-                            selectedVerboseHint = $0
-                            hintLabel = NSLocalizedString(
-                                "Autotune ISF Adjustment Fraction",
-                                comment: "Autotune ISF Adjustment Fraction"
-                            )
-                        }
-                    ),
-                    units: state.units,
-                    type: .decimal("autotuneISFAdjustmentFraction"),
-                    label: NSLocalizedString("Autotune ISF Adjustment Fraction", comment: "Autotune ISF Adjustment Fraction"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "The default of 0.5 for this value keeps autotune ISF closer to pump ISF via a weighted average of fullNewISF and pumpISF. 1.0 allows full adjustment, 0 is no adjustment from pump ISF.",
-                        comment: "Autotune ISF Adjustment Fraction"
-                    )
-                )
+                // 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,
@@ -211,18 +262,26 @@ extension AlgorithmAdvancedSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
-                            hintLabel = NSLocalizedString("Min 5m Carbimpact", comment: "Min 5m Carbimpact")
+                            selectedVerboseHint = $0.map { AnyView($0) }
+                            hintLabel = NSLocalizedString("Min 5m Carb Impact", comment: "Min 5m Carb Impact")
                         }
                     ),
                     units: state.units,
                     type: .decimal("min5mCarbimpact"),
-                    label: NSLocalizedString("Min 5m Carbimpact", comment: "Min 5m Carbimpact"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "This is a setting for default carb absorption impact per 5 minutes. The default is an expected 8 mg/dL/5min. This affects how fast COB is decayed in situations when carb absorption is not visible in BG deviations. The default of 8 mg/dL/5min corresponds to a minimum carb absorption rate of 24g/hr at a CSF of 4 mg/dL/g.",
-                        comment: "Min 5m Carbimpact"
-                    )
+                    label: NSLocalizedString("Min 5m Carb Impact", comment: "Min 5m Carb Impact"),
+                    miniHint: "Default impact of carb absorption over a 5 minute interval.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text(
+                            "Min 5m Carb Impact sets the expected glucose rise from carbs over 5 minutes when absorption isn't obvious from glucose data."
+                        )
+                        Text(
+                            "The default value of 8 mg/dL per 5 minutes corresponds to an absorption rate of 24 g of carbs per hour."
+                        )
+                        Text(
+                            "This setting helps the system estimate how much glucose your body is absorbing, even when it's not immediately visible in your glucose data, ensuring more accurate insulin dosing during carb absorption."
+                        )
+                    }
                 )
 
                 SettingInputSection(
@@ -232,18 +291,24 @@ extension AlgorithmAdvancedSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
-                            hintLabel = NSLocalizedString("Remaining Carbs Fraction", comment: "Remaining Carbs Fraction")
+                            selectedVerboseHint = $0.map { AnyView($0) }
+                            hintLabel = NSLocalizedString("Remaining Carbs Percentage", comment: "Remaining Carbs Percentage")
                         }
                     ),
                     units: state.units,
                     type: .decimal("remainingCarbsFraction"),
-                    label: NSLocalizedString("Remaining Carbs Fraction", comment: "Remaining Carbs Fraction"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "This is the fraction of carbs we’ll assume will absorb over 4h if we don’t yet see carb absorption.",
-                        comment: "Remaining Carbs Fraction"
-                    )
+                    label: NSLocalizedString("Remaining Carbs Percentage", comment: "Remaining Carbs Percentage"),
+                    miniHint: "Percentage of carbs still available if no absorption is detected.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: 100%").bold()
+                        Text(
+                            "Remaining Carbs Percentage estimates carbs still absorbing over 4 hours if glucose data doesn't show clear absorption."
+                        )
+                        Text(
+                            "This fallback setting prevents under-dosing by spreading a portion of the entered carbs over time, balancing insulin needs with undetected carb impact."
+                        )
+                    }
                 )
 
                 SettingInputSection(
@@ -253,18 +318,24 @@ extension AlgorithmAdvancedSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = NSLocalizedString("Remaining Carbs Cap", comment: "Remaining Carbs Cap")
                         }
                     ),
                     units: state.units,
                     type: .decimal("remainingCarbsCap"),
                     label: NSLocalizedString("Remaining Carbs Cap", comment: "Remaining Carbs Cap"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "This is the amount of the maximum number of carbs we’ll assume will absorb over 4h if we don’t yet see carb absorption.",
-                        comment: "Remaining Carbs Cap"
-                    )
+                    miniHint: "Maximum amount of carbs still available if no absorption is detected.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: 90 g").bold()
+                        Text(
+                            "The Remaining Carbs Cap defines the upper limit for how many carbs the system will assume are absorbing over 4 hours, even when there's no clear sign of absorption from your glucose readings."
+                        )
+                        Text(
+                            "This cap prevents the system from overestimating how much insulin is needed when carb absorption isn't visible, offering a safeguard for accurate dosing."
+                        )
+                    }
                 )
 
                 SettingInputSection(
@@ -274,26 +345,34 @@ extension AlgorithmAdvancedSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = NSLocalizedString("Noisy CGM Target Multiplier", comment: "Noisy CGM Target Multiplier")
                         }
                     ),
                     units: state.units,
                     type: .decimal("noisyCGMTargetMultiplier"),
-                    label: NSLocalizedString("Noisy CGM Target Multiplier", comment: "Noisy CGM Target Multiplier"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "Defaults to 1.3. Increase target by this amount when looping off raw/noisy CGM data",
-                        comment: "Noisy CGM Target Multiplier"
-                    )
+                    label: NSLocalizedString("Noisy CGM Target Increase", comment: "Noisy CGM Target Increase"),
+                    miniHint: "Percentage increase of glucose target when CGM is inconsistent.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: 130%").bold()
+                        Text(
+                            "The Noisy CGM Target Increase raises your glucose target when the system detects noisy or raw CGM data. By default, the target is increased to 130% of your set target glucose to account for the less reliable glucose readings."
+                        )
+                        Text(
+                            "This helps reduce the risk of incorrect insulin dosing based on inaccurate sensor data, ensuring safer insulin adjustments during periods of poor CGM accuracy."
+                        )
+                        Text("Note: A CGM is considered noisy when it provides inconsistent readings.")
+                    }
                 )
             }
+            .listSectionSpacing(sectionSpacing)
             .sheet(isPresented: $shouldDisplayHint) {
                 SettingInputHintView(
                     hintDetent: $hintDetent,
                     shouldDisplayHint: $shouldDisplayHint,
                     hintLabel: hintLabel ?? "",
-                    hintText: selectedVerboseHint ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                     sheetTitle: "Help"
                 )
             }

+ 50 - 20
FreeAPS/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift

@@ -7,7 +7,7 @@ extension AutosensSettings {
         @StateObject var state = StateModel()
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: String?
+        @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
@@ -96,18 +96,27 @@ extension AutosensSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = NSLocalizedString("Autosens Max", comment: "Autosens Max")
                         }
                     ),
                     units: state.units,
                     type: .decimal("autosensMax"),
                     label: NSLocalizedString("Autosens Max", comment: "Autosens Max"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "This is a multiplier cap for autosens (and autotune) to set a 20% max limit on how high the autosens ratio can be, which in turn determines how high autosens can adjust basals, how low it can adjust ISF, and how low it can set the BG target.",
-                        comment: "Autosens Max"
-                    ),
+                    miniHint: "Upper limit of the Autosens Ratio.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: 120%").bold()
+                        Text(
+                            "Autosens Max sets the maximum Autosens Ratio used by Autosens, Dynamic ISF, and Sigmoid Formula."
+                        )
+                        Text(
+                            "The Autosens Ratio is used to calculate the amount of adjustment needed to basal rates, ISF, and CR."
+                        )
+                        Text(
+                            "Tip: Increasing this value allows automatic adjustments of basal rates to be higher, ISF to be lower, and CR to be lower."
+                        )
+                    },
                     headerText: "Glucose Deviations Algorithm"
                 )
 
@@ -118,18 +127,27 @@ extension AutosensSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = NSLocalizedString("Autosens Min", comment: "Autosens Min")
                         }
                     ),
                     units: state.units,
                     type: .decimal("autosensMin"),
                     label: NSLocalizedString("Autosens Min", comment: "Autosens Min"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "The other side of the autosens safety limits, putting a cap on how low autosens can adjust basals, and how high it can adjust ISF and BG targets.",
-                        comment: "Autosens Min"
-                    )
+                    miniHint: "Lower limit of the Autosens Ratio.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: 80%").bold()
+                        Text(
+                            "Autosens Min sets the minimum Autosens Ratio used by Autosens, Dynamic ISF, and Sigmoid Formula."
+                        )
+                        Text(
+                            "The Autosens Ratio is used to calculate the amount of adjustment needed to basal rates, ISF, and CR."
+                        )
+                        Text(
+                            "Tip: Decreasing this value allows automatic adjustments of basal rates to be lower, ISF to be higher, and CR to be higher."
+                        )
+                    }
                 )
 
                 SettingInputSection(
@@ -139,26 +157,38 @@ extension AutosensSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = NSLocalizedString("Rewind Resets Autosens", comment: "Rewind Resets Autosens")
                         }
                     ),
                     units: state.units,
                     type: .boolean,
                     label: NSLocalizedString("Rewind Resets Autosens", comment: "Rewind Resets Autosens"),
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: NSLocalizedString(
-                        "This feature, enabled by default, resets the autosens ratio to neutral when you rewind your pump, on the assumption that this corresponds to a probable site change. Autosens will begin learning sensitivity anew from the time of the rewind, which may take up to 6 hours. If you usually rewind your pump independently of site changes, you may want to consider disabling this feature.",
-                        comment: "Rewind Resets Autosens"
-                    )
+                    miniHint: "Pump rewind initiates a reset in Autosens Ratio.",
+                    verboseHint: VStack(alignment: .leading, spacing: 5) {
+                        Text("Default: ON").bold()
+                        Text("Medtronic Users Only").bold()
+                        VStack(alignment: .leading, spacing: 10) {
+                            Text(
+                                "This feature resets the Autosens Ratio to neutral when you rewind your pump on the assumption that this corresponds to a site change."
+                            )
+                            Text(
+                                "Autosens will begin learning sensitivity anew from the time of the rewind, which may take up to 6 hours."
+                            )
+                            Text(
+                                "Tip: If you usually rewind your pump independently of site changes, you may want to consider disabling this feature."
+                            )
+                        }
+                    }
                 )
             }
+            .listSectionSpacing(sectionSpacing)
             .sheet(isPresented: $shouldDisplayHint) {
                 SettingInputHintView(
                     hintDetent: $hintDetent,
                     shouldDisplayHint: $shouldDisplayHint,
                     hintLabel: hintLabel ?? "",
-                    hintText: selectedVerboseHint ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                     sheetTitle: "Help"
                 )
             }

+ 23 - 18
FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift

@@ -8,7 +8,7 @@ extension AutotuneConfig {
 
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: String?
+        @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
@@ -48,15 +48,20 @@ extension AutotuneConfig {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = "Use Autotune"
                         }
                     ),
                     units: state.units,
                     type: .boolean,
                     label: "Use Autotune",
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: "Autotune… bla bla bla",
+                    miniHint: "It is not advised to use Autotune with Trio.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("It is not advised to use Autotune with Trio").bold()
+                        Text("Autotune is not designed to work with Trio. It is best to keep Autotune off and do not use it.")
+
+                    },
                     headerText: "Data-driven Adjustments"
                 )
 
@@ -68,15 +73,15 @@ extension AutotuneConfig {
                         selectedVerboseHint: Binding(
                             get: { selectedVerboseHint },
                             set: {
-                                selectedVerboseHint = $0
+                                selectedVerboseHint = $0.map { AnyView($0) }
                                 hintLabel = "Only Autotune Basal Insulin"
                             }
                         ),
                         units: state.units,
                         type: .boolean,
                         label: "Only Autotune Basal Insulin",
-                        miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                        verboseHint: "Only Autotune Basal Insulin… bla bla bla"
+                        miniHint: "Restricts Autotune adjustments to only basal settings.",
+                        verboseHint: Text("Restricts Autotune adjustments to only basal settings.")
                     )
                 }
 
@@ -156,16 +161,16 @@ extension AutotuneConfig {
                         .tint(.white)
                     }
 
-                    // Section {
-                    //     Button {
-                    //         replaceAlert = true
-                    //     } label: {
-                    //         Text("Save as Normal Basal Rates")
-                    //     }
-                    //     .frame(maxWidth: .infinity, alignment: .center)
-                    //     .listRowBackground(Color(.systemGray4))
-                    //     .tint(.white)
-                    // }
+                    Section {
+                        Button {
+                            replaceAlert = true
+                        } label: {
+                            Text("Save as Normal Basal Rates")
+                        }
+                        .frame(maxWidth: .infinity, alignment: .center)
+                        .listRowBackground(Color(.systemGray4))
+                        .tint(.white)
+                    }
                 }
             }
             .sheet(isPresented: $shouldDisplayHint) {
@@ -173,7 +178,7 @@ extension AutotuneConfig {
                     hintDetent: $hintDetent,
                     shouldDisplayHint: $shouldDisplayHint,
                     hintLabel: hintLabel ?? "",
-                    hintText: selectedVerboseHint ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                     sheetTitle: "Help"
                 )
             }

+ 67 - 21
FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift

@@ -9,7 +9,7 @@ extension BolusCalculatorConfig {
 
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: String?
+        @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
@@ -32,7 +32,7 @@ extension BolusCalculatorConfig {
         }
 
         var body: some View {
-            Form {
+            List {
                 SettingInputSection(
                     decimalValue: $decimalPlaceholder,
                     booleanValue: $state.displayPresets,
@@ -40,15 +40,18 @@ extension BolusCalculatorConfig {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = "Display Meal Presets"
                         }
                     ),
                     units: state.units,
                     type: .boolean,
                     label: "Display Meal Presets",
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
+                    miniHint: "Allow the creation of saved, preset meals.",
+                    verboseHint: VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: ON").bold()
+                        Text("Enabling this feature allows you to create and save preset meals.")
+                    }
                 )
 
                 SettingInputSection(
@@ -58,15 +61,27 @@ extension BolusCalculatorConfig {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = "Recommended Bolus Percentage"
                         }
                     ),
                     units: state.units,
                     type: .decimal("overrideFactor"),
                     label: "Recommended Bolus Percentage",
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: "Recommended Bolus Percentage… bla bla bla",
+                    miniHint: "Percentage of bolus suggested in bolus calculator.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: 80%").bold()
+                        Text(
+                            "Recommended Bolus Percentage is a safety feature built into Trio. Trio first calculates an insulin required value, which is the full dosage. That dosage is then multiplied by your Recommended Bolus Percentage to display your suggested insulin dose in the bolus calculator."
+                        )
+                        Text(
+                            "Because Trio utilizes SMBs and UAM SMBs to help you reach your target glucose and other AID systems do not bolus for COB the same way Trio does, this is initially set to below the full calculated amount (80%). When SMBs and UAM SMBs are enabled, you may find your current CR results in lows and needs to be increased before you increase this setting closer to or at 100%."
+                        )
+                        Text(
+                            "Tip: If you are a new Trio user, it is not advised to set this to 100% until you have verified that your core settings (basal rates, ISF, and CR) do not need adjusting."
+                        )
+                    },
                     headerText: "Calculator Configuration"
                 )
 
@@ -77,16 +92,31 @@ extension BolusCalculatorConfig {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
-                            hintLabel = "Fatty Meal Factor"
+                            selectedVerboseHint = $0.map { AnyView($0) }
+                            hintLabel = "Fatty Meal"
                         }
                     ),
                     units: state.units,
                     type: .conditionalDecimal("fattyMealFactor"),
-                    label: "Enable Fatty Meal Factor",
-                    conditionalLabel: "Fatty Meal Factor",
-                    miniHint: "Lower your bolus recommendation by factor x for fatty meals.",
-                    verboseHint: "You can add the option in your bolus calculator to apply another (!) customizable factor at the end of the calculation which could be useful for fatty meals, e.g Pizza (default 0.7)."
+                    label: "Enable Fatty Meal Option",
+                    conditionalLabel: "Fatty Meal Bolus Percentage",
+                    miniHint: "Add and set a bolus option for meals that absorb slowly.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        Text("Default Percent: 70%").bold()
+                        Text("Do not enable this feature until you have optimized your CR (carb ratio) setting.").bold()
+                        Text(
+                            "Enabling this setting adds a \"Fatty Meal\" option to the bolus calculator. Once this feature is enabled, a percentage setting will appear for you to select."
+                        )
+                        Text(
+                            "When \"Fatty Meal\" is selected in the bolus calculator, the recommended bolus will be multiplied by the \"Fatty Meal Bolus Percentage\" as well as the \"Recommended Bolus Percentage\"."
+                        )
+                        Text(
+                            "If you have a \"Recommended Bolus Percentage\" of 80%, and a \"Fatty Meal Bolus Percentage\" of 70%, your recommended bolus will be multiplied by: (80 × 70) ÷ 100 = 56%."
+                        )
+                        Text("This could be useful for slow absorbing meals like pizza.")
+                    }
                 )
 
                 SettingInputSection(
@@ -96,24 +126,40 @@ extension BolusCalculatorConfig {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
-                            hintLabel = "Super Bolus & Sweet Meal Factor"
+                            selectedVerboseHint = $0.map { AnyView($0) }
+                            hintLabel = "Super Bolus"
                         }
                     ),
                     units: state.units,
                     type: .conditionalDecimal("sweetMealFactor"),
-                    label: "Enable Super Bolus",
-                    conditionalLabel: "Super Bolus Factor",
-                    miniHint: "Add x times current scheduled basal rate to your bolus recommendation.",
-                    verboseHint: "You can enable the super bolus functionality which could be useful when eating sweets/cake etc. Therefore your current basal rate will be added x-times to your bolus recommendation. You can adjust the factor X here, the default is 2 times your current scheduled basal rate."
+                    label: "Enable Super Bolus Option",
+                    conditionalLabel: "Super Bolus Percentage",
+                    miniHint: "Add and set a bolus option for meals that absorb quickly.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        Text("Default Percent: 200%").bold()
+                        Text("Do not enable this feature until you have optimized your CR (carb ratio) setting.").bold()
+                        Text(
+                            "Enabling this setting adds a \"Super Bolus\" option to the bolus calculator. Once this feature is enabled, a percentage setting will appear for you to select."
+                        )
+                        Text(
+                            "When \"Super Bolus\" is selected in the bolus calculator, your current basal rate multiplied by \"Super Bolus Percentage\" will be added to your bolus recommendation."
+                        )
+                        Text(
+                            "If your current basal rate is 0.8 U/hr and \"Super Bolus Percentage\" is set to 200%: 0.8 × (200 ÷ 100) = 1.6 units will be added to your bolus recommendation."
+                        )
+                        Text("This could be useful for fast absorbing meals like sugary cereal.")
+                    }
                 )
             }
+            .listSectionSpacing(sectionSpacing)
             .sheet(isPresented: $shouldDisplayHint) {
                 SettingInputHintView(
                     hintDetent: $hintDetent,
                     shouldDisplayHint: $shouldDisplayHint,
                     hintLabel: hintLabel ?? "",
-                    hintText: selectedVerboseHint ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                     sheetTitle: "Help"
                 )
             }

+ 33 - 12
FreeAPS/Sources/Modules/CGM/View/CGMRootView.swift

@@ -11,7 +11,7 @@ extension CGM {
 
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: String?
+        @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
@@ -21,7 +21,7 @@ extension CGM {
 
         var body: some View {
             NavigationView {
-                Form {
+                List {
                     Section(
                         header: Text("CGM Integration to Trio"),
                         content: {
@@ -35,9 +35,9 @@ extension CGM {
                                     }
                                 }.padding(.top)
 
-                                HStack(alignment: .top) {
+                                HStack(alignment: .center) {
                                     Text(
-                                        "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
+                                        "Select your CGM. See hint for compatible devices."
                                     )
                                     .font(.footnote)
                                     .foregroundColor(.secondary)
@@ -47,7 +47,11 @@ extension CGM {
                                         action: {
                                             hintLabel = "Available CGM Types for Trio"
                                             selectedVerboseHint =
-                                                "CGM Types… bla bla \n\nLorem ipsum dolor sit amet, consetetur sadipscing elitr."
+                                                AnyView(
+                                                    Text(
+                                                        "• Dexcom G5 \n• Dexcom G6 / ONE \n• Dexcom G7 / ONE+ \n• Dexcom Share \n• Freestyle Libre \n• Freestyle Libre Demo \n• Glucose Simulator \n• Medtronic Enlite \n• Nightscout \n• xDrip4iOS"
+                                                    )
+                                                )
                                             shouldDisplayHint.toggle()
                                         },
                                         label: {
@@ -146,9 +150,9 @@ extension CGM {
                                     Text("CGM is not used as heartbeat.").padding(.top)
                                 }
 
-                                HStack(alignment: .top) {
+                                HStack(alignment: .center) {
                                     Text(
-                                        "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
+                                        "A heartbeat tells Trio to start a loop cycle. This is required for closed loop."
                                     )
                                     .font(.footnote)
                                     .foregroundColor(.secondary)
@@ -157,7 +161,12 @@ extension CGM {
                                     Button(
                                         action: {
                                             hintLabel = "CGM Heartbeat"
-                                            selectedVerboseHint = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
+                                            selectedVerboseHint =
+                                                AnyView(
+                                                    Text(
+                                                        "The CGM Heartbeat can come from either a CGM or a pump to wake up Trio when phone is locked or in the background. If CGM is on the same phone as Trio and xDrip4iOS is configured to use the same AppGroup as Trio and the heartbeat feature is turned on in xDrip4iOS, then the CGM can provide a heartbeat to wake up Trio when phone is locked or app is in the background."
+                                                    )
+                                                )
                                             shouldDisplayHint.toggle()
                                         },
                                         label: {
@@ -184,15 +193,27 @@ extension CGM {
                         selectedVerboseHint: Binding(
                             get: { selectedVerboseHint },
                             set: {
-                                selectedVerboseHint = $0
+                                selectedVerboseHint = $0.map { AnyView($0) }
                                 hintLabel = "Smooth Glucose Value"
                             }
                         ),
                         units: state.units,
                         type: .boolean,
                         label: "Smooth Glucose Value",
-                        miniHint: "Smooth CGM readings using Savitzky–Golay filtering.",
-                        verboseHint: "Smooth Glucose Value… bla bla bla"
+                        miniHint: "Smooth CGM readings using Savitzky-Golay filtering.",
+                        verboseHint:
+                        VStack(alignment: .leading, spacing: 10) {
+                            Text("Default: OFF").bold()
+                            Text(
+                                "This filter looks at small groups of nearby readings and fits them to a simple mathematical curve. This process doesn't change the overall pattern of your glucose data but helps smooth out the \"noise\" or irregular fluctuations that could lead to false highs or lows."
+                            )
+                            Text(
+                                "It's designed to keep the important trends in your data while minimizing those small, misleading variations, giving you and Trio a clearer sense of where your blood sugar is really headed. This type of filtering is useful in Trio, as it can help prevent over-corrections based on inaccurate glucose readings. This can help reduce the impact of sudden spikes or dips that might not reflect your true blood glucose levels."
+                            )
+                            Text(
+                                "Note: If enabled, the smoothed values you see in Trio may differ from what is shown in your CGM app."
+                            )
+                        }
                     )
                 }
                 .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
@@ -205,7 +226,7 @@ extension CGM {
                         hintDetent: $hintDetent,
                         shouldDisplayHint: $shouldDisplayHint,
                         hintLabel: hintLabel ?? "",
-                        hintText: selectedVerboseHint ?? "",
+                        hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                         sheetTitle: "Help"
                     )
                 }

+ 47 - 11
FreeAPS/Sources/Modules/CalendarEventSettings/View/CalendarEventSettingsRootView.swift

@@ -7,7 +7,7 @@ extension CalendarEventSettings {
         @StateObject var state = StateModel()
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: String?
+        @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
@@ -25,15 +25,28 @@ extension CalendarEventSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = "Create Events in Calendar"
                         }
                     ),
                     units: state.units,
                     type: .boolean,
                     label: "Create Events in Calendar",
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: "Create Calendar Events… bla bla bla",
+                    miniHint: "Use calendar events to display current data.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        Text(
+                            "When enabled, Trio will create a customizable calendar event to keep you notified of your current glucose reading with every successful loop cycle."
+                        )
+                        Text(
+                            "This is useful if you use CarPlay or a variety of other external services that limit the view of most apps, but allow the calendar app."
+                        )
+                        Text(
+                            "Once enabled, the available customizations will appear. You can customize with the calendar of your choosing, use of emoji labels, and the inclusion of IOB & COB data."
+                        )
+                        Text("Note: Once a new calendar event is created, the previous event will be deleted.")
+                    },
                     headerText: "Diabetes Data as Calendar Event"
                 )
 
@@ -55,15 +68,32 @@ extension CalendarEventSettings {
                         selectedVerboseHint: Binding(
                             get: { selectedVerboseHint },
                             set: {
-                                selectedVerboseHint = $0
+                                selectedVerboseHint = $0.map { AnyView($0) }
                                 hintLabel = "Display Emojis as Labels"
                             }
                         ),
                         units: state.units,
                         type: .boolean,
                         label: "Display Emojis as Labels",
-                        miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                        verboseHint: "Display Emojis as Labels… bla bla bla"
+                        miniHint: "Use emojis for calendar events. See hint for more details.",
+                        verboseHint: VStack(alignment: .leading, spacing: 10) {
+                            Text("Default: OFF").bold()
+                            VStack(alignment: .leading, spacing: 5) {
+                                Text(
+                                    "When enabled, the calendar event created will indicate whether glucose readings are in-range or out-of-range using the following color emojis:"
+                                )
+                                Text("🟢: In-Range")
+                                Text("🟠: Above-Range")
+                                Text("🔴: Below-Range")
+                            }
+                            VStack(alignment: .leading, spacing: 5) {
+                                Text(
+                                    "If \"Display IOB and COB\" is also enabled, \"IOB\" and \"COB\" will be replaced with the following emojis:"
+                                )
+                                Text("💉: IOB")
+                                Text("🥨: COB")
+                            }
+                        }
                     )
 
                     SettingInputSection(
@@ -73,15 +103,20 @@ extension CalendarEventSettings {
                         selectedVerboseHint: Binding(
                             get: { selectedVerboseHint },
                             set: {
-                                selectedVerboseHint = $0
+                                selectedVerboseHint = $0.map { AnyView($0) }
                                 hintLabel = "Display IOB and COB"
                             }
                         ),
                         units: state.units,
                         type: .boolean,
                         label: "Display IOB and COB",
-                        miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                        verboseHint: "Display IOB and COB… bla bla bla"
+                        miniHint: "Include IOB & COB in the calendar event data.",
+                        verboseHint: VStack(alignment: .leading, spacing: 10) {
+                            Text("Default: OFF").bold()
+                            Text(
+                                "When enabled, Trio will include the current IOB and COB values, along with the current glucose reading, in each calendar event created."
+                            )
+                        }
                     )
                 } else if state.useCalendar {
                     if #available(iOS 17.0, *) {
@@ -98,12 +133,13 @@ extension CalendarEventSettings {
                     }
                 }
             }
+            .listSectionSpacing(sectionSpacing)
             .sheet(isPresented: $shouldDisplayHint) {
                 SettingInputHintView(
                     hintDetent: $hintDetent,
                     shouldDisplayHint: $shouldDisplayHint,
                     hintLabel: hintLabel ?? "",
-                    hintText: selectedVerboseHint ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                     sheetTitle: "Help"
                 )
             }

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

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

+ 8 - 0
FreeAPS/Sources/Modules/ContactImage/ContactImageDataFlow.swift

@@ -0,0 +1,8 @@
+import Combine
+import Foundation
+
+enum ContactImage {
+    enum Config {}
+}
+
+protocol ContactImageProvider: Provider {}

+ 6 - 0
FreeAPS/Sources/Modules/ContactImage/ContactImageProvider.swift

@@ -0,0 +1,6 @@
+import Combine
+import Foundation
+
+extension ContactImage {
+    final class Provider: BaseProvider, ContactImageProvider {}
+}

+ 49 - 49
FreeAPS/Sources/Modules/ContactTrick/ContactTrickStateModel.swift

@@ -2,69 +2,69 @@ import ConnectIQ
 import CoreData
 import SwiftUI
 
-extension ContactTrick {
-    @Observable final class StateModel: BaseStateModel<Provider>, ContactTrickManagerDelegate {
-        @ObservationIgnored @Injected() var contactTrickStorage: ContactTrickStorage!
-        @ObservationIgnored @Injected() var contactTrickManager: ContactTrickManager!
+extension ContactImage {
+    @Observable final class StateModel: BaseStateModel<Provider>, ContactImageManagerDelegate {
+        @ObservationIgnored @Injected() var contactImageStorage: ContactImageStorage!
+        @ObservationIgnored @Injected() var contactImageManager: ContactImageManager!
 
-        var contactTrickEntries = [ContactTrickEntry]()
+        var contactImageEntries = [ContactImageEntry]()
         var units: GlucoseUnits = .mmolL
         // Help Sheet
         var isHelpSheetPresented: Bool = false
         var helpSheetDetent = PresentationDetent.large
 
         // Current state for live preview
-        var state = ContactTrickState()
+        var state = ContactImageState()
 
         /// Subscribes to updates and initializes data fetching.
         override func subscribe() {
             units = settingsManager.settings.units
-            contactTrickManager.delegate = self
+            contactImageManager.delegate = self
 
             Task {
-                /// Initial fetch to fill the ContactTrickEntry array
-                await fetchContactTrickEntriesAndUpdateUI()
+                /// Initial fetch to fill the ContactImageEntry array
+                await fetchContactImageEntriesAndUpdateUI()
 
                 // Initial state update is needed for preview
-                await contactTrickManager.updateContactTrickState()
+                await contactImageManager.updateContactImageState()
             }
         }
 
-        func contactTrickManagerDidUpdateState(_ state: ContactTrickState) {
+        func contactImageManagerDidUpdateState(_ state: ContactImageState) {
             Task { @MainActor in
                 self.state = state
             }
         }
 
-        /// Fetches all ContactTrickEntries and validates them against iOS Contacts.
-        func fetchContactTrickEntriesAndUpdateUI() async {
+        /// Fetches all ContactImageEntries and validates them against iOS Contacts.
+        func fetchContactImageEntriesAndUpdateUI() async {
             // 1. Get all entries from Core Data
-            let cdEntries = await contactTrickStorage.fetchContactTrickEntries()
+            let cdEntries = await contactImageStorage.fetchContactImageEntries()
 
             // 2. Validate entries against iOS Contacts
             let validatedEntries = await validateEntries(cdEntries)
 
             // 3. Update UI with validated entries
             await MainActor.run {
-                self.contactTrickEntries = validatedEntries
+                self.contactImageEntries = validatedEntries
             }
         }
 
         /// Validates entries against iOS Contacts and removes invalid ones
-        private func validateEntries(_ entries: [ContactTrickEntry]) async -> [ContactTrickEntry] {
-            var validated: [ContactTrickEntry] = []
+        private func validateEntries(_ entries: [ContactImageEntry]) async -> [ContactImageEntry] {
+            var validated: [ContactImageEntry] = []
 
             for entry in entries {
                 if let contactId = entry.contactId {
                     // Check if contact still exists in iOS Contacts
-                    let exists = await contactTrickManager.validateContactExists(withIdentifier: contactId)
+                    let exists = await contactImageManager.validateContactExists(withIdentifier: contactId)
 
                     if exists {
                         validated.append(entry)
                     } else {
                         // Contact was deleted in iOS, remove from Core Data
                         if let objectID = entry.managedObjectID {
-                            await contactTrickStorage.deleteContactTrickEntry(objectID)
+                            await contactImageStorage.deleteContactImageEntry(objectID)
                             debugPrint("Removed orphaned contact entry: \(entry.name)")
                         }
                     }
@@ -76,18 +76,18 @@ extension ContactTrick {
 
         /// Creates a new contact in Apple Contacts and saves it to Core Data.
         /// - Parameters:
-        ///   - entry: The ContactTrickEntry to be saved.
+        ///   - entry: The ContactImageEntry to be saved.
         ///   - name: The name of the contact.
-        func createAndSaveContactTrick(entry: ContactTrickEntry, name: String) async {
+        func createAndSaveContactImage(entry: ContactImageEntry, name: String) async {
             // 1. Check for contact access permissions.
-            let hasAccess = await contactTrickManager.requestAccess()
+            let hasAccess = await contactImageManager.requestAccess()
             guard hasAccess else {
                 debugPrint("\(DebuggingIdentifiers.failed) No access to contacts.")
                 return
             }
 
             // 2. Create the contact and retrieve its `identifier`.
-            guard let contactId = await contactTrickManager.createContact(name: name) else {
+            guard let contactId = await contactImageManager.createContact(name: name) else {
                 debugPrint("\(DebuggingIdentifiers.failed) Failed to create contact.")
                 return
             }
@@ -98,30 +98,30 @@ extension ContactTrick {
             updatedEntry.name = name
 
             // 4. Save the contact to Core Data.
-            await addContactTrickEntry(updatedEntry)
+            await addContactImageEntry(updatedEntry)
 
-            // 5. Update ContactTrickState and set the image for the newly created contact
-            await contactTrickManager.updateContactTrickState()
-            await contactTrickManager.setImageForContact(contactId: contactId)
+            // 5. Update ContactImageState and set the image for the newly created contact
+            await contactImageManager.updateContactImageState()
+            await contactImageManager.setImageForContact(contactId: contactId)
         }
 
-        /// Adds a ContactTrickEntry to Core Data.
-        /// - Parameter entry: The ContactTrickEntry to be saved.
-        func addContactTrickEntry(_ entry: ContactTrickEntry) async {
-            await contactTrickStorage.storeContactTrickEntry(entry)
-            await fetchContactTrickEntriesAndUpdateUI()
+        /// Adds a ContactImageEntry to Core Data.
+        /// - Parameter entry: The ContactImageEntry to be saved.
+        func addContactImageEntry(_ entry: ContactImageEntry) async {
+            await contactImageStorage.storeContactImageEntry(entry)
+            await fetchContactImageEntriesAndUpdateUI()
         }
 
         /// Deletes a contact from Apple Contacts and Core Data.
-        /// - Parameter entry: The ContactTrickEntry representing the contact to be deleted.
-        func deleteContact(entry: ContactTrickEntry) async {
+        /// - Parameter entry: The ContactImageEntry representing the contact to be deleted.
+        func deleteContact(entry: ContactImageEntry) async {
             guard let contactId = entry.contactId else {
                 debugPrint("\(DebuggingIdentifiers.failed) Contact does not have a valid ID.")
                 return
             }
 
             // 1. Attempt to delete the contact from Apple Contacts.
-            let contactDeleted = await contactTrickManager.deleteContact(withIdentifier: contactId)
+            let contactDeleted = await contactImageManager.deleteContact(withIdentifier: contactId)
             if contactDeleted {
                 debugPrint("\(DebuggingIdentifiers.succeeded) Contact successfully deleted from Apple Contacts: \(contactId)")
             } else {
@@ -130,33 +130,33 @@ extension ContactTrick {
 
             // 2. Delete the entry from Core Data.
             if let objectID = entry.managedObjectID {
-                await deleteContactTrick(objectID: objectID)
+                await deleteContactImage(objectID: objectID)
             }
         }
 
         /// Deletes a Core Data entry.
         /// - Parameter objectID: The Managed Object ID of the entry to be deleted.
-        func deleteContactTrick(objectID: NSManagedObjectID) async {
-            await contactTrickStorage.deleteContactTrickEntry(objectID)
-            await fetchContactTrickEntriesAndUpdateUI()
+        func deleteContactImage(objectID: NSManagedObjectID) async {
+            await contactImageStorage.deleteContactImageEntry(objectID)
+            await fetchContactImageEntriesAndUpdateUI()
         }
 
         /// Updates a contact in Apple Contacts and Core Data.
         /// - Parameters:
-        ///   - entry: The ContactTrickEntry to be updated.
-        func updateContact(with entry: ContactTrickEntry) async {
+        ///   - entry: The ContactImageEntry to be updated.
+        func updateContact(with entry: ContactImageEntry) async {
             guard let contactId = entry.contactId else {
                 debugPrint("\(DebuggingIdentifiers.failed) Contact does not have a valid ID.")
                 return
             }
 
             // 1. Update the entry in Core Data.
-            await updateContactTrick(entry)
+            await updateContactImage(entry)
 
             // 2. Update the contact in Apple Contacts.
 
             /// Update name
-            let contactUpdated = await contactTrickManager
+            let contactUpdated = await contactImageManager
                 .updateContact(withIdentifier: contactId, newName: entry.name) // TODO: - Probably not needed anymore
 
             guard contactUpdated else {
@@ -165,15 +165,15 @@ extension ContactTrick {
             }
 
             /// Update state and image
-            await contactTrickManager.updateContactTrickState()
-            await contactTrickManager.setImageForContact(contactId: contactId)
+            await contactImageManager.updateContactImageState()
+            await contactImageManager.setImageForContact(contactId: contactId)
         }
 
         /// Updates a Core Data entry.
-        /// - Parameter entry: The updated ContactTrickEntry.
-        func updateContactTrick(_ entry: ContactTrickEntry) async {
-            await contactTrickStorage.updateContactTrickEntry(entry)
-            await fetchContactTrickEntriesAndUpdateUI()
+        /// - Parameter entry: The updated ContactImageEntry.
+        func updateContactImage(_ entry: ContactImageEntry) async {
+            await contactImageStorage.updateContactImageEntry(entry)
+            await fetchContactImageEntriesAndUpdateUI()
         }
     }
 }

+ 42 - 35
FreeAPS/Sources/Modules/ContactTrick/View/AddContactTrickSheet.swift

@@ -1,27 +1,27 @@
 import SwiftUI
 
-struct AddContactTrickSheet: View {
+struct AddContactImageSheet: View {
     @Environment(\.dismiss) var dismiss
     @Environment(\.colorScheme) var colorScheme
     @Environment(AppState.self) var appState
 
-    @ObservedObject var state: ContactTrick.StateModel
+    @ObservedObject var state: ContactImage.StateModel
 
     @State private var hasHighContrast: Bool = true
-    @State private var ringWidth: ContactTrickEntry.RingWidth = .regular
-    @State private var ringGap: ContactTrickEntry.RingGap = .small
-    @State private var layout: ContactTrickLayout = .single
-    @State private var primary: ContactTrickValue = .glucose
-    @State private var top: ContactTrickValue = .none
-    @State private var bottom: ContactTrickValue = .trend
-    @State private var ring: ContactTrickLargeRing = .none
-    @State private var fontSize: ContactTrickEntry.FontSize = .regular
-    @State private var secondaryFontSize: ContactTrickEntry.FontSize = .small
+    @State private var ringWidth: ContactImageEntry.RingWidth = .regular
+    @State private var ringGap: ContactImageEntry.RingGap = .small
+    @State private var layout: ContactImageLayout = .default
+    @State private var primary: ContactImageValue = .glucose
+    @State private var top: ContactImageValue = .none
+    @State private var bottom: ContactImageValue = .trend
+    @State private var ring: ContactImageLargeRing = .none
+    @State private var fontSize: ContactImageEntry.FontSize = .regular
+    @State private var secondaryFontSize: ContactImageEntry.FontSize = .small
     @State private var fontWeight: Font.Weight = .medium
     @State private var fontWidth: Font.Width = .standard
 
-    private var previewEntry: ContactTrickEntry {
-        ContactTrickEntry(
+    private var previewEntry: ContactImageEntry {
+        ContactImageEntry(
             id: UUID(),
             name: "", // automatically set and populated
             layout: layout,
@@ -29,7 +29,7 @@ struct AddContactTrickSheet: View {
             primary: primary,
             top: top,
             bottom: bottom,
-            contactId: nil, // not needed for preview, gets set later in ContactTrickStateModel via ContactTrickManager
+            contactId: nil, // not needed for preview, gets set later in ContactImageStateModel via ContactImageManager
             hasHighContrast: hasHighContrast,
             ringWidth: ringWidth,
             ringGap: ringGap,
@@ -48,7 +48,7 @@ struct AddContactTrickSheet: View {
                     Spacer()
                     ZStack {
                         Circle()
-                            .fill(previewEntry.hasHighContrast ? .black : .white)
+                            .fill(.black)
                             .foregroundColor(.white)
                             .frame(width: 100, height: 100)
                         Image(uiImage: ContactPicture.getImage(contact: previewEntry, state: state.state))
@@ -57,7 +57,7 @@ struct AddContactTrickSheet: View {
                             .clipShape(Circle())
                         Circle()
                             .stroke(lineWidth: 2)
-                            .foregroundColor(.white)
+                            .foregroundColor(colorScheme == .dark ? .white : .black)
                             .frame(width: 100, height: 100)
                     }
                     Spacer()
@@ -69,30 +69,35 @@ struct AddContactTrickSheet: View {
                     // Layout Section
                     Section(header: Text("Style")) {
                         Picker("Layout", selection: $layout) {
-                            ForEach(ContactTrickLayout.allCases, id: \.id) { layout in
+                            ForEach(ContactImageLayout.allCases, id: \.id) { layout in
                                 Text(layout.displayName).tag(layout)
                             }
-                        }
+                        }.onChange(of: layout, { oldLayout, newLayout in
+                            if oldLayout != newLayout, newLayout == .split {
+                                top = .glucose
+                            } else {
+                                top = .none
+                            }
+                        })
                         Toggle("High Contrast Mode", isOn: $hasHighContrast)
                     }.listRowBackground(Color.chart)
 
                     // Primary Value Section
                     Section(header: Text("Display Values")) {
-                        if layout == .single {
+                        Picker("Top Value", selection: $top) {
+                            ForEach(ContactImageValue.allCases, id: \.id) { value in
+                                Text(value.displayName).tag(value)
+                            }
+                        }
+                        if layout == .default {
                             Picker("Primary", selection: $primary) {
-                                ForEach(ContactTrickValue.allCases, id: \.id) { value in
+                                ForEach(ContactImageValue.allCases, id: \.id) { value in
                                     Text(value.displayName).tag(value)
                                 }
                             }
                         }
-
-                        Picker("Top Value", selection: $top) {
-                            ForEach(ContactTrickValue.allCases, id: \.id) { value in
-                                Text(value.displayName).tag(value)
-                            }
-                        }
                         Picker("Bottom Value", selection: $bottom) {
-                            ForEach(ContactTrickValue.allCases, id: \.id) { value in
+                            ForEach(ContactImageValue.allCases, id: \.id) { value in
                                 Text(value.displayName).tag(value)
                             }
                         }
@@ -102,19 +107,19 @@ struct AddContactTrickSheet: View {
                     // Ring Settings Section
                     Section(header: Text("Ring Settings")) {
                         Picker("Ring Type", selection: $ring) {
-                            ForEach(ContactTrickLargeRing.allCases, id: \.self) { ring in
+                            ForEach(ContactImageLargeRing.allCases, id: \.self) { ring in
                                 Text(ring.displayName).tag(ring)
                             }
                         }
 
                         if ring != .none {
                             Picker("Ring Width", selection: $ringWidth) {
-                                ForEach(ContactTrickEntry.RingWidth.allCases, id: \.self) { width in
+                                ForEach(ContactImageEntry.RingWidth.allCases, id: \.self) { width in
                                     Text(width.displayName).tag(width)
                                 }
                             }
                             Picker("Ring Gap", selection: $ringGap) {
-                                ForEach(ContactTrickEntry.RingGap.allCases, id: \.self) { gap in
+                                ForEach(ContactImageEntry.RingGap.allCases, id: \.self) { gap in
                                     Text(gap.displayName).tag(gap)
                                 }
                             }
@@ -124,7 +129,9 @@ struct AddContactTrickSheet: View {
                     // Font Settings Section
                     Section(header: Text("Font Settings")) {
                         fontSizePicker
-                        secondaryFontSizePicker
+                        if layout == .split {
+                            secondaryFontSizePicker
+                        }
                         fontWeightPicker
                         fontWidthPicker
                     }.listRowBackground(Color.chart)
@@ -202,7 +209,7 @@ struct AddContactTrickSheet: View {
 
     private var fontSizePicker: some View {
         Picker("Font Size", selection: $fontSize) {
-            ForEach(ContactTrickEntry.FontSize.allCases, id: \.self) { size in
+            ForEach(ContactImageEntry.FontSize.allCases, id: \.self) { size in
                 Text(size.displayName).tag(size)
             }
         }
@@ -210,7 +217,7 @@ struct AddContactTrickSheet: View {
 
     private var secondaryFontSizePicker: some View {
         Picker("Secondary Font Size", selection: $secondaryFontSize) {
-            ForEach(ContactTrickEntry.FontSize.allCases, id: \.self) { size in
+            ForEach(ContactImageEntry.FontSize.allCases, id: \.self) { size in
                 Text(size.displayName).tag(size)
             }
         }
@@ -230,7 +237,7 @@ struct AddContactTrickSheet: View {
     private var fontWidthPicker: some View {
         Picker("Font Width", selection: $fontWidth) {
             ForEach(
-                [Font.Width.standard, Font.Width.condensed, Font.Width.expanded],
+                [Font.Width.standard, Font.Width.expanded],
                 id: \.self
             ) { width in
                 Text("\(width.displayName)".capitalized).tag(width)
@@ -241,7 +248,7 @@ struct AddContactTrickSheet: View {
     private func saveNewEntry() {
         // Save the currently previewed entry
         Task {
-            await state.createAndSaveContactTrick(entry: previewEntry, name: "Trio \(state.contactTrickEntries.count + 1)")
+            await state.createAndSaveContactImage(entry: previewEntry, name: "Trio \(state.contactImageEntries.count + 1)")
             dismiss()
         }
     }

+ 50 - 41
FreeAPS/Sources/Modules/ContactTrick/View/ContactTrickDetailView.swift

@@ -1,19 +1,19 @@
 import SwiftUI
 
-struct ContactTrickDetailView: View {
+struct ContactImageDetailView: View {
     @Environment(\.dismiss) var dismiss
     @Environment(\.colorScheme) var colorScheme
     @Environment(AppState.self) var appState
 
-    @ObservedObject var state: ContactTrick.StateModel
+    @ObservedObject var state: ContactImage.StateModel
 
-    @State private var contactTrickEntry: ContactTrickEntry
-    @State private var initialContactTrickEntry: ContactTrickEntry
+    @State private var contactImageEntry: ContactImageEntry
+    @State private var initialContactImageEntry: ContactImageEntry
 
-    init(entry: ContactTrickEntry, state: ContactTrick.StateModel) {
+    init(entry: ContactImageEntry, state: ContactImage.StateModel) {
         self.state = state
-        _contactTrickEntry = State(initialValue: entry)
-        _initialContactTrickEntry = State(initialValue: entry)
+        _contactImageEntry = State(initialValue: entry)
+        _initialContactImageEntry = State(initialValue: entry)
     }
 
     var body: some View {
@@ -23,16 +23,16 @@ struct ContactTrickDetailView: View {
                 Spacer()
                 ZStack {
                     Circle()
-                        .fill(contactTrickEntry.hasHighContrast ? .black : .white)
+                        .fill(.black)
                         .foregroundColor(.white)
                         .frame(width: 100, height: 100)
-                    Image(uiImage: ContactPicture.getImage(contact: contactTrickEntry, state: state.state))
+                    Image(uiImage: ContactPicture.getImage(contact: contactImageEntry, state: state.state))
                         .resizable()
                         .frame(width: 100, height: 100)
                         .clipShape(Circle())
                     Circle()
                         .stroke(lineWidth: 2)
-                        .foregroundColor(.white)
+                        .foregroundColor(colorScheme == .dark ? .white : .black)
                         .frame(width: 100, height: 100)
                 }
                 Spacer()
@@ -42,31 +42,38 @@ struct ContactTrickDetailView: View {
 
             Form {
                 Section(header: Text("Style")) {
-                    Picker("Layout", selection: $contactTrickEntry.layout) {
-                        ForEach(ContactTrickLayout.allCases, id: \.id) { layout in
+                    Picker("Layout", selection: $contactImageEntry.layout) {
+                        ForEach(ContactImageLayout.allCases, id: \.id) { layout in
                             Text(layout.displayName).tag(layout)
                         }
-                    }
-                    Toggle("High Contrast Mode", isOn: $contactTrickEntry.hasHighContrast)
+                    }.onChange(of: contactImageEntry.layout, { oldLayout, newLayout in
+                        if oldLayout != newLayout, newLayout == .split {
+                            contactImageEntry.top = .glucose
+                        } else {
+                            contactImageEntry.top = .none
+                        }
+                    })
+
+                    Toggle("High Contrast Mode", isOn: $contactImageEntry.hasHighContrast)
                 }.listRowBackground(Color.chart)
 
                 Section(header: Text("Display Values")) {
-                    if contactTrickEntry.layout == .single {
-                        Picker("Primary", selection: $contactTrickEntry.primary) {
-                            ForEach(ContactTrickValue.allCases, id: \.id) { value in
-                                Text(value.displayName).tag(value)
-                            }
+                    Picker("Top Value", selection: $contactImageEntry.top) {
+                        ForEach(ContactImageValue.allCases, id: \.id) { value in
+                            Text(value.displayName).tag(value)
                         }
                     }
 
-                    Picker("Top Value", selection: $contactTrickEntry.top) {
-                        ForEach(ContactTrickValue.allCases, id: \.id) { value in
-                            Text(value.displayName).tag(value)
+                    if contactImageEntry.layout == .default {
+                        Picker("Primary", selection: $contactImageEntry.primary) {
+                            ForEach(ContactImageValue.allCases, id: \.id) { value in
+                                Text(value.displayName).tag(value)
+                            }
                         }
                     }
 
-                    Picker("Bottom Value", selection: $contactTrickEntry.bottom) {
-                        ForEach(ContactTrickValue.allCases, id: \.id) { value in
+                    Picker("Bottom Value", selection: $contactImageEntry.bottom) {
+                        ForEach(ContactImageValue.allCases, id: \.id) { value in
                             Text(value.displayName).tag(value)
                         }
                     }
@@ -74,20 +81,20 @@ struct ContactTrickDetailView: View {
 
                 // Ring Settings Section
                 Section(header: Text("Ring Settings")) {
-                    Picker("Ring Type", selection: $contactTrickEntry.ring) {
-                        ForEach(ContactTrickLargeRing.allCases, id: \.self) { ring in
+                    Picker("Ring Type", selection: $contactImageEntry.ring) {
+                        ForEach(ContactImageLargeRing.allCases, id: \.self) { ring in
                             Text(ring.displayName).tag(ring)
                         }
                     }
 
-                    if contactTrickEntry.ring != .none {
-                        Picker("Ring Width", selection: $contactTrickEntry.ringWidth) {
-                            ForEach(ContactTrickEntry.RingWidth.allCases, id: \.self) { width in
+                    if contactImageEntry.ring != .none {
+                        Picker("Ring Width", selection: $contactImageEntry.ringWidth) {
+                            ForEach(ContactImageEntry.RingWidth.allCases, id: \.self) { width in
                                 Text(width.displayName).tag(width)
                             }
                         }
-                        Picker("Ring Gap", selection: $contactTrickEntry.ringGap) {
-                            ForEach(ContactTrickEntry.RingGap.allCases, id: \.self) { gap in
+                        Picker("Ring Gap", selection: $contactImageEntry.ringGap) {
+                            ForEach(ContactImageEntry.RingGap.allCases, id: \.self) { gap in
                                 Text(gap.displayName).tag(gap)
                             }
                         }
@@ -97,7 +104,9 @@ struct ContactTrickDetailView: View {
                 // Font Settings Section
                 Section(header: Text("Font Settings")) {
                     fontSizePicker
-                    secondaryFontSizePicker
+                    if contactImageEntry.layout == .split {
+                        secondaryFontSizePicker
+                    }
                     fontWeightPicker
                     fontWidthPicker
                 }.listRowBackground(Color.chart)
@@ -146,13 +155,13 @@ struct ContactTrickDetailView: View {
 
     private func saveChanges() {
         Task {
-            await state.updateContact(with: contactTrickEntry)
+            await state.updateContact(with: contactImageEntry)
             dismiss()
         }
     }
 
     var stickySaveButton: some View {
-        var isUnchanged: Bool { initialContactTrickEntry == contactTrickEntry }
+        var isUnchanged: Bool { initialContactImageEntry == contactImageEntry }
 
         return ZStack {
             Rectangle()
@@ -177,23 +186,23 @@ struct ContactTrickDetailView: View {
     }
 
     private var fontSizePicker: some View {
-        Picker("Font Size", selection: $contactTrickEntry.fontSize) {
-            ForEach(ContactTrickEntry.FontSize.allCases, id: \.self) { size in
+        Picker("Font Size", selection: $contactImageEntry.fontSize) {
+            ForEach(ContactImageEntry.FontSize.allCases, id: \.self) { size in
                 Text(size.displayName).tag(size)
             }
         }
     }
 
     private var secondaryFontSizePicker: some View {
-        Picker("Secondary Font Size", selection: $contactTrickEntry.secondaryFontSize) {
-            ForEach(ContactTrickEntry.FontSize.allCases, id: \.self) { size in
+        Picker("Secondary Font Size", selection: $contactImageEntry.secondaryFontSize) {
+            ForEach(ContactImageEntry.FontSize.allCases, id: \.self) { size in
                 Text(size.displayName).tag(size)
             }
         }
     }
 
     private var fontWeightPicker: some View {
-        Picker("Font Weight", selection: $contactTrickEntry.fontWeight) {
+        Picker("Font Weight", selection: $contactImageEntry.fontWeight) {
             ForEach(
                 [Font.Weight.light, Font.Weight.regular, Font.Weight.medium, Font.Weight.bold, Font.Weight.black],
                 id: \.self
@@ -204,9 +213,9 @@ struct ContactTrickDetailView: View {
     }
 
     private var fontWidthPicker: some View {
-        Picker("Font Width", selection: $contactTrickEntry.fontWidth) {
+        Picker("Font Width", selection: $contactImageEntry.fontWidth) {
             ForEach(
-                [Font.Width.standard, Font.Width.condensed, Font.Width.expanded],
+                [Font.Width.standard, Font.Width.expanded],
                 id: \.self
             ) { width in
                 Text("\(width.displayName)".capitalized).tag(width)

+ 10 - 10
FreeAPS/Sources/Modules/ContactTrick/View/ContactTrickRootView.swift

@@ -3,7 +3,7 @@ import ContactsUI
 import SwiftUI
 import Swinject
 
-extension ContactTrick {
+extension ContactImage {
     struct RootView: BaseView {
         let resolver: Resolver
         @State var state = StateModel()
@@ -14,7 +14,7 @@ extension ContactTrick {
 
         var body: some View {
             Form {
-                contactTrickList
+                contactItemsList
             }
             .onAppear(perform: configureView)
             .navigationTitle("Contacts Configuration")
@@ -34,13 +34,13 @@ extension ContactTrick {
                 }
             }
             .sheet(isPresented: $isAddSheetPresented) {
-                AddContactTrickSheet(state: state)
+                AddContactImageSheet(state: state)
             }
         }
 
-        private var contactTrickList: some View {
+        private var contactItemsList: some View {
             List {
-                if state.contactTrickEntries.isEmpty {
+                if state.contactImageEntries.isEmpty {
                     Section(
                         header: Text(""),
                         content: {
@@ -48,12 +48,12 @@ extension ContactTrick {
                         }
                     ).listRowBackground(Color.chart)
                 } else {
-                    ForEach(state.contactTrickEntries, id: \.id) { entry in
-                        NavigationLink(destination: ContactTrickDetailView(entry: entry, state: state)) {
+                    ForEach(state.contactImageEntries, id: \.id) { entry in
+                        NavigationLink(destination: ContactImageDetailView(entry: entry, state: state)) {
                             HStack {
                                 ZStack {
                                     Circle()
-                                        .fill(entry.hasHighContrast ? .black : .white)
+                                        .fill(.black)
                                         .foregroundColor(.white)
                                         .frame(width: 40, height: 40)
 
@@ -64,7 +64,7 @@ extension ContactTrick {
 
                                     Circle()
                                         .stroke(lineWidth: 2)
-                                        .foregroundColor(.white)
+                                        .foregroundColor(colorScheme == .dark ? .white : .black)
                                         .frame(width: 40, height: 40)
                                 }
 
@@ -80,7 +80,7 @@ extension ContactTrick {
         private func onDelete(offsets: IndexSet) {
             Task {
                 for offset in offsets {
-                    let entry = state.contactTrickEntries[offset]
+                    let entry = state.contactImageEntries[offset]
                     await state.deleteContact(entry: entry)
                 }
             }

+ 0 - 8
FreeAPS/Sources/Modules/ContactTrick/ContactTrickDataFlow.swift

@@ -1,8 +0,0 @@
-import Combine
-import Foundation
-
-enum ContactTrick {
-    enum Config {}
-}
-
-protocol ContactTrickProvider: Provider {}

+ 0 - 6
FreeAPS/Sources/Modules/ContactTrick/ContactTrickProvider.swift

@@ -1,6 +0,0 @@
-import Combine
-import Foundation
-
-extension ContactTrick {
-    final class Provider: BaseProvider, ContactTrickProvider {}
-}

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

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

Fichier diff supprimé car celui-ci est trop grand
+ 154 - 33
FreeAPS/Sources/Modules/DynamicSettings/View/DynamicSettingsRootView.swift


+ 1 - 1
FreeAPS/Sources/Modules/GeneralSettings/UnitsLimitsSettingsProvider.swift

@@ -16,7 +16,7 @@ extension UnitsLimitsSettings {
         func settings() -> PumpSettings {
             storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self)
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
-                ?? PumpSettings(insulinActionCurve: 6.0, maxBolus: 10, maxBasal: 2)
+                ?? PumpSettings(insulinActionCurve: 10.0, maxBolus: 10, maxBasal: 2)
         }
 
         func save(settings: PumpSettings) -> AnyPublisher<Void, Error> {

Fichier diff supprimé car celui-ci est trop grand
+ 57 - 20
FreeAPS/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift


Fichier diff supprimé car celui-ci est trop grand
+ 89 - 32
FreeAPS/Sources/Modules/GlucoseNotificationSettings/View/GlucoseNotificationSettingsRootView.swift


+ 21 - 16
FreeAPS/Sources/Modules/HealthKit/View/AppleHealthKitRootView.swift

@@ -8,7 +8,7 @@ extension AppleHealthKit {
 
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: String?
+        @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
@@ -17,7 +17,7 @@ extension AppleHealthKit {
         @Environment(AppState.self) var appState
 
         var body: some View {
-            Form {
+            List {
                 SettingInputSection(
                     decimalValue: $decimalPlaceholder,
                     booleanValue: $state.useAppleHealth,
@@ -25,18 +25,20 @@ extension AppleHealthKit {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = "Connect to Apple Health"
                         }
                     ),
                     units: state.units,
                     type: .boolean,
                     label: "Connect to Apple Health",
-                    miniHint: "Allows Trio to read from and write to Apple Health.",
-                    verboseHint: NSLocalizedString(
-                        "This allows Trio to read from and write to Apple Health. You must also give permissions in iOS Settings > Health > Data Access. If you enter a glucose value into Apple Health, open Trio to confirm it shows up.",
-                        comment: "Suspend Zeros IOB"
-                    ),
+                    miniHint: "Allow Trio to read from and write to Apple Health.",
+                    verboseHint:
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        Text("This allows Trio to read from and write to Apple Health.")
+                        Text("Warning: You must also give permissions in iOS System Settings for the Health app.").bold()
+                    },
                     headerText: "Apple Health Integration"
                 )
 
@@ -47,25 +49,28 @@ extension AppleHealthKit {
                                 Image(systemName: "exclamationmark.circle.fill")
                                 Text("Give Apple Health Write Permissions")
                             }.padding(.bottom)
-                            Text("""
-                            1. Open the Settings app on your iOS device.
-                            2. Scroll down and select "Health."
-                            3. Tap on "Data Access & Devices."
-                            4. Find and select "Trio" from the list of apps.
-                            5. Ensure that the "Write Data" option is enabled for the desired health metrics.
-                            """).font(.footnote)
+                            VStack(alignment: .leading, spacing: 5) {
+                                Text("1. Open the Settings app on your iOS device.")
+                                Text(
+                                    "2. Scroll down or type \"Health\" in the settings search bar and select the \"Health\" app."
+                                )
+                                Text("3. Tap on \"Data Access & Devices\".")
+                                Text("4. Find and select \"Trio\" from the list of apps.")
+                                Text("5. Ensure that the \"Write Data\" option is enabled for the desired health metrics.")
+                            }.font(.footnote)
                         }
                         .padding(.vertical)
                         .foregroundColor(Color.secondary)
                     }.listRowBackground(Color.chart)
                 }
             }
+            .listSectionSpacing(sectionSpacing)
             .sheet(isPresented: $shouldDisplayHint) {
                 SettingInputHintView(
                     hintDetent: $hintDetent,
                     shouldDisplayHint: $shouldDisplayHint,
                     hintLabel: hintLabel ?? "",
-                    hintText: selectedVerboseHint ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                     sheetTitle: "Help"
                 )
             }

+ 1 - 1
FreeAPS/Sources/Modules/Home/HomeProvider.swift

@@ -36,7 +36,7 @@ extension Home {
         func pumpSettings() -> PumpSettings {
             storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self)
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
-                ?? PumpSettings(insulinActionCurve: 6, maxBolus: 10, maxBasal: 2)
+                ?? PumpSettings(insulinActionCurve: 10, maxBolus: 10, maxBasal: 2)
         }
 
         func pumpReservoir() -> Decimal? {

+ 3 - 3
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -59,7 +59,7 @@ extension Home {
         var highGlucose: Decimal = 180
         var currentGlucoseTarget: Decimal = 100
         var glucoseColorScheme: GlucoseColorScheme = .staticColor
-        var overrideUnit: Bool = false
+        var hbA1cDisplayUnit: HbA1cDisplayUnit = .percent
         var displayXgridLines: Bool = false
         var displayYgridLines: Bool = false
         var thresholdLines: Bool = false
@@ -357,7 +357,7 @@ extension Home {
             maxValue = settingsManager.preferences.autosensMax
             lowGlucose = settingsManager.settings.low
             highGlucose = settingsManager.settings.high
-            overrideUnit = settingsManager.settings.overrideHbA1cUnit
+            hbA1cDisplayUnit = settingsManager.settings.hbA1cDisplayUnit
             displayXgridLines = settingsManager.settings.xGridLines
             displayYgridLines = settingsManager.settings.yGridLines
             thresholdLines = settingsManager.settings.rulerMarks
@@ -574,7 +574,7 @@ extension Home.StateModel:
         Task {
             await getCurrentGlucoseTarget()
         }
-        overrideUnit = settingsManager.settings.overrideHbA1cUnit
+        hbA1cDisplayUnit = settingsManager.settings.hbA1cDisplayUnit
         glucoseColorScheme = settingsManager.settings.glucoseColorScheme
         displayXgridLines = settingsManager.settings.xGridLines
         displayYgridLines = settingsManager.settings.yGridLines

+ 100 - 37
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -876,6 +876,7 @@ extension Home {
                 Button("Medtronic") { state.addPump(.minimed) }
                 Button("Omnipod Eros") { state.addPump(.omnipod) }
                 Button("Omnipod Dash") { state.addPump(.omnipodBLE) }
+                Button("Dana(RS/-i)") { state.addPump(.dana) }
                 Button("Pump Simulator") { state.addPump(.simulator) }
             } message: { Text("Select Pump Model") }
             .sheet(isPresented: $state.setupPump) {
@@ -937,22 +938,30 @@ extension Home {
             List {
                 DefinitionRow(
                     term: "IOB (Insulin on Board)",
-                    definition: "Forecasts BG based on the amount of insulin still active in the body.",
+                    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: "Forecasts the worst-case blood glucose (BG) scenario if no carbs are absorbed and insulin delivery is stopped until BG starts rising.",
+                    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: "Forecasts BG changes by considering the amount of carbohydrates still being absorbed in the body.",
+                    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: "Forecasts BG levels and insulin dosing needs for unexpected meals or other causes of BG rises without prior notice.",
+                    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
                 )
             }
@@ -964,11 +973,14 @@ extension Home {
             List {
                 DefinitionRow(
                     term: "Cone of Uncertainty",
-                    definition: """
-                    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 algorithm's result.
-
-                    To modify the forecast display type, go to Trio Settings > Features > User Interface > Forecast Display Type.
-                    """,
+                    definition: VStack {
+                        Text(
+                            "For simplicity reasons, oref's various forecast curves are displayed as a \"Cone of Uncertainty\" that depicts a possible, forecasted range of future glucose fluctuation based on the current data and the algothim's result."
+                        )
+                        Text(
+                            "Note: To modify the forecast display type, go to Trio Settings > Features > User Interface > Forecast Display Type."
+                        )
+                    },
                     color: Color.blue.opacity(0.5)
                 )
             }
@@ -1046,38 +1058,89 @@ extension Home {
             }
         }
 
-        private func parseReasonConclusion(_ reasonConclusion: String, isMmolL: Bool) -> String {
-            var updatedConclusion = reasonConclusion
-
-            // Handle "minGuardBG x<y" pattern
-            if let range = updatedConclusion.range(of: "minGuardBG\\s*-?\\d+<\\d+", options: .regularExpression) {
-                let matchedString = updatedConclusion[range]
-                let parts = matchedString.components(separatedBy: "<")
-
-                if let firstValue = Double(parts[0].components(separatedBy: CharacterSet.decimalDigits.inverted).joined()),
-                   let secondValue = Double(parts[1])
-                {
-                    let formattedFirstValue = isMmolL ? Double(firstValue.asMmolL) : firstValue
-                    let formattedSecondValue = isMmolL ? Double(secondValue.asMmolL) : secondValue
-
-                    let formattedString = "minGuardBG \(formattedFirstValue)<\(formattedSecondValue)"
-                    updatedConclusion = updatedConclusion.replacingOccurrences(of: matchedString, with: formattedString)
+        //TODO: Consolidate all mmol parsing methods (in TagCloudView, NightscoutManager and HomeRootView) to one central func
+        private func parseReasonConclusion(_ reasonConclusion: String, isMmolL _: Bool) -> String {
+            let patterns = [
+                "minGuardBG\\s*-?\\d+\\.?\\d*<-?\\d+\\.?\\d*",
+                "Eventual BG\\s*-?\\d+\\.?\\d*\\s*>=\\s*-?\\d+\\.?\\d*",
+                "\\S+\\s+-?\\d+\\.?\\d*\\s*>\\s*\\d+%\\s+of\\s+BG\\s+-?\\d+\\.?\\d*"
+            ]
+            let pattern = patterns.joined(separator: "|")
+            let regex = try! NSRegularExpression(pattern: pattern)
+
+            func convertToMmolL(_ value: String) -> String {
+                if let glucoseValue = Double(value.replacingOccurrences(of: "[^\\d.-]", with: "", options: .regularExpression)) {
+                    let mmolValue = Decimal(glucoseValue).asMmolL
+                    return mmolValue.description
                 }
+                return value
             }
 
-            // Handle "Eventual BG x >= target" pattern
-            if let range = updatedConclusion.range(of: "Eventual BG\\s*\\d+\\s*>?=\\s*\\d+", options: .regularExpression) {
-                let matchedString = updatedConclusion[range]
-                let parts = matchedString.components(separatedBy: " >= ")
-
-                if let firstValue = Double(parts[0].components(separatedBy: CharacterSet.decimalDigits.inverted).joined()),
-                   let secondValue = Double(parts[1])
-                {
-                    let formattedFirstValue = isMmolL ? Double(firstValue.asMmolL) : firstValue
-                    let formattedSecondValue = isMmolL ? Double(secondValue.asMmolL) : secondValue
+            let matches = regex.matches(
+                in: reasonConclusion,
+                range: NSRange(reasonConclusion.startIndex..., in: reasonConclusion)
+            )
+            var updatedConclusion = reasonConclusion
 
-                    let formattedString = "Eventual BG \(formattedFirstValue) >= \(formattedSecondValue)"
-                    updatedConclusion = updatedConclusion.replacingOccurrences(of: matchedString, with: formattedString)
+            for match in matches.reversed() {
+                guard let range = Range(match.range, in: reasonConclusion) else { continue }
+                let matchedString = String(reasonConclusion[range])
+
+                if matchedString.contains("<") {
+                    // Handle "minGuardBG x<y" pattern
+                    let parts = matchedString.components(separatedBy: "<")
+                    if parts.count == 2,
+                       let firstValue = Double(
+                           parts[0]
+                               .components(separatedBy: CharacterSet(charactersIn: "0123456789.-").inverted).joined()
+                       ),
+                       let secondValue = Double(
+                           parts[1]
+                               .components(separatedBy: CharacterSet(charactersIn: "0123456789.-").inverted).joined()
+                       )
+                    {
+                        let formattedFirstValue = convertToMmolL(String(firstValue))
+                        let formattedSecondValue = convertToMmolL(String(secondValue))
+                        let formattedString = "minGuardBG \(formattedFirstValue)<\(formattedSecondValue)"
+                        updatedConclusion.replaceSubrange(range, with: formattedString)
+                    }
+                } else if matchedString.contains(">=") {
+                    // Handle "Eventual BG x >= target" pattern
+                    let parts = matchedString.components(separatedBy: " >= ")
+                    if parts.count == 2,
+                       let firstValue = Double(
+                           parts[0]
+                               .components(separatedBy: CharacterSet(charactersIn: "0123456789.-").inverted).joined()
+                       ),
+                       let secondValue = Double(
+                           parts[1]
+                               .components(separatedBy: CharacterSet(charactersIn: "0123456789.-").inverted).joined()
+                       )
+                    {
+                        let formattedFirstValue = convertToMmolL(String(firstValue))
+                        let formattedSecondValue = convertToMmolL(String(secondValue))
+                        let formattedString = "Eventual BG \(formattedFirstValue) >= \(formattedSecondValue)"
+                        updatedConclusion.replaceSubrange(range, with: formattedString)
+                    }
+                } else if matchedString.contains(">") {
+                    // Handle "maxDelta 37 > 20% of BG 95" style
+                    let pattern = "(\\S+)\\s+(-?\\d+\\.?\\d*)\\s*>\\s*(\\d+)%\\s+of\\s+BG\\s+(-?\\d+\\.?\\d*)"
+                    let localRegex = try! NSRegularExpression(pattern: pattern)
+                    if let localMatch = localRegex.firstMatch(
+                        in: matchedString,
+                        range: NSRange(matchedString.startIndex..., in: matchedString)
+                    ) {
+                        let metric = String(matchedString[Range(localMatch.range(at: 1), in: matchedString)!])
+                        let firstValue = String(matchedString[Range(localMatch.range(at: 2), in: matchedString)!])
+                        let percentage = String(matchedString[Range(localMatch.range(at: 3), in: matchedString)!])
+                        let bgValue = String(matchedString[Range(localMatch.range(at: 4), in: matchedString)!])
+
+                        let formattedFirstValue = convertToMmolL(firstValue)
+                        let formattedBGValue = convertToMmolL(bgValue)
+
+                        let formattedString = "\(metric) \(formattedFirstValue) > \(percentage)% of BG \(formattedBGValue)"
+                        updatedConclusion.replaceSubrange(range, with: formattedString)
+                    }
                 }
             }
 

+ 48 - 8
FreeAPS/Sources/Modules/LiveActivitySettings/View/LiveActivitySettingsRootView.swift

@@ -9,7 +9,7 @@ extension LiveActivitySettings {
 
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: String?
+        @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
@@ -52,15 +52,32 @@ extension LiveActivitySettings {
                         selectedVerboseHint: Binding(
                             get: { selectedVerboseHint },
                             set: {
-                                selectedVerboseHint = $0
+                                selectedVerboseHint = $0.map { AnyView($0) }
                                 hintLabel = "Enable Live Activity"
                             }
                         ),
                         units: state.units,
                         type: .boolean,
                         label: "Enable Live Activity",
-                        miniHint: "Live Activities display Trio's glucose readings, and other current data on the iPhone Lock Screen and in the Dynamic Island",
-                        verboseHint: "With Live Activities, you can let Trio display most current data, e.g. glucose reading from CGM, insulin on board, carbohydrates on board, or even a glucose trend chart, on the iPhone Lock Screen and in the Dynamic Island. It allows you to refer to live information at a glance and perform quick actions in your diabetes management.",
+                        miniHint: "Display customizable data on Lock Screen and Dynamic Island.",
+                        verboseHint: VStack(alignment: .leading, spacing: 10) {
+                            Text("Default: OFF").bold()
+                            VStack(alignment: .leading, spacing: 10) {
+                                Text(
+                                    "With Live Activities enabled, Trio displays your choice of the following current data on your iPhone's Lock Screen and in the Dynamic Island:"
+                                )
+                                VStack(alignment: .leading) {
+                                    Text("• Current Glucose Reading")
+                                    Text("• IOB: Insulin On Board")
+                                    Text("• COB: Carbohydrates On Board")
+                                    Text("• Last Updated: Time of Last Loop Cycle")
+                                    Text("• Glucose Trend Chart")
+                                }.font(.footnote)
+                                Text(
+                                    "It allows you to refer to live information at a glance and perform quick actions in your diabetes management."
+                                )
+                            }
+                        },
                         headerText: "Display Live Data From Trio"
                     )
 
@@ -76,9 +93,9 @@ extension LiveActivitySettings {
                                     }
                                 }.padding(.top)
 
-                                HStack(alignment: .top) {
+                                HStack(alignment: .center) {
                                     Text(
-                                        "Trio Live Activities can be simplistic or detailed in their information display. See hint for more details."
+                                        "Select simple or detailed style. See hint for more details."
                                     )
                                     .font(.footnote)
                                     .foregroundColor(.secondary)
@@ -88,7 +105,29 @@ extension LiveActivitySettings {
                                         action: {
                                             hintLabel = "Lock Screen Widget Style"
                                             selectedVerboseHint =
-                                                "Trio's simple lock screen widget only display current glucose reading, trend arrow, delta and the timestamp of the current reading.\n\nThe detailed Lock Screen widget offers users a glucose chart, glucose trend arrow, glucose delta, current insulin and carbohydrates on board, and an icon as an indicator for running overrides."
+                                                AnyView(
+                                                    VStack(alignment: .leading, spacing: 10) {
+                                                        Text("Default: Simple").bold()
+                                                        VStack(alignment: .leading, spacing: 10) {
+                                                            Text("Simple:").bold()
+                                                            Text(
+                                                                "Trio's Simple Lock Screen Widget displays current glucose reading, trend arrow, delta and the timestamp of the current reading."
+                                                            )
+                                                        }
+                                                        VStack(alignment: .leading, spacing: 10) {
+                                                            Text("Detailed:").bold()
+                                                            Text(
+                                                                "The Detailed Lock Screen Widget offers users a glucose chart as well as the ability to customize the information provided in the Detailed Widget using the following options:"
+                                                            )
+                                                        }
+                                                        VStack(alignment: .leading) {
+                                                            Text("• Current Glucose Reading")
+                                                            Text("• IOB: Insulin On Board")
+                                                            Text("• COB: Carbohydrates On Board")
+                                                            Text("• Last Updated: Time of Last Loop Cycle")
+                                                        }.font(.footnote)
+                                                    }
+                                                )
                                             shouldDisplayHint.toggle()
                                         },
                                         label: {
@@ -115,6 +154,7 @@ extension LiveActivitySettings {
                     }
                 }
             }
+            .listSectionSpacing(sectionSpacing)
             .onReceive(resolver.resolve(LiveActivityBridge.self)!.$systemEnabled, perform: {
                 self.systemLiveActivitySetting = $0
             })
@@ -123,7 +163,7 @@ extension LiveActivitySettings {
                     hintDetent: $hintDetent,
                     shouldDisplayHint: $shouldDisplayHint,
                     hintLabel: hintLabel ?? "",
-                    hintText: selectedVerboseHint ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                     sheetTitle: "Help"
                 )
             }

+ 121 - 30
FreeAPS/Sources/Modules/MealSettings/View/MealSettingsRootView.swift

@@ -9,7 +9,7 @@ extension MealSettings {
 
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: String?
+        @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
@@ -41,7 +41,7 @@ extension MealSettings {
         }
 
         var body: some View {
-            Form {
+            List {
                 Section(
                     header: Text("Limits per Entry"),
                     content: {
@@ -146,9 +146,9 @@ extension MealSettings {
                                 }
                             }
 
-                            HStack(alignment: .top) {
+                            HStack(alignment: .center) {
                                 Text(
-                                    "Set limits for entering meals in treatment view."
+                                    "Set limits for each type of macro per meal entry."
                                 )
                                 .lineLimit(nil)
                                 .font(.footnote)
@@ -158,7 +158,17 @@ extension MealSettings {
                                 Button(
                                     action: {
                                         hintLabel = "Limits per Entry"
-                                        selectedVerboseHint = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
+                                        selectedVerboseHint =
+                                            AnyView(
+                                                VStack(alignment: .leading, spacing: 5) {
+                                                    Text("Max Carbs:").bold()
+                                                    Text("Enter the largest carbohydrate value allowed per meal entry")
+                                                    Text("Max Fat:").bold()
+                                                    Text("Enter the largest fat value allowed per meal entry")
+                                                    Text("Max Protein:").bold()
+                                                    Text("Enter the largest protein value allowed per meal entry")
+                                                }
+                                            )
                                         shouldDisplayHint.toggle()
                                     },
                                     label: {
@@ -179,18 +189,51 @@ extension MealSettings {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
-                            hintLabel = "Display and Allow Fat and Protein Entries"
+                            selectedVerboseHint = $0.map { AnyView($0) }
+                            hintLabel = "Enable Fat and Protein Entries"
                         }
                     ),
                     units: state.units,
                     type: .boolean,
-                    label: "Display and Allow Fat and Protein Entries",
-                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                    verboseHint: "Allows fat and protein to be converted into future carb equivalents using the Warsaw formula of kilocalories divided by 10.\n\nDefaults: Spread Duration: 8 h, Spread Interval: 30 min, FPU Factor: 0.5, Delay 60 min.",
+                    label: "Enable Fat and Protein Entries",
+                    miniHint: "Add fat and protein macros to meal entries.",
+                    verboseHint: VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        VStack(spacing: 10) {
+                            Text(
+                                "Enabling this setting allows you to log fat and protein, which are then converted into future carb equivalents using the Warsaw Method."
+                            )
+                            VStack(alignment: .leading, spacing: 5) {
+                                Text("Warsaw Method:").bold()
+                                Text(
+                                    "The Warsaw Method helps account for the delayed glucose spikes caused by fat and protein in meals. It uses Fat-Protein Units (FPU) to calculate the carb effect from fat and protein. The system spreads insulin delivery over several hours to mimic natural insulin release, helping to manage post-meal glucose spikes."
+                                )
+                            }
+                            VStack(alignment: .center, spacing: 5) {
+                                Text("Fat Conversion").bold()
+                                Text("𝑭 = fat(g) × 90%")
+                            }
+                            VStack(alignment: .center, spacing: 5) {
+                                Text("Protein Conversion").bold()
+                                Text("𝑷 = protein(g) × 40%")
+                            }
+                            VStack(alignment: .center, spacing: 5) {
+                                Text("FPU Conversion").bold()
+                                Text("𝑭 + 𝑷 = g CHO")
+                            }
+                            VStack(alignment: .leading, spacing: 5) {
+                                Text(
+                                    "You can personalize the conversion calculation by adjusting the following settings that will appear when this option is enabled:"
+                                )
+                                Text("• Fat and Protein Delay")
+                                Text("• Maximum Duration")
+                                Text("• Spread Interval")
+                                Text("• Fat and Protein Percentage")
+                            }
+                        }
+                    },
                     headerText: "Fat and Protein"
                 )
-
                 if state.useFPUconversion {
                     SettingInputSection(
                         decimalValue: $state.delay,
@@ -199,15 +242,24 @@ extension MealSettings {
                         selectedVerboseHint: Binding(
                             get: { selectedVerboseHint },
                             set: {
-                                selectedVerboseHint = $0
+                                selectedVerboseHint = $0.map { AnyView($0) }
                                 hintLabel = "Fat and Protein Delay"
                             }
                         ),
                         units: state.units,
                         type: .decimal("delay"),
                         label: "Fat and Protein Delay",
-                        miniHint: "Delay is time from now until the first future carb entry.",
-                        verboseHint: "X-Axis Interval Step… bla bla bla"
+                        miniHint: "Delay between fat & protein entry and first FPU entry.",
+                        verboseHint:
+                        VStack(alignment: .leading, spacing: 10) {
+                            Text("Default: 60 min").bold()
+                            Text(
+                                "The Fat and Protein Delay setting defines the time between when you log fat and protein and when the system starts delivering insulin for the Fat-Protein Unit Carb Equivalents (FPUs)."
+                            )
+                            Text(
+                                "This delay accounts for the slower absorption of fat and protein, as calculated by the Warsaw Method, ensuring insulin delivery is properly timed to manage glucose spikes caused by high-fat, high-protein meals."
+                            )
+                        }
                     )
 
                     SettingInputSection(
@@ -217,15 +269,27 @@ extension MealSettings {
                         selectedVerboseHint: Binding(
                             get: { selectedVerboseHint },
                             set: {
-                                selectedVerboseHint = $0
-                                hintLabel = "Maximum Duration (hours)"
+                                selectedVerboseHint = $0.map { AnyView($0) }
+                                hintLabel = "Maximum Duration"
                             }
                         ),
                         units: state.units,
                         type: .decimal("timeCap"),
-                        label: "Maximum Duration (hours)",
-                        miniHint: "Carb spread over a maximum number of hours (5-12).",
-                        verboseHint: "This spreads the carb equivilants over a maximum duration setting that can be configured from 5-12 hours."
+                        label: "Maximum Duration",
+                        miniHint: "Set the maximum timeframe to extend FPUs.",
+                        verboseHint:
+                        VStack(alignment: .leading, spacing: 10) {
+                            Text("Default: 8 hours").bold()
+                            Text(
+                                "This sets the maximum length of time that Fat and Protein Carb Equivalents (FPUs) will be extended over from a single Fat and/or Protein bolus calcultor entry."
+                            )
+                            Text(
+                                "It is one factor used in combination with the Fat and Protein Delay, Spread Interval, and Fat and Protein Factor to create the FPU entries."
+                            )
+                            Text("Increasing this setting may result in more FPU entries with smaller carb values.")
+                            Text("Decreasing this setting may result in fewer FPU entries with larger carb values.")
+                            Text("Note: Accepted range for this setting is 5 - 12 hours.")
+                        }
                     )
 
                     SettingInputSection(
@@ -235,15 +299,25 @@ extension MealSettings {
                         selectedVerboseHint: Binding(
                             get: { selectedVerboseHint },
                             set: {
-                                selectedVerboseHint = $0
-                                hintLabel = "Spread Interval (minutes)"
+                                selectedVerboseHint = $0.map { AnyView($0) }
+                                hintLabel = "Spread Interval"
                             }
                         ),
                         units: state.units,
                         type: .decimal("minuteInterval"),
-                        label: "Spread Interval (minutes)",
-                        miniHint: "Interval in minutes is how many minutes are between entries.",
-                        verboseHint: "Interval in minutes is how many minutes are between entries. The shorter the interval, the smoother the result. 10, 15, 20, 30, or 60 are reasonable choices."
+                        label: "Spread Interval",
+                        miniHint: "Time interval between FPUs.",
+                        verboseHint:
+                        VStack(alignment: .leading, spacing: 10) {
+                            Text("Default: 30 minutes").bold()
+                            Text(
+                                "This determines how many minutes will be between individual Fat-Protein Unit Carb Equivalent (FPU) entries from a single Fat and/or Protein bolus calculator entry."
+                            )
+                            Text("The shorter the interval, the smoother the correlating dosing result.")
+                            Text("Increasing this setting may result in fewer FPU entries with larger carb values.")
+                            Text("Decreasing this setting may result in more FPU entries with smaller carb values.")
+                            Text("Accepted range for this setting is 5 - 60 minutes.")
+                        }
                     )
 
                     SettingInputSection(
@@ -253,24 +327,41 @@ extension MealSettings {
                         selectedVerboseHint: Binding(
                             get: { selectedVerboseHint },
                             set: {
-                                selectedVerboseHint = $0
-                                hintLabel = "Fat and Protein Factor"
+                                selectedVerboseHint = $0.map { AnyView($0) }
+                                hintLabel = "Fat and Protein Percentage"
                             }
                         ),
                         units: state.units,
                         type: .decimal("individualAdjustmentFactor"),
-                        label: "Fat and Protein Factor",
-                        miniHint: "Influences how many carb equivalents are recorded for fat and protein.",
-                        verboseHint: "The Fat and Protein Factor influences how much effect the fat and protein has on the entries. 1.0 is full effect (original Warsaw Method) and 0.5 is half effect. Note that you may find that your normal carb ratio needs to increase to a larger number if you begin adding fat and protein entries. For this reason, it is best to start with a factor of about 0.5 to ease into it."
+                        label: "Fat and Protein Percentage",
+                        miniHint: "Adjust the Warsaw Method FPU Conversion rate.",
+                        verboseHint: VStack(alignment: .leading, spacing: 10) {
+                            Text("Default: 50%").bold()
+                            VStack(spacing: 10) {
+                                Text("This setting changes how much effect the fat and protein entry has on FPUs.")
+                                VStack(alignment: .center, spacing: 5) {
+                                    Text("50% is half effect:").bold()
+                                    Text("(Fat × 45%) + (Protein × 20%)")
+                                    Text("100% is full effect:").bold()
+                                    Text("(Fat × 90%) + (Protein × 40%)")
+                                    Text("200% is double effect:").bold()
+                                    Text("(Fat × 180%) + (Protein x 80%)")
+                                }
+                                Text(
+                                    "Tip: You may find that your normal carb ratio needs to increase to a larger number when you begin adding fat and protein entries. For this reason, it is best to start with a factor of about 50%."
+                                )
+                            }
+                        }
                     )
                 }
             }
+            .listSectionSpacing(sectionSpacing)
             .sheet(isPresented: $shouldDisplayHint) {
                 SettingInputHintView(
                     hintDetent: $hintDetent,
                     shouldDisplayHint: $shouldDisplayHint,
                     hintLabel: hintLabel ?? "",
-                    hintText: selectedVerboseHint ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                     sheetTitle: "Help"
                 )
             }

+ 1 - 1
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigProvider.swift

@@ -16,7 +16,7 @@ extension NightscoutConfig {
         func getPumpSettings() -> PumpSettings {
             storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self)
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
-                ?? PumpSettings(insulinActionCurve: 6.0, maxBolus: 10, maxBasal: 2)
+                ?? PumpSettings(insulinActionCurve: 10.0, maxBolus: 10, maxBasal: 2)
         }
 
         func savePumpSettings(settings: PumpSettings) -> AnyPublisher<Void, Error> {

+ 36 - 14
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift

@@ -12,7 +12,7 @@ extension NightscoutConfig {
         @State var importedHasRun = false
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: String?
+        @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
@@ -22,7 +22,7 @@ extension NightscoutConfig {
 
         var body: some View {
             ZStack {
-                Form {
+                List {
                     Section(
                         header: Text("Nightscout Integration"),
                         content: {
@@ -50,15 +50,18 @@ extension NightscoutConfig {
                             Button {
                                 importAlert = Alert(
                                     title: Text("Import Therapy Settings?"),
-                                    message: Text(
-                                        "Are you sure you want to import profile settings from Nightscout?\n\nThis will overwrite the following Trio therapy settings: Basal Rates, Insulin Sensitivities, Carb Ratios, Target Glucose, and Duration of Insulin Action."
-                                    ),
+                                    message: Text("Are you sure you want to import profile settings from Nightscout?\n\n")
+                                        + Text("This will overwrite the following Trio therapy settings:\n")
+                                        + Text("• Basal Rates\n")
+                                        + Text("• Insulin Sensitivities\n")
+                                        + Text("• Carb Ratios\n")
+                                        + Text("• Glucose Targets\n")
+                                        + Text("• Duration of Insulin Action"),
                                     primaryButton: .default(
                                         Text("Yes, Import!"),
                                         action: {
                                             Task {
                                                 await state.importSettings()
-                                                // Check the import status and errors after the import process finishes
                                                 if state.importStatus == .failed, state.importErrors.isNotEmpty,
                                                    let errorMessage = state.importErrors.first
                                                 {
@@ -84,9 +87,9 @@ extension NightscoutConfig {
                                 .buttonStyle(.bordered)
                                 .disabled(state.url.isEmpty || state.connecting)
 
-                            HStack(alignment: .top) {
+                            HStack(alignment: .center) {
                                 Text(
-                                    "You can import therapy settings from Nightscout. See hint for more information which settings will be overwritten."
+                                    "Import therapy settings from Nightscout.\nSee hint for the list of settings available for import."
                                 )
                                 .font(.footnote)
                                 .foregroundColor(.secondary)
@@ -96,7 +99,20 @@ extension NightscoutConfig {
                                     action: {
                                         hintLabel = "Import Settings from Nightscout"
                                         selectedVerboseHint =
-                                            "This will overwrite the following Trio therapy settings: \n • Basal Rates \n • Insulin Sensitivities \n • Carb Ratios \n • Target Glucose \n • Duration of Insulin Action"
+                                            AnyView(
+                                                VStack(alignment: .leading, spacing: 10) {
+                                                    Text(
+                                                        "This will overwrite the following Trio therapy settings:"
+                                                    )
+                                                    VStack(alignment: .leading) {
+                                                        Text("• Basal Rates")
+                                                        Text("• Insulin Sensitivities")
+                                                        Text("• Carb Ratios")
+                                                        Text("• Glucose Targets")
+                                                        Text("• Duration of Insulin Action")
+                                                    }
+                                                }
+                                            )
                                         shouldDisplayHint.toggle()
                                     },
                                     label: {
@@ -124,9 +140,9 @@ extension NightscoutConfig {
                                     .buttonStyle(.bordered)
                                     .disabled(state.url.isEmpty || state.connecting || state.backfilling)
 
-                                HStack(alignment: .top) {
+                                HStack(alignment: .center) {
                                     Text(
-                                        "You can backfill missing glucose data from Nightscout."
+                                        "Backfill missing glucose data from Nightscout."
                                     )
                                     .font(.footnote)
                                     .foregroundColor(.secondary)
@@ -136,7 +152,11 @@ extension NightscoutConfig {
                                         action: {
                                             hintLabel = "Backfill Glucose from Nightscout"
                                             selectedVerboseHint =
-                                                "Explanation… limitation… etc."
+                                                AnyView(
+                                                    Text(
+                                                        "This will backfill 24 hours of glucose data from your connected Nightscout URL to Trio"
+                                                    )
+                                                )
                                             shouldDisplayHint.toggle()
                                         },
                                         label: {
@@ -149,7 +169,9 @@ extension NightscoutConfig {
                             }.padding(.vertical)
                         }
                     ).listRowBackground(Color.chart)
-                }.blur(radius: state.importStatus == .running ? 5 : 0)
+                }
+                .listSectionSpacing(sectionSpacing)
+                .blur(radius: state.importStatus == .running ? 5 : 0)
 
                 if state.importStatus == .running {
                     CustomProgressView(text: "Importing Profile...")
@@ -163,7 +185,7 @@ extension NightscoutConfig {
                     hintDetent: $hintDetent,
                     shouldDisplayHint: $shouldDisplayHint,
                     hintLabel: hintLabel ?? "",
-                    hintText: selectedVerboseHint ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                     sheetTitle: "Help"
                 )
             }

+ 2 - 1
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConnectView.swift

@@ -15,7 +15,7 @@ struct NightscoutConnectView: View {
     }
 
     var body: some View {
-        Form {
+        List {
             Section(
                 header: Text("Connect to Nightscout"),
                 content: {
@@ -90,6 +90,7 @@ struct NightscoutConnectView: View {
 //                }
 //            } header: { Text("Local glucose source") }.listRowBackground(Color.chart)
         }
+        .listSectionSpacing(sectionSpacing)
         .navigationTitle("Connect")
         .navigationBarTitleDisplayMode(.automatic)
         .scrollContentBackground(.hidden)

+ 24 - 9
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutFetchView.swift

@@ -6,7 +6,7 @@ struct NightscoutFetchView: View {
 
     @State private var shouldDisplayHint: Bool = false
     @State var hintDetent = PresentationDetent.large
-    @State var selectedVerboseHint: String?
+    @State var selectedVerboseHint: AnyView?
     @State var hintLabel: String?
     @State private var decimalPlaceholder: Decimal = 0.0
     @State private var booleanPlaceholder: Bool = false
@@ -15,7 +15,7 @@ struct NightscoutFetchView: View {
     @Environment(AppState.self) var appState
 
     var body: some View {
-        Form {
+        List {
             SettingInputSection(
                 decimalValue: $decimalPlaceholder,
                 booleanValue: $state.isDownloadEnabled,
@@ -23,15 +23,20 @@ struct NightscoutFetchView: View {
                 selectedVerboseHint: Binding(
                     get: { selectedVerboseHint },
                     set: {
-                        selectedVerboseHint = $0
+                        selectedVerboseHint = $0.map { AnyView($0) }
                         hintLabel = "Allow Fetching from Nightscout"
                     }
                 ),
                 units: state.units,
                 type: .boolean,
                 label: "Allow Fetching from Nightscout",
-                miniHint: "Enable fetching of selected data sets from Nightscout. See hint for more details.",
-                verboseHint: "The Fetch Treatments toggle enables fetching of carbs and temp targets entered in Careportal or by another uploading device than Trio from Nightscout.",
+                miniHint: "Enable fetching of selected data sets from Nightscout.",
+                verboseHint: VStack(alignment: .leading, spacing: 10) {
+                    Text("Default: OFF").bold()
+                    Text(
+                        "The Fetch Treatments toggle enables fetching of carbs and temp targets entered in Careportal or by another uploading device than Trio from Nightscout."
+                    )
+                },
                 headerText: "Remote & Fetch Capabilities"
             )
 
@@ -43,15 +48,24 @@ struct NightscoutFetchView: View {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = "Allow Remote Control of Trio"
                         }
                     ),
                     units: state.units,
                     type: .boolean,
                     label: "Allow Remote Control of Trio",
-                    miniHint: "Enables selected remote control capabilities via Nightscout. See hint for more details.",
-                    verboseHint: "When enabled you allow these remote functions through announcements from Nightscout: \n • Suspend/Resume Pump \n • Opening/Closing Loop \n  • Set Temp Basal \n • Enact Bolus"
+                    miniHint: "Enables selected remote control capabilities via Nightscout.",
+                    verboseHint: VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        Text("When enabled you allow the following remote functions through announcements from Nightscout:")
+                        VStack(alignment: .leading) {
+                            Text("• Suspend/Resume Pump")
+                            Text("• Opening/Closing Loop")
+                            Text("• Set Temp Basal")
+                            Text("• Enact Bolus")
+                        }
+                    }
                 )
             } else {
                 Section {
@@ -59,12 +73,13 @@ struct NightscoutFetchView: View {
                 }.listRowBackground(Color.tabBar)
             }
         }
+        .listSectionSpacing(sectionSpacing)
         .sheet(isPresented: $shouldDisplayHint) {
             SettingInputHintView(
                 hintDetent: $hintDetent,
                 shouldDisplayHint: $shouldDisplayHint,
                 hintLabel: hintLabel ?? "",
-                hintText: selectedVerboseHint ?? "",
+                hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                 sheetTitle: "Help"
             )
         }

+ 26 - 9
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutUploadView.swift

@@ -5,7 +5,7 @@ struct NightscoutUploadView: View {
 
     @State private var shouldDisplayHint: Bool = false
     @State var hintDetent = PresentationDetent.large
-    @State var selectedVerboseHint: String?
+    @State var selectedVerboseHint: AnyView?
     @State var hintLabel: String?
     @State private var decimalPlaceholder: Decimal = 0.0
     @State private var booleanPlaceholder: Bool = false
@@ -14,7 +14,7 @@ struct NightscoutUploadView: View {
     @Environment(AppState.self) var appState
 
     var body: some View {
-        Form {
+        List {
             SettingInputSection(
                 decimalValue: $decimalPlaceholder,
                 booleanValue: $state.isUploadEnabled,
@@ -22,7 +22,7 @@ struct NightscoutUploadView: View {
                 selectedVerboseHint: Binding(
                     get: { selectedVerboseHint },
                     set: {
-                        selectedVerboseHint = $0
+                        selectedVerboseHint = $0.map { AnyView($0) }
                         hintLabel = "Allow Uploading to Nightscout"
                         shouldDisplayHint = true
                     }
@@ -30,8 +30,21 @@ struct NightscoutUploadView: View {
                 units: state.units,
                 type: .boolean,
                 label: "Allow Uploading to Nightscout",
-                miniHint: "Enables upload of selected data sets to Nightscout. See hint for more details.",
-                verboseHint: "The Upload Treatments toggle enables uploading of carbs, temp targets, device status, preferences and settings."
+                miniHint: "Enable uploading of selected data sets to Nightscout.",
+                verboseHint:
+                VStack(alignment: .leading, spacing: 10) {
+                    Text("Default: OFF").bold()
+                    Text(
+                        "The Upload Treatments toggle enables uploading of the following data sets to your connected Nightscout URL:"
+                    )
+                    VStack(alignment: .leading, spacing: 5) {
+                        Text("• Carbs")
+                        Text("• Temp Targets")
+                        Text("• Device Status")
+                        Text("• Preferences")
+                        Text("• Settings")
+                    }
+                }
             )
 
             if state.changeUploadGlucose {
@@ -42,7 +55,7 @@ struct NightscoutUploadView: View {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = "Upload Glucose"
                             shouldDisplayHint = true
                         }
@@ -50,17 +63,21 @@ struct NightscoutUploadView: View {
                     units: state.units,
                     type: .boolean,
                     label: "Upload Glucose",
-                    miniHint: "Enables uploading of CGM readings to Nightscout.",
-                    verboseHint: "Write stuff here."
+                    miniHint: "Enable uploading of CGM readings to Nightscout.",
+                    verboseHint: VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        Text("Enabling this setting allows CGM readings from Trio to be used in Nightscout.")
+                    }
                 )
             }
         }
+        .listSectionSpacing(sectionSpacing)
         .sheet(isPresented: $shouldDisplayHint) {
             SettingInputHintView(
                 hintDetent: $hintDetent,
                 shouldDisplayHint: $shouldDisplayHint,
                 hintLabel: hintLabel ?? "",
-                hintText: selectedVerboseHint ?? "",
+                hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                 sheetTitle: "Help"
             )
         }

+ 12 - 5
FreeAPS/Sources/Modules/NightscoutConfig/View/ProfileImport/ReviewInsulinActionView.swift

@@ -10,7 +10,7 @@ struct ReviewInsulinActionView: BaseView {
 
     @State private var shouldDisplayHint: Bool = false
     @State private var hintDetent = PresentationDetent.large
-    @State private var selectedVerboseHint: String?
+    @State private var selectedVerboseHint: AnyView?
     @State private var hintLabel: String?
     @State private var decimalPlaceholder: Decimal = 0.0
     @State private var booleanPlaceholder: Bool = false
@@ -27,15 +27,22 @@ struct ReviewInsulinActionView: BaseView {
                 selectedVerboseHint: Binding(
                     get: { selectedVerboseHint },
                     set: {
-                        selectedVerboseHint = $0
+                        selectedVerboseHint = $0.map { AnyView($0) }
                         hintLabel = "Duration of Insulin Action"
                     }
                 ),
                 units: state.units,
                 type: .decimal("dia"),
                 label: "Duration of Insulin Action",
-                miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                verboseHint: "Duration of Insulin Action… bla bla bla",
+                miniHint: "Number of hours insulin is active in your body.",
+                verboseHint:
+                VStack(alignment: .leading, spacing: 10) {
+                    Text("Default: 10 hours").bold()
+                    Text("Number of hours insulin will contribute to IOB after dosing.")
+                    Text(
+                        "Tip: It is better to use a Custom Peak Time than to adjust Duration of Insulin Action (DIA)."
+                    )
+                },
                 headerText: "Review imported DIA"
             )
         }
@@ -44,7 +51,7 @@ struct ReviewInsulinActionView: BaseView {
                 hintDetent: $hintDetent,
                 shouldDisplayHint: $shouldDisplayHint,
                 hintLabel: hintLabel ?? "",
-                hintText: selectedVerboseHint ?? "",
+                hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                 sheetTitle: "Help"
             )
         }

+ 1 - 0
FreeAPS/Sources/Modules/PumpConfig/PumpConfigDataFlow.swift

@@ -9,6 +9,7 @@ enum PumpConfig {
         case minimed
         case omnipod
         case omnipodBLE
+        case dana
         case simulator
     }
 

+ 1 - 1
FreeAPS/Sources/Modules/PumpConfig/PumpConfigProvider.swift

@@ -22,7 +22,7 @@ extension PumpConfig {
         func pumpSettings() -> PumpSettings {
             storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self)
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
-                ?? PumpSettings(insulinActionCurve: 6, maxBolus: 10, maxBasal: 2)
+                ?? PumpSettings(insulinActionCurve: 10, maxBolus: 10, maxBasal: 2)
         }
 
         var alertNotAck: AnyPublisher<Bool, Never> {

+ 21 - 5
FreeAPS/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift

@@ -9,7 +9,7 @@ extension PumpConfig {
 
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: String?
+        @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
@@ -47,9 +47,9 @@ extension PumpConfig {
                                         .frame(maxWidth: .infinity, alignment: .center)
                                         .buttonStyle(.bordered)
 
-                                    HStack(alignment: .top) {
+                                    HStack(alignment: .center) {
                                         Text(
-                                            "Pair a compatible pump with Trio. See details for available devices."
+                                            "Pair your insulin pump with Trio. See hint for compatible devices."
                                         )
                                         .font(.footnote)
                                         .foregroundColor(.secondary)
@@ -59,7 +59,22 @@ extension PumpConfig {
                                             action: {
                                                 hintLabel = "Pump Pairing to Trio"
                                                 selectedVerboseHint =
-                                                    "Explanation… limitation… etc."
+                                                    AnyView(
+                                                        VStack(alignment: .leading, spacing: 10) {
+                                                            Text(
+                                                                "Current Pump Models Supported:"
+                                                            )
+                                                            VStack(alignment: .leading) {
+                                                                Text("• Medtronic")
+                                                                Text("• Omnipod Eros")
+                                                                Text("• Omnipod Dash")
+                                                                Text("• Pump Simulator")
+                                                            }
+                                                            Text(
+                                                                "Note: If using a pump simulator, you will not have continuous readings from the CGM in Trio. Using a pump simulator is only advisable for becoming familiar with the app user interface. It will not give you insight on how the algorithm will respond."
+                                                            )
+                                                        }
+                                                    )
                                                 shouldDisplayHint.toggle()
                                             },
                                             label: {
@@ -86,7 +101,7 @@ extension PumpConfig {
                         hintDetent: $hintDetent,
                         shouldDisplayHint: $shouldDisplayHint,
                         hintLabel: hintLabel ?? "",
-                        hintText: selectedVerboseHint ?? "",
+                        hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                         sheetTitle: "Help"
                     )
                 }
@@ -94,6 +109,7 @@ extension PumpConfig {
                     Button("Medtronic") { state.addPump(.minimed) }
                     Button("Omnipod Eros") { state.addPump(.omnipod) }
                     Button("Omnipod Dash") { state.addPump(.omnipodBLE) }
+                    Button("Dana(RS/-i)") { state.addPump(.dana) }
                     Button("Pump Simulator") { state.addPump(.simulator) }
                 } message: { Text("Select Pump Model") }
             }

+ 10 - 0
FreeAPS/Sources/Modules/PumpConfig/View/PumpSetupView.swift

@@ -1,3 +1,4 @@
+import DanaKit
 import LoopKit
 import LoopKitUI
 import MinimedKit
@@ -58,6 +59,15 @@ extension PumpConfig {
                     allowDebugFeatures: true,
                     allowedInsulinTypes: [.apidra, .humalog, .novolog, .fiasp, .lyumjev]
                 )
+            case .dana:
+                setupViewController = DanaKitPumpManager.setupViewController(
+                    initialSettings: initialSettings,
+                    bluetoothProvider: bluetoothManager,
+                    colorPalette: .default,
+                    allowDebugFeatures: true,
+                    prefersToSkipUserInteraction: false,
+                    allowedInsulinTypes: [.apidra, .humalog, .novolog, .fiasp, .lyumjev]
+                )
             case .simulator:
                 setupViewController = MockPumpManager.setupViewController(
                     initialSettings: initialSettings,

+ 15 - 6
FreeAPS/Sources/Modules/RemoteControlConfig/View/RemoteControlConfig.swift

@@ -11,7 +11,7 @@ extension RemoteControlConfig {
 
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: String?
+        @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var isCopied: Bool = false
@@ -20,7 +20,7 @@ extension RemoteControlConfig {
         @Environment(AppState.self) var appState
 
         var body: some View {
-            Form {
+            List {
                 SettingInputSection(
                     decimalValue: $decimalPlaceholder,
                     booleanValue: $state.isTrioRemoteControlEnabled,
@@ -28,15 +28,23 @@ extension RemoteControlConfig {
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
-                            selectedVerboseHint = $0
+                            selectedVerboseHint = $0.map { AnyView($0) }
                             hintLabel = "Enable Remote Command"
                         }
                     ),
                     units: state.units,
                     type: .boolean,
                     label: "Enable Remote Control",
-                    miniHint: "Remote Control allow Trio to receive instructions, such as boluses and temp targets, from LoopFollow.",
-                    verboseHint: "When Remote Control is enabled, you can send boluses, overrides, temporary targets, carbs, and other commands to Trio via push notifications. To ensure security, these commands are protected by a shared secret, which must be entered in LoopFollow.",
+                    miniHint: "Allow Trio to receive commands from Loop Follow remotely.",
+                    verboseHint: VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        Text(
+                            "When Remote Control is enabled, you can send boluses, overrides, temporary targets, carbs, and other commands to Trio via push notifications."
+                        )
+                        Text(
+                            "To ensure security, these commands are protected by a shared secret, which must be entered in the Loop Follow app."
+                        )
+                    },
                     headerText: "Trio Remote Control"
                 )
 
@@ -79,12 +87,13 @@ extension RemoteControlConfig {
                     }
                 ).listRowBackground(Color.chart)
             }
+            .listSectionSpacing(sectionSpacing)
             .sheet(isPresented: $shouldDisplayHint) {
                 SettingInputHintView(
                     hintDetent: $hintDetent,
                     shouldDisplayHint: $shouldDisplayHint,
                     hintLabel: hintLabel ?? "",
-                    hintText: selectedVerboseHint ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                     sheetTitle: "Help"
                 )
             }

Fichier diff supprimé car celui-ci est trop grand
+ 189 - 78
FreeAPS/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift


+ 10 - 8
FreeAPS/Sources/Modules/Settings/SettingItems.swift

@@ -73,7 +73,7 @@ enum SettingItems {
         SettingItem(title: "ISF", view: .isfEditor, path: ["Therapy Settings"]),
         SettingItem(title: "Carb Ratios", view: .crEditor, path: ["Therapy Settings"]),
         SettingItem(title: "CR", view: .crEditor, path: ["Therapy Settings"]),
-        SettingItem(title: "Target Glucose", view: .targetsEditor, path: ["Therapy Settings"])
+        SettingItem(title: "Glucose Targets", view: .targetsEditor, path: ["Therapy Settings"])
     ]
 
     static let algorithmItems = [
@@ -97,7 +97,7 @@ enum SettingItems {
             path: ["Algorithm", "Super Micro Bolus (SMB)"]
         ),
         SettingItem(
-            title: "Dynamic Sensitivity",
+            title: "Dynamic Settings",
             view: .dynamicISF,
             searchContents: [
                 "Activate Dynamic Sensitivity (ISF)",
@@ -136,7 +136,7 @@ enum SettingItems {
                 "Unsuspend If No Temp",
                 "Suspend Zeros IOB",
                 "Min 5m Carbimpact",
-                "Autotune ISF Adjustment Fractio",
+                // "Autotune ISF Adjustment Fraction",
                 "Remaining Carbs Fraction",
                 "Remaining Carbs Cap",
                 "Noisy CGM Target Multiplier"
@@ -191,26 +191,28 @@ enum SettingItems {
             view: .userInterfaceSettings,
             searchContents: [
                 "Show X-Axis Grid Lines",
-                "Show Y-Axis Grid Line",
+                "Show Y-Axis Grid Lines",
                 "Show Low and High Thresholds",
                 "Low Threshold",
                 "High Threshold",
                 "X-Axis Interval Step",
                 "Total Insulin Display Type",
-                "Total Daily Dose",
-                "Total Insulin in Scope",
+                "Total Daily Dose (TDD)",
+                "Total Insulin in Scope (TINS)",
                 "Override HbA1c Unit",
                 "Standing / Laying TIR Chart",
                 "Show Carbs Required Badge",
                 "Carbs Required Threshold",
                 "Forecast Display Type",
+                "Cone",
+                "Lines",
                 "Trio Color Scheme",
                 "Glucose Color Scheme"
             ],
             path: ["Features", "User Interface"]
         ),
-        SettingItem(title: "App Icons", view: .iconConfig),
-        SettingItem(title: "Autotune", view: .autotuneConfig)
+        SettingItem(title: "App Icons", view: .iconConfig)
+        // SettingItem(title: "Autotune", view: .autotuneConfig)
     ]
 
     static let notificationItems = [

+ 46 - 39
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -14,7 +14,7 @@ extension Settings {
 
         @State private var shouldDisplayHint: Bool = false
         @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: String?
+        @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State private var decimalPlaceholder: Decimal = 0.0
         @State private var booleanPlaceholder: Bool = false
@@ -28,7 +28,7 @@ extension Settings {
         }
 
         var body: some View {
-            Form {
+            List {
                 if searchText.isEmpty {
                     let buildDetails = BuildDetails.default
 
@@ -76,15 +76,22 @@ extension Settings {
                         selectedVerboseHint: Binding(
                             get: { selectedVerboseHint },
                             set: {
-                                selectedVerboseHint = $0
+                                selectedVerboseHint = $0.map { AnyView($0) }
                                 hintLabel = "Closed Loop"
                             }
                         ),
                         units: state.units,
                         type: .boolean,
                         label: "Closed Loop",
-                        miniHint: "Enables automated insulin delivery. Requires active CGM sensor session and connected pump.",
-                        verboseHint: "Running Trio in closed loop mode requires an active CGM sensor session and a connected pump. This enables automated insulin delivery.\n\nBefore enabling, dial in your settings (basal / insulin sensitivity / carb ratio), and familiarize yourself with the app.",
+                        miniHint: "Enable automated insulin delivery.",
+                        verboseHint: VStack(alignment: .leading, spacing: 10) {
+                            Text(
+                                "Running Trio in closed loop mode requires an active CGM sensor session and a connected pump. This enables automated insulin delivery."
+                            )
+                            Text(
+                                "Before enabling, dial in your settings (basal / insulin sensitivity / carb ratio), and familiarize yourself with the app."
+                            )
+                        },
                         headerText: "Automated Insulin Delivery"
                     )
 
@@ -281,42 +288,42 @@ extension Settings {
 //                        }
 //                    }
 //                }.listRowBackground(Color.chart)
-
-            }.scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
-                .sheet(isPresented: $shouldDisplayHint) {
-                    SettingInputHintView(
-                        hintDetent: $hintDetent,
-                        shouldDisplayHint: $shouldDisplayHint,
-                        hintLabel: hintLabel ?? "",
-                        hintText: selectedVerboseHint ?? "",
-                        sheetTitle: "Help"
-                    )
-                }
-                .sheet(isPresented: $showShareSheet) {
-                    ShareSheet(activityItems: state.logItems())
-                }
-                .onAppear(perform: configureView)
-                .navigationTitle("Settings")
-                .navigationBarTitleDisplayMode(.automatic)
-                .toolbar {
-                    ToolbarItem(placement: .topBarTrailing) {
-                        Button(
-                            action: {
-                                if let url = URL(string: "https://triodocs.org/") {
-                                    UIApplication.shared.open(url)
-                                }
-                            },
-                            label: {
-                                HStack {
-                                    Text("Trio Docs")
-                                    Image(systemName: "questionmark.circle")
-                                }
+            }
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
+            .sheet(isPresented: $shouldDisplayHint) {
+                SettingInputHintView(
+                    hintDetent: $hintDetent,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    hintLabel: hintLabel ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
+                    sheetTitle: "Help"
+                )
+            }
+            .sheet(isPresented: $showShareSheet) {
+                ShareSheet(activityItems: state.logItems())
+            }
+            .onAppear(perform: configureView)
+            .navigationTitle("Settings")
+            .navigationBarTitleDisplayMode(.automatic)
+            .toolbar {
+                ToolbarItem(placement: .topBarTrailing) {
+                    Button(
+                        action: {
+                            if let url = URL(string: "https://triodocs.org/") {
+                                UIApplication.shared.open(url)
                             }
-                        )
-                    }
+                        },
+                        label: {
+                            HStack {
+                                Text("Trio Docs")
+                                Image(systemName: "questionmark.circle")
+                            }
+                        }
+                    )
                 }
-                .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
-                .screenNavigation(self)
+            }
+            .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
+            .screenNavigation(self)
         }
     }
 }

+ 1 - 1
FreeAPS/Sources/Modules/Settings/View/Subviews/AlgorithmSettings.swift

@@ -23,7 +23,7 @@ struct AlgorithmSettings: BaseView {
                 content: {
                     Text("Autosens").navigationLink(to: .autosensSettings, from: self)
                     Text("Super Micro Bolus (SMB)").navigationLink(to: .smbSettings, from: self)
-                    Text("Dynamic Sensitivity").navigationLink(to: .dynamicISF, from: self)
+                    Text("Dynamic Settings").navigationLink(to: .dynamicISF, from: self)
                     Text("Target Behavior").navigationLink(to: .targetBehavior, from: self)
                     Text("Additionals").navigationLink(to: .algorithmAdvancedSettings, from: self)
                 }

+ 6 - 6
FreeAPS/Sources/Modules/Settings/View/Subviews/FeatureSettingsView.swift

@@ -38,12 +38,12 @@ struct FeatureSettingsView: BaseView {
             )
             .listRowBackground(Color.chart)
 
-            Section(
-                header: Text("Data-Driven Settings Tuning"),
-                content: {
-                    Text("Autotune").navigationLink(to: .autotuneConfig, from: self)
-                }
-            )
+            // Section(
+            // header: Text("Data-Driven Settings Tuning"),
+            // content: {
+            // Text("Autotune").navigationLink(to: .autotuneConfig, from: self)
+            // }
+            // )
             .listRowBackground(Color.chart)
         }
         .scrollContentBackground(.hidden)

+ 25 - 12
FreeAPS/Sources/Modules/Settings/View/Subviews/NotificationsView.swift

@@ -17,15 +17,21 @@ struct NotificationsView: BaseView {
     @State var showAlert = false
     @State private var shouldDisplayHint: Bool = false
     @State var hintDetent = PresentationDetent.large
-    @State var selectedVerboseHint: String? =
-        "Notifications give you important Trio information without requiring you to open the app.\n\nKeep these turned ON in your phone’s settings to ensure you receive Trio Notifications, Critical Alerts, and Time Sensitive Notifications."
+    @State var selectedVerboseHint: AnyView? = AnyView(
+        VStack(alignment: .leading, spacing: 10) {
+            Text("Notifications give you important Trio information without requiring you to open the app.")
+            Text(
+                "Keep these turned ON in your phone’s settings to ensure you receive Trio Notifications, Critical Alerts, and Time Sensitive Notifications."
+            )
+        }
+    )
     @State var hintLabel: String? = "Manage iOS Preferences"
 
     @Environment(\.colorScheme) var colorScheme
     @Environment(AppState.self) var appState
 
     var body: some View {
-        Form {
+        List {
             Section(
                 header: Text("Manage iOS Preferences"),
                 content: {
@@ -37,18 +43,24 @@ struct NotificationsView: BaseView {
                 VStack {
                     notificationsEnabledStatus
                     HStack(alignment: .top) {
-                        Text(
-                            "Notifications give you important Trio information without requiring you to open the app."
-                        )
-                        .font(.footnote)
-                        .foregroundColor(.secondary)
-                        .lineLimit(nil)
+                        Text("Notifications give you important Trio information without requiring you to open the app.")
+                            .font(.footnote)
+                            .foregroundColor(.secondary)
+                            .lineLimit(nil)
                         Spacer()
                         Button(
                             action: {
                                 hintLabel = "Manage iOS Preferences"
-                                selectedVerboseHint =
-                                    "Notifications give you important Trio information without requiring you to open the app.\n\nKeep these turned ON in your phone’s settings to ensure you receive Trio Notifications, Critical Alerts, and Time Sensitive Notifications."
+                                selectedVerboseHint = AnyView(
+                                    VStack(alignment: .leading, spacing: 10) {
+                                        Text(
+                                            "Notifications give you important Trio information without requiring you to open the app."
+                                        )
+                                        Text(
+                                            "Keep these turned ON in your phone’s settings to ensure you receive Trio Notifications, Critical Alerts, and Time Sensitive Notifications."
+                                        )
+                                    }
+                                )
                                 shouldDisplayHint.toggle()
                             },
                             label: {
@@ -75,6 +87,7 @@ struct NotificationsView: BaseView {
                 }
             ).listRowBackground(Color.chart)
         }
+        .listSectionSpacing(sectionSpacing)
         .onReceive(
             resolver.resolve(AlertPermissionsChecker.self)!.$notificationsDisabled,
             perform: {
@@ -95,7 +108,7 @@ struct NotificationsView: BaseView {
                 hintDetent: $hintDetent,
                 shouldDisplayHint: $shouldDisplayHint,
                 hintLabel: hintLabel ?? "",
-                hintText: selectedVerboseHint ?? "",
+                hintText: selectedVerboseHint ?? AnyView(EmptyView()),
                 sheetTitle: "Help"
             )
         }

+ 0 - 0
FreeAPS/Sources/Modules/Settings/View/Subviews/TherapySettingsView.swift


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff