ソースを参照

Merge branch 'core-data-sync-trio' of github.com:dnzxy/Trio-dev into tdd

Deniz Cengiz 1 年間 前
コミット
212758d30e
100 ファイル変更1670 行追加1350 行削除
  1. 3 0
      .gitmodules
  2. 1 0
      Config.xcconfig
  3. 1 0
      DanaKit
  4. 77 58
      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 4
      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 114
      FreeAPS/Sources/APS/APSManager.swift
  18. 3 0
      FreeAPS/Sources/APS/DeviceDataManager.swift
  19. 0 52
      FreeAPS/Sources/APS/FetchAnnouncementsManager.swift
  20. 0 3
      FreeAPS/Sources/APS/OpenAPS/Constants.swift
  21. 0 80
      FreeAPS/Sources/APS/Storage/AnnouncementsStorage.swift
  22. 150 0
      FreeAPS/Sources/APS/Storage/ContactImageStorage.swift
  23. 0 150
      FreeAPS/Sources/APS/Storage/ContactTrickStorage.swift
  24. 1 1
      FreeAPS/Sources/APS/Storage/OverrideStorage.swift
  25. 0 1
      FreeAPS/Sources/Application/FreeAPSApp.swift
  26. 0 1
      FreeAPS/Sources/Assemblies/APSAssembly.swift
  27. 1 1
      FreeAPS/Sources/Assemblies/ServiceAssembly.swift
  28. 1 2
      FreeAPS/Sources/Assemblies/StorageAssembly.swift
  29. 4 0
      FreeAPS/Sources/Helpers/ConstantValues.swift
  30. 1 7
      FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings
  31. 0 6
      FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings
  32. 1 7
      FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings
  33. 1 7
      FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings
  34. 1 7
      FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings
  35. 1 7
      FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings
  36. 1 7
      FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings
  37. 1 7
      FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings
  38. 1 7
      FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings
  39. 1 7
      FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings
  40. 1 7
      FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings
  41. 1 7
      FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings
  42. 1 7
      FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings
  43. 1 7
      FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings
  44. 1 7
      FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings
  45. 1 7
      FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings
  46. 1 7
      FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings
  47. 1 7
      FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings
  48. 1 7
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  49. 1 7
      FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings
  50. 1 7
      FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings
  51. 1 7
      FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings
  52. 1 7
      FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings
  53. 0 57
      FreeAPS/Sources/Models/Announcement.swift
  54. 15 15
      FreeAPS/Sources/Models/ContactTrickEntry.swift
  55. 17 17
      FreeAPS/Sources/Models/DecimalPickerSettings.swift
  56. 7 12
      FreeAPS/Sources/Models/FreeAPSSettings.swift
  57. 16 0
      FreeAPS/Sources/Models/HbA1cDisplayUnit.swift
  58. 0 1
      FreeAPS/Sources/Models/Icons.swift
  59. 1 1
      FreeAPS/Sources/Models/Preferences.swift
  60. 16 0
      FreeAPS/Sources/Models/TimeInRangeChartStyle.swift
  61. 2 2
      FreeAPS/Sources/Models/TotalInsulinDisplayType.swift
  62. 7 3
      FreeAPS/Sources/Modules/Adjustments/View/AdjustmentsRootView.swift
  63. 19 29
      FreeAPS/Sources/Modules/Adjustments/View/Overrides/AddOverrideForm.swift
  64. 19 29
      FreeAPS/Sources/Modules/Adjustments/View/Overrides/EditOverrideForm.swift
  65. 53 0
      FreeAPS/Sources/Modules/Adjustments/View/Overrides/OverrideHelpView.swift
  66. 4 22
      FreeAPS/Sources/Modules/Adjustments/View/TempTargets/AddTempTargetForm.swift
  67. 16 3
      FreeAPS/Sources/Modules/Adjustments/View/TempTargets/EditTempTargetForm.swift
  68. 39 0
      FreeAPS/Sources/Modules/Adjustments/View/TempTargets/TempTargetHelpView.swift
  69. 3 3
      FreeAPS/Sources/Modules/Adjustments/View/ViewElements/TargetPicker.swift
  70. 1 1
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsProvider.swift
  71. 1 1
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift
  72. 175 96
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift
  73. 3 1
      FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsDataFlow.swift
  74. 7 1
      FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsProvider.swift
  75. 42 0
      FreeAPS/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift
  76. 158 20
      FreeAPS/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift
  77. 23 18
      FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift
  78. 67 21
      FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift
  79. 33 12
      FreeAPS/Sources/Modules/CGM/View/CGMRootView.swift
  80. 47 11
      FreeAPS/Sources/Modules/CalendarEventSettings/View/CalendarEventSettingsRootView.swift
  81. 1 1
      FreeAPS/Sources/Modules/CarbRatioEditor/CarbRatioEditorStateModel.swift
  82. 8 0
      FreeAPS/Sources/Modules/ContactImage/ContactImageDataFlow.swift
  83. 6 0
      FreeAPS/Sources/Modules/ContactImage/ContactImageProvider.swift
  84. 49 49
      FreeAPS/Sources/Modules/ContactTrick/ContactTrickStateModel.swift
  85. 43 52
      FreeAPS/Sources/Modules/ContactTrick/View/AddContactTrickSheet.swift
  86. 51 58
      FreeAPS/Sources/Modules/ContactTrick/View/ContactTrickDetailView.swift
  87. 75 0
      FreeAPS/Sources/Modules/ContactImage/View/ContactImageHelpView.swift
  88. 19 10
      FreeAPS/Sources/Modules/ContactTrick/View/ContactTrickRootView.swift
  89. 0 8
      FreeAPS/Sources/Modules/ContactTrick/ContactTrickDataFlow.swift
  90. 0 6
      FreeAPS/Sources/Modules/ContactTrick/ContactTrickProvider.swift
  91. 1 1
      FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift
  92. 154 33
      FreeAPS/Sources/Modules/DynamicSettings/View/DynamicSettingsRootView.swift
  93. 1 1
      FreeAPS/Sources/Modules/GeneralSettings/UnitsLimitsSettingsProvider.swift
  94. 57 20
      FreeAPS/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift
  95. 89 32
      FreeAPS/Sources/Modules/GlucoseNotificationSettings/View/GlucoseNotificationSettingsRootView.swift
  96. 21 16
      FreeAPS/Sources/Modules/HealthKit/View/AppleHealthKitRootView.swift
  97. 0 1
      FreeAPS/Sources/Modules/Home/HomeDataFlow.swift
  98. 1 8
      FreeAPS/Sources/Modules/Home/HomeProvider.swift
  99. 3 5
      FreeAPS/Sources/Modules/Home/HomeStateModel.swift
  100. 0 0
      FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/BasalChart.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/loopandlearn/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

+ 77 - 58
FreeAPS.xcodeproj/project.pbxproj

@@ -121,8 +121,6 @@
 		38569349270B5DFB0002C50D /* AppGroupSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38569346270B5DFB0002C50D /* AppGroupSource.swift */; };
 		38569353270B5E350002C50D /* CGMRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38569352270B5E350002C50D /* CGMRootView.swift */; };
 		385CEA8225F23DFD002D6D5B /* NightscoutStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385CEA8125F23DFD002D6D5B /* NightscoutStatus.swift */; };
-		385CEAC125F2EA52002D6D5B /* Announcement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385CEAC025F2EA52002D6D5B /* Announcement.swift */; };
-		385CEAC425F2F154002D6D5B /* AnnouncementsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 385CEAC325F2F154002D6D5B /* AnnouncementsStorage.swift */; };
 		3862CC2E2743F9F700BF832C /* CalendarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3862CC2D2743F9F700BF832C /* CalendarManager.swift */; };
 		3870FF4725EC187A0088248F /* BloodGlucose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3870FF4225EC13F40088248F /* BloodGlucose.swift */; };
 		3871F39C25ED892B0013ECB5 /* TempTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3871F39B25ED892B0013ECB5 /* TempTarget.swift */; };
@@ -148,7 +146,6 @@
 		38A0363B25ECF07E00FCBB52 /* GlucoseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A0363A25ECF07E00FCBB52 /* GlucoseStorage.swift */; };
 		38A0364225ED069400FCBB52 /* TempBasal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A0364125ED069400FCBB52 /* TempBasal.swift */; };
 		38A13D3225E28B4B00EAA382 /* PumpHistoryEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A13D3125E28B4B00EAA382 /* PumpHistoryEvent.swift */; };
-		38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */; };
 		38A504A425DD9C4000C5B9E8 /* UserDefaultsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A5049125DD9C4000C5B9E8 /* UserDefaultsExtensions.swift */; };
 		38A9260525F012D8009E3739 /* CarbRatios.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A9260425F012D8009E3739 /* CarbRatios.swift */; };
 		38AAF85525FFF846004AF583 /* CurrentGlucoseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */; };
@@ -345,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 */; };
@@ -362,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 */; };
@@ -426,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 */; };
@@ -487,7 +486,11 @@
 		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 */; };
+		DDA6E3202D258E0500C2988C /* OverrideHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6E31F2D258E0500C2988C /* OverrideHelpView.swift */; };
+		DDA6E3222D25901100C2988C /* TempTargetHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6E3212D25901100C2988C /* TempTargetHelpView.swift */; };
+		DDA6E2502D22187500C2988C /* ChartLegendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6E24F2D22187500C2988C /* ChartLegendView.swift */; };
+		DDA6E3572D25988500C2988C /* ContactImageHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6E3562D25988500C2988C /* ContactImageHelpView.swift */; };
+		DDB37CC52D05048F00D99BF4 /* ContactImageStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */; };
 		DDB37CC72D05127500D99BF4 /* FontExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB37CC62D05127500D99BF4 /* FontExtensions.swift */; };
 		DDCEBF5B2CC1B76400DF4C36 /* LiveActivity+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCEBF5A2CC1B76400DF4C36 /* LiveActivity+Helper.swift */; };
 		DDD163122C4C689900CD525A /* AdjustmentsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD163112C4C689900CD525A /* AdjustmentsStateModel.swift */; };
@@ -497,6 +500,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 */; };
@@ -546,10 +550,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 */; };
@@ -817,8 +821,6 @@
 		38569346270B5DFB0002C50D /* AppGroupSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppGroupSource.swift; sourceTree = "<group>"; };
 		38569352270B5E350002C50D /* CGMRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMRootView.swift; sourceTree = "<group>"; };
 		385CEA8125F23DFD002D6D5B /* NightscoutStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutStatus.swift; sourceTree = "<group>"; };
-		385CEAC025F2EA52002D6D5B /* Announcement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Announcement.swift; sourceTree = "<group>"; };
-		385CEAC325F2F154002D6D5B /* AnnouncementsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementsStorage.swift; sourceTree = "<group>"; };
 		3862CC2D2743F9F700BF832C /* CalendarManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarManager.swift; sourceTree = "<group>"; };
 		3870FF4225EC13F40088248F /* BloodGlucose.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BloodGlucose.swift; sourceTree = "<group>"; };
 		3871F39B25ED892B0013ECB5 /* TempTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTarget.swift; sourceTree = "<group>"; };
@@ -846,7 +848,6 @@
 		38A0363A25ECF07E00FCBB52 /* GlucoseStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseStorage.swift; sourceTree = "<group>"; };
 		38A0364125ED069400FCBB52 /* TempBasal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempBasal.swift; sourceTree = "<group>"; };
 		38A13D3125E28B4B00EAA382 /* PumpHistoryEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpHistoryEvent.swift; sourceTree = "<group>"; };
-		38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAnnouncementsManager.swift; sourceTree = "<group>"; };
 		38A5049125DD9C4000C5B9E8 /* UserDefaultsExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtensions.swift; sourceTree = "<group>"; };
 		38A9260425F012D8009E3739 /* CarbRatios.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbRatios.swift; sourceTree = "<group>"; };
 		38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentGlucoseView.swift; sourceTree = "<group>"; };
@@ -1048,11 +1049,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>"; };
@@ -1066,6 +1067,7 @@
 		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 /* TreatmentsProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TreatmentsProvider.swift; sourceTree = "<group>"; };
+		C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantValues.swift; sourceTree = "<group>"; };
 		C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalRootView.swift; sourceTree = "<group>"; };
 		C8D1A7CA8C10C4403D4BBFA7 /* TreatmentsDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TreatmentsDataFlow.swift; sourceTree = "<group>"; };
 		CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawFetchedProfile.swift; sourceTree = "<group>"; };
@@ -1131,6 +1133,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>"; };
@@ -1192,9 +1195,13 @@
 		DD9ECB6E2CA9A0BA00AA7C45 /* RemoteControlConfigProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfigProvider.swift; sourceTree = "<group>"; };
 		DD9ECB6F2CA9A0BA00AA7C45 /* RemoteControlConfigDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfigDataFlow.swift; sourceTree = "<group>"; };
 		DD9ECB732CA9A0C300AA7C45 /* RemoteControlConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfig.swift; sourceTree = "<group>"; };
+		DDA6E31F2D258E0500C2988C /* OverrideHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideHelpView.swift; sourceTree = "<group>"; };
+		DDA6E3212D25901100C2988C /* TempTargetHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetHelpView.swift; sourceTree = "<group>"; };
+		DDA6E24F2D22187500C2988C /* ChartLegendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartLegendView.swift; sourceTree = "<group>"; };
+		DDA6E3562D25988500C2988C /* ContactImageHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageHelpView.swift; sourceTree = "<group>"; };
 		DDB37CC22D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDB37CC32D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
-		DDB37CC42D05048F00D99BF4 /* 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>"; };
@@ -1204,6 +1211,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>"; };
@@ -1251,10 +1259,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>"; };
@@ -1566,7 +1574,7 @@
 				E42231DBF0DBE2B4B92D1B15 /* CarbRatioEditor */,
 				F75CB57ED6971B46F8756083 /* CGM */,
 				0610F7D6D2EC00E3BA1569F0 /* ConfigEditor */,
-				E592A3762CEEC038009A472C /* ContactTrick */,
+				E592A3762CEEC038009A472C /* ContactImage */,
 				9E56E3626FAD933385101B76 /* DataTable */,
 				195D80B22AF696EE00D25097 /* DynamicSettings */,
 				DD17454C2C55CA0200211FAC /* GeneralSettings */,
@@ -1706,7 +1714,7 @@
 				3811DE9225C9D88200A708ED /* Appearance */,
 				CEB434E128B8F9BC00B70274 /* Bluetooth */,
 				3862CC2C2743F9DC00BF832C /* Calendar */,
-				E592A37E2CEEC046009A472C /* ContactTrick */,
+				E592A37E2CEEC046009A472C /* ContactImage */,
 				F90692A8274B7A980037068D /* HealthKit */,
 				6B1A8D2C2B156EC100E76752 /* LiveActivity */,
 				3811DE9425C9D88200A708ED /* Network */,
@@ -1822,7 +1830,6 @@
 				CE95BF562BA5F5FE00DC3DE3 /* PluginManager.swift */,
 				3811DF0F25CAAAE200A708ED /* APSManager.swift */,
 				38BF021E25E7F0DE00579895 /* DeviceDataManager.swift */,
-				38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */,
 				38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */,
 				38192E06261BA9960094D973 /* FetchTreatmentsManager.swift */,
 				3856933F270B57A00002C50D /* CGM */,
@@ -1887,6 +1894,7 @@
 		3833B51E260264AC003021B3 /* Chart */ = {
 			isa = PBXGroup;
 			children = (
+				DDA6E24F2D22187500C2988C /* ChartLegendView.swift */,
 				BD3CC0712B0B89D50013189E /* MainChartView.swift */,
 				BDDAF9F12D0055CC00B34E7A /* ChartElements */,
 			);
@@ -1996,9 +2004,9 @@
 		388E5A5925B6F0250019842D /* Models */ = {
 			isa = PBXGroup;
 			children = (
+				DD07CA132CE80B73002D45A9 /* TimeInRangeChartStyle.swift */,
 				DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */,
 				DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */,
-				385CEAC025F2EA52002D6D5B /* Announcement.swift */,
 				388E5A5F25B6F2310019842D /* Autosens.swift */,
 				38A00B1E25FC00F7006BC0B0 /* Autotune.swift */,
 				388358C725EEF6D200E024B2 /* BasalProfileEntry.swift */,
@@ -2043,6 +2051,7 @@
 				DD6B7CB32C7B71F700B75029 /* ForecastDisplayType.swift */,
 				DD6B7CB52C7B748B00B75029 /* TotalInsulinDisplayType.swift */,
 				DD9ECB692CA99F6C00AA7C45 /* PushMessage.swift */,
+				DDD6D4D22CDE90720029439A /* HbA1cDisplayUnit.swift */,
 			);
 			path = Models;
 			sourceTree = "<group>";
@@ -2050,6 +2059,7 @@
 		388E5A5A25B6F05F0019842D /* Helpers */ = {
 			isa = PBXGroup;
 			children = (
+				C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */,
 				DD940BAB2CA75889000830A5 /* DynamicGlucoseColor.swift */,
 				38F37827261260DC009DB701 /* Color+Extensions.swift */,
 				389ECE042601144100D86C4F /* ConcurrentMap.swift */,
@@ -2089,10 +2099,12 @@
 		38A0362725ECF05300FCBB52 /* Storage */ = {
 			isa = PBXGroup;
 			children = (
-				DDB37CC42D05048F00D99BF4 /* ContactTrickStorage.swift */,
-				385CEAC325F2F154002D6D5B /* AnnouncementsStorage.swift */,
+				CE82E02428E867BA00473A9C /* AlertStorage.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 */,
@@ -2488,6 +2500,7 @@
 		BD793CAD2CE7660C00D669AC /* Overrides */ = {
 			isa = PBXGroup;
 			children = (
+				DDA6E31F2D258E0500C2988C /* OverrideHelpView.swift */,
 				DDD1631B2C4C697400CD525A /* AddOverrideForm.swift */,
 				DDD163192C4C695E00CD525A /* EditOverrideForm.swift */,
 			);
@@ -2497,6 +2510,7 @@
 		BD793CAE2CE7661D00D669AC /* TempTargets */ = {
 			isa = PBXGroup;
 			children = (
+				DDA6E3212D25901100C2988C /* TempTargetHelpView.swift */,
 				58A3D5392C96D4DE003F90FC /* AddTempTargetForm.swift */,
 				5825A1BD2C97335C0046467E /* EditTempTargetForm.swift */,
 			);
@@ -3003,32 +3017,33 @@
 		E592A3722CEEC038009A472C /* View */ = {
 			isa = PBXGroup;
 			children = (
-				E592A3712CEEC038009A472C /* ContactTrickRootView.swift */,
-				BDC531112D1060FA00088832 /* ContactTrickDetailView.swift */,
-				BDC531132D10611D00088832 /* AddContactTrickSheet.swift */,
+				DDA6E3562D25988500C2988C /* ContactImageHelpView.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 */ = {
@@ -3420,6 +3435,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 */,
@@ -3429,7 +3445,6 @@
 				5825A1BE2C97335C0046467E /* EditTempTargetForm.swift in Sources */,
 				19D466A329AA2B80004D5F33 /* MealSettingsDataFlow.swift in Sources */,
 				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
-				385CEAC425F2F154002D6D5B /* AnnouncementsStorage.swift in Sources */,
 				38AEE73D25F0200C0013F05B /* FreeAPSSettings.swift in Sources */,
 				38FCF3FD25E997A80078B0D1 /* PumpHistoryStorage.swift in Sources */,
 				58645BA72CA2D390008AFCE7 /* ChartAxisSetup.swift in Sources */,
@@ -3450,6 +3465,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 */,
@@ -3549,7 +3565,11 @@
 				DDD1631F2C4C6F6900CD525A /* TrioCoreDataPersistentContainer.xcdatamodeld in Sources */,
 				DD1745482C55C61D00211FAC /* AutosensSettingsStateModel.swift in Sources */,
 				DD1745462C55C61500211FAC /* AutosensSettingsProvider.swift in Sources */,
+				DDA6E3202D258E0500C2988C /* OverrideHelpView.swift in Sources */,
+				DDA6E2502D22187500C2988C /* ChartLegendView.swift in Sources */,
 				3811DEAF25C9D88300A708ED /* KeyValueStorage.swift in Sources */,
+				DDD6D4D32CDE90720029439A /* HbA1cDisplayUnit.swift in Sources */,
+				DDA6E3572D25988500C2988C /* ContactImageHelpView.swift in Sources */,
 				38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */,
 				388358C825EEF6D200E024B2 /* BasalProfileEntry.swift in Sources */,
 				3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */,
@@ -3680,8 +3700,8 @@
 				CE1F6DDB2BAE08B60064EB8D /* TidepoolManager.swift in Sources */,
 				BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */,
 				9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */,
+				DDA6E3222D25901100C2988C /* TempTargetHelpView.swift in Sources */,
 				58645B9D2CA2D275008AFCE7 /* DeterminationSetup.swift in Sources */,
-				38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */,
 				DD1745442C55C60E00211FAC /* AutosensSettingsDataFlow.swift in Sources */,
 				BDCAF2382C639F35002DC907 /* SettingItems.swift in Sources */,
 				58D08B342C8DF9A700AA37D3 /* CobIobChart.swift in Sources */,
@@ -3709,7 +3729,6 @@
 				DD1745242C55526000211FAC /* SMBSettingsStateModel.swift in Sources */,
 				F90692D1274B99B60037068D /* HealthKitProvider.swift in Sources */,
 				19F95FF729F10FEE00314DDC /* StatStateModel.swift in Sources */,
-				385CEAC125F2EA52002D6D5B /* Announcement.swift in Sources */,
 				8B759CFCF47B392BB365C251 /* BasalProfileEditorDataFlow.swift in Sources */,
 				195D80B42AF6973A00D25097 /* DynamicSettingsRootView.swift in Sources */,
 				389442CB25F65F7100FA1F27 /* NightscoutTreatment.swift in Sources */,
@@ -3734,7 +3753,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 */,
@@ -3787,11 +3806,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 */,
@@ -3810,7 +3829,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 */,
@@ -3868,8 +3887,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 */,
@@ -3888,7 +3907,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


ファイルの差分が大きいため隠しています
+ 1 - 1
FreeAPS/Resources/javascript/bundle/determine-basal.js


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

@@ -1,7 +1,6 @@
 {
   "units" : "mg/dL",
   "closedLoop" : false,
-  "allowAnnouncements" : false,
   "useAutotune" : false,
   "onlyAutotuneBasals" : false,
   "isUploadEnabled" : false,
@@ -33,14 +32,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 +51,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 - 114
FreeAPS/Sources/APS/APSManager.swift

@@ -28,7 +28,6 @@ protocol APSManager {
     func roundBolus(amount: Decimal) -> Decimal
     var lastError: CurrentValueSubject<Error?, Never> { get }
     func cancelBolus() async
-    func enactAnnouncement(_ announcement: Announcement)
 }
 
 enum APSError: LocalizedError {
@@ -64,7 +63,6 @@ final class BaseAPSManager: APSManager, Injectable {
     @Injected() private var alertHistoryStorage: AlertHistoryStorage!
     @Injected() private var tempTargetsStorage: TempTargetsStorage!
     @Injected() private var carbsStorage: CarbsStorage!
-    @Injected() private var announcementsStorage: AnnouncementsStorage!
     @Injected() private var determinationStorage: DeterminationStorage!
     @Injected() private var deviceDataManager: DeviceDataManager!
     @Injected() private var nightscout: NightscoutManager!
@@ -581,101 +579,6 @@ final class BaseAPSManager: APSManager, Injectable {
         await openAPS.autotune()
     }
 
-    func enactAnnouncement(_ announcement: Announcement) {
-        guard let action = announcement.action else {
-            warning(.apsManager, "Invalid Announcement action")
-            return
-        }
-
-        guard let pump = pumpManager else {
-            warning(.apsManager, "Pump is not set")
-            return
-        }
-
-        debug(.apsManager, "Start enact announcement: \(action)")
-
-        switch action {
-        case let .bolus(amount):
-            if let error = verifyStatus() {
-                processError(error)
-                return
-            }
-            let roundedAmount = pump.roundToSupportedBolusVolume(units: Double(amount))
-            pump.enactBolus(units: roundedAmount, activationType: .manualRecommendationAccepted) { error in
-                if let error = error {
-                    // warning(.apsManager, "Announcement Bolus failed with error: \(error.localizedDescription)")
-                    switch error {
-                    case .uncertainDelivery:
-                        // Do not generate notification on uncertain delivery error
-                        break
-                    default:
-                        // Do not generate notifications for automatic boluses that fail.
-                        warning(.apsManager, "Announcement Bolus failed with error: \(error.localizedDescription)")
-                    }
-
-                } else {
-                    debug(.apsManager, "Announcement Bolus succeeded")
-                    self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
-                    self.bolusProgress.send(0)
-                }
-            }
-        case let .pump(pumpAction):
-            switch pumpAction {
-            case .suspend:
-                if let error = verifyStatus() {
-                    processError(error)
-                    return
-                }
-                pump.suspendDelivery { error in
-                    if let error = error {
-                        debug(.apsManager, "Pump not suspended by Announcement: \(error.localizedDescription)")
-                    } else {
-                        debug(.apsManager, "Pump suspended by Announcement")
-                        self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
-                    }
-                }
-            case .resume:
-                guard pump.status.pumpStatus.suspended else {
-                    return
-                }
-                pump.resumeDelivery { error in
-                    if let error = error {
-                        warning(.apsManager, "Pump not resumed by Announcement: \(error.localizedDescription)")
-                    } else {
-                        debug(.apsManager, "Pump resumed by Announcement")
-                        self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
-                    }
-                }
-            }
-        case let .looping(closedLoop):
-            settings.closedLoop = closedLoop
-            debug(.apsManager, "Closed loop \(closedLoop) by Announcement")
-            announcementsStorage.storeAnnouncements([announcement], enacted: true)
-        case let .tempbasal(rate, duration):
-            if let error = verifyStatus() {
-                processError(error)
-                return
-            }
-            // unable to do temp basal during manual temp basal 😁
-            if isManualTempBasal {
-                processError(APSError.manualBasalTemp(message: "Loop not possible during the manual basal temp"))
-                return
-            }
-            guard !settings.closedLoop else {
-                return
-            }
-            let roundedRate = pump.roundToSupportedBasalRate(unitsPerHour: Double(rate))
-            pump.enactTempBasal(unitsPerHour: roundedRate, for: TimeInterval(duration) * 60) { error in
-                if let error = error {
-                    warning(.apsManager, "Announcement TempBasal failed with error: \(error.localizedDescription)")
-                } else {
-                    debug(.apsManager, "Announcement TempBasal succeeded")
-                    self.announcementsStorage.storeAnnouncements([announcement], enacted: true)
-                }
-            }
-        }
-    }
-
     private func fetchCurrentTempBasal(date: Date) async -> TempBasal {
         let results = await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: PumpEventStored.self,
@@ -1067,10 +970,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(),
@@ -1088,14 +989,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)
@@ -1258,7 +1160,7 @@ final class BaseAPSManager: APSManager, Injectable {
                 cv: Double,
                 readings: Double
             ),
-            overrideHbA1cUnit: Bool,
+            hbA1cDisplayUnit: HbA1cDisplayUnit,
             numberofDays: Double,
             TimeInRange: TIRs,
             avg: Averages,
@@ -1294,7 +1196,7 @@ final class BaseAPSManager: APSManager, Injectable {
                 cv: Double,
                 readings: Double
             ),
-            overrideHbA1cUnit: Bool,
+            hbA1cDisplayUnit: HbA1cDisplayUnit,
             numberofDays: Double,
             TimeInRange: TIRs,
             avg: Averages,
@@ -1325,18 +1227,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)
             )
 
@@ -1410,7 +1312,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 - 52
FreeAPS/Sources/APS/FetchAnnouncementsManager.swift

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

+ 0 - 3
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 {
@@ -93,8 +92,6 @@ extension OpenAPS {
 
     enum FreeAPS {
         static let settings = "freeaps/freeaps_settings.json"
-        static let announcements = "freeaps/announcements.json"
-        static let announcementsEnacted = "freeaps/announcements_enacted.json"
         static let tempTargetsPresets = "freeaps/temptargets_presets.json"
         static let calibrations = "freeaps/calibrations.json"
     }

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

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

+ 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/APS/Storage/OverrideStorage.swift

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

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

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

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

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

+ 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 - 2
FreeAPS/Sources/Assemblies/StorageAssembly.swift

@@ -14,8 +14,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(AnnouncementsStorage.self) { r in BaseAnnouncementsStorage(resolver: r) }
+        container.register(ContactImageStorage.self) { r in BaseContactImageStorage(resolver: r) }
         container.register(SettingsManager.self) { r in BaseSettingsManager(resolver: r) }
         container.register(Keychain.self) { _ in BaseKeychain() }
         container.register(AlertHistoryStorage.self) { r in BaseAlertHistoryStorage(resolver: r) }

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

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

ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings


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

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

ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings


ファイルの差分が大きいため隠しています
+ 1 - 7
FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings


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

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

+ 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 - 12
FreeAPS/Sources/Models/FreeAPSSettings.swift

@@ -18,7 +18,6 @@ enum BolusShortcutLimit: String, JSON, CaseIterable, Identifiable {
 struct FreeAPSSettings: JSON, Equatable {
     var units: GlucoseUnits = .mgdL
     var closedLoop: Bool = false
-    var allowAnnouncements: Bool = false
     var useAutotune: Bool = false
     var isUploadEnabled: Bool = false
     var isDownloadEnabled: Bool = false
@@ -53,14 +52,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 +72,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
@@ -94,10 +93,6 @@ extension FreeAPSSettings: Decodable {
             settings.closedLoop = closedLoop
         }
 
-        if let allowAnnouncements = try? container.decode(Bool.self, forKey: .allowAnnouncements) {
-            settings.allowAnnouncements = allowAnnouncements
-        }
-
         if let useAutotune = try? container.decode(Bool.self, forKey: .useAutotune) {
             settings.useAutotune = useAutotune
         }
@@ -283,8 +278,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 +290,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: "")
         }
     }
 }

+ 7 - 3
FreeAPS/Sources/Modules/Adjustments/View/AdjustmentsRootView.swift

@@ -419,6 +419,7 @@ extension Adjustments {
                     .background(.thinMaterial)
                     .opacity(0.8)
                     .clipShape(Rectangle())
+
                 Group {
                     switch state.selectedTab {
                     case .overrides:
@@ -430,9 +431,10 @@ extension Adjustments {
                             }
                         }, label: {
                             Text("Stop Override")
+                                .frame(maxWidth: .infinity, maxHeight: .infinity)
                                 .padding(10)
                         })
-                            .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
+                            .frame(width: UIScreen.main.bounds.width * 0.9, height: 40, alignment: .center)
                             .disabled(!state.isEnabled)
                             .background(!state.isEnabled ? Color(.systemGray4) : Color(.systemRed))
                             .tint(.white)
@@ -448,15 +450,17 @@ extension Adjustments {
                             }
                         }, label: {
                             Text("Stop Temp Target")
+                                .frame(maxWidth: .infinity, maxHeight: .infinity)
                                 .padding(10)
                         })
-                            .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
+                            .frame(width: UIScreen.main.bounds.width * 0.9, height: 40, alignment: .center)
                             .disabled(!state.isTempTargetEnabled)
                             .background(!state.isTempTargetEnabled ? Color(.systemGray4) : Color(.systemRed))
                             .tint(.white)
                             .clipShape(RoundedRectangle(cornerRadius: 8))
                     }
-                }.padding(5)
+                }
+                .padding(5)
             }
         }
 

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

@@ -55,23 +55,7 @@ struct AddOverrideForm: View {
             }
             .onAppear { targetStep = state.units == .mgdL ? 5 : 9 }
             .sheet(isPresented: $state.isHelpSheetPresented) {
-                NavigationStack {
-                    List {
-                        Text("Lorem Ipsum Dolor Sit Amet")
-                    }
-                    .padding(.trailing, 10)
-                    .navigationBarTitle("Help", displayMode: .inline)
-
-                    Button { state.isHelpSheetPresented.toggle() }
-                    label: { Text("Got it!").frame(maxWidth: .infinity, alignment: .center) }
-                        .buttonStyle(.bordered)
-                        .padding(.top)
-                }
-                .padding()
-                .presentationDetents(
-                    [.fraction(0.9), .large],
-                    selection: $state.helpSheetDetent
-                )
+                OverrideHelpView(state: state, helpSheetDetent: $state.helpSheetDetent)
             }
         }
     }
@@ -94,9 +78,9 @@ struct AddOverrideForm: View {
                     Spacer()
                     Text("\(state.overridePercentage.formatted(.number)) %")
                         .foregroundColor(!displayPickerPercentage ? .primary : .accentColor)
-                }
-                .onTapGesture {
-                    displayPickerPercentage = toggleScrollWheel(displayPickerPercentage)
+                        .onTapGesture {
+                            displayPickerPercentage = toggleScrollWheel(displayPickerPercentage)
+                        }
                 }
 
                 if displayPickerPercentage {
@@ -232,6 +216,9 @@ struct AddOverrideForm: View {
                                 state.convertTo12HourFormat(Int(truncating: state.start as NSNumber))
                         )
                         .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
+                        .onTapGesture {
+                            displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
+                        }
                         Spacer()
                         Divider().frame(width: 1, height: 20)
                         Spacer()
@@ -242,11 +229,11 @@ struct AddOverrideForm: View {
                                 state.convertTo12HourFormat(Int(truncating: state.end as NSNumber))
                         )
                         .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
+                        .onTapGesture {
+                            displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
+                        }
                         Spacer()
                     }
-                    .onTapGesture {
-                        displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
-                    }
 
                     if displayPickerDisableSmbSchedule {
                         HStack {
@@ -301,6 +288,9 @@ struct AddOverrideForm: View {
                             Spacer()
                             Text("\(state.smbMinutes.formatted(.number)) min")
                                 .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
+                                .onTapGesture {
+                                    displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                                }
                             Spacer()
                             Divider().frame(width: 1, height: 20)
                             Spacer()
@@ -308,9 +298,9 @@ struct AddOverrideForm: View {
                             Spacer()
                             Text("\(state.uamMinutes.formatted(.number)) min")
                                 .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
-                        }
-                        .onTapGesture {
-                            displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                                .onTapGesture {
+                                    displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                                }
                         }
 
                         if displayPickerSmbMinutes {
@@ -355,9 +345,9 @@ struct AddOverrideForm: View {
                         Spacer()
                         Text(state.formatHrMin(Int(state.overrideDuration)))
                             .foregroundColor(!displayPickerDuration ? .primary : .accentColor)
-                    }
-                    .onTapGesture {
-                        displayPickerDuration = toggleScrollWheel(displayPickerDuration)
+                            .onTapGesture {
+                                displayPickerDuration = toggleScrollWheel(displayPickerDuration)
+                            }
                     }
 
                     if displayPickerDuration {

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

@@ -117,23 +117,7 @@ struct EditOverrideForm: View {
                 }
             }
             .sheet(isPresented: $state.isHelpSheetPresented) {
-                NavigationStack {
-                    List {
-                        Text("Lorem Ipsum Dolor Sit Amet")
-                    }
-                    .padding(.trailing, 10)
-                    .navigationBarTitle("Help", displayMode: .inline)
-
-                    Button { state.isHelpSheetPresented.toggle() }
-                    label: { Text("Got it!").frame(maxWidth: .infinity, alignment: .center) }
-                        .buttonStyle(.bordered)
-                        .padding(.top)
-                }
-                .padding()
-                .presentationDetents(
-                    [.fraction(0.9), .large],
-                    selection: $state.helpSheetDetent
-                )
+                OverrideHelpView(state: state, helpSheetDetent: $state.helpSheetDetent)
             }
         }
     }
@@ -160,9 +144,9 @@ struct EditOverrideForm: View {
                     Spacer()
                     Text("\(percentage.formatted(.number)) %")
                         .foregroundColor(!displayPickerPercentage ? .primary : .accentColor)
-                }
-                .onTapGesture {
-                    displayPickerPercentage = toggleScrollWheel(displayPickerPercentage)
+                        .onTapGesture {
+                            displayPickerPercentage = toggleScrollWheel(displayPickerPercentage)
+                        }
                 }
 
                 if displayPickerPercentage {
@@ -302,6 +286,9 @@ struct EditOverrideForm: View {
                                 state.convertTo12HourFormat(Int(truncating: start! as NSNumber))
                         )
                         .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
+                        .onTapGesture {
+                            displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
+                        }
 
                         Spacer()
 
@@ -316,9 +303,9 @@ struct EditOverrideForm: View {
                                 state.convertTo12HourFormat(Int(truncating: end! as NSNumber))
                         )
                         .foregroundColor(!displayPickerDisableSmbSchedule ? .primary : .accentColor)
-                    }
-                    .onTapGesture {
-                        displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
+                        .onTapGesture {
+                            displayPickerDisableSmbSchedule = toggleScrollWheel(displayPickerDisableSmbSchedule)
+                        }
                     }
 
                     if displayPickerDisableSmbSchedule {
@@ -383,6 +370,9 @@ struct EditOverrideForm: View {
                             Spacer()
                             Text("\(smbMinutes?.formatted(.number) ?? "\(state.defaultSmbMinutes)") min")
                                 .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
+                                .onTapGesture {
+                                    displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                                }
 
                             Spacer()
 
@@ -394,9 +384,9 @@ struct EditOverrideForm: View {
                             Spacer()
                             Text("\(uamMinutes?.formatted(.number) ?? "\(state.defaultUamMinutes)") min")
                                 .foregroundColor(!displayPickerSmbMinutes ? .primary : .accentColor)
-                        }
-                        .onTapGesture {
-                            displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                                .onTapGesture {
+                                    displayPickerSmbMinutes = toggleScrollWheel(displayPickerSmbMinutes)
+                                }
                         }
 
                         if displayPickerSmbMinutes {
@@ -452,9 +442,9 @@ struct EditOverrideForm: View {
                         Spacer()
                         Text(state.formatHrMin(Int(truncating: duration as NSNumber)))
                             .foregroundColor(!displayPickerDuration ? .primary : .accentColor)
-                    }
-                    .onTapGesture {
-                        displayPickerDuration = toggleScrollWheel(displayPickerDuration)
+                            .onTapGesture {
+                                displayPickerDuration = toggleScrollWheel(displayPickerDuration)
+                            }
                     }
 
                     if displayPickerDuration {

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

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

ファイルの差分が大きいため隠しています
+ 4 - 22
FreeAPS/Sources/Modules/Adjustments/View/TempTargets/AddTempTargetForm.swift


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

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

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

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

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

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

+ 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"
                 )
             }

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

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

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

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

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

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

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

+ 43 - 52
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)
@@ -157,23 +164,7 @@ struct AddContactTrickSheet: View {
                 }
             }
             .sheet(isPresented: $state.isHelpSheetPresented) {
-                NavigationStack {
-                    List {
-                        Text("Lorem Ipsum Dolor Sit Amet")
-                    }
-                    .padding(.trailing, 10)
-                    .navigationBarTitle("Help", displayMode: .inline)
-
-                    Button { state.isHelpSheetPresented.toggle() }
-                    label: { Text("Got it!").frame(maxWidth: .infinity, alignment: .center) }
-                        .buttonStyle(.bordered)
-                        .padding(.top)
-                }
-                .padding()
-                .presentationDetents(
-                    [.fraction(0.9), .large],
-                    selection: $state.helpSheetDetent
-                )
+                ContactImageHelpView(state: state, helpSheetDetent: $state.helpSheetDetent)
             }
         }
     }
@@ -202,7 +193,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 +201,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 +221,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 +232,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()
         }
     }

+ 51 - 58
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)
@@ -124,35 +133,19 @@ struct ContactTrickDetailView: View {
             }
         }
         .sheet(isPresented: $state.isHelpSheetPresented) {
-            NavigationStack {
-                List {
-                    Text("Lorem Ipsum Dolor Sit Amet")
-                }
-                .padding(.trailing, 10)
-                .navigationBarTitle("Help", displayMode: .inline)
-
-                Button { state.isHelpSheetPresented.toggle() }
-                label: { Text("Got it!").frame(maxWidth: .infinity, alignment: .center) }
-                    .buttonStyle(.bordered)
-                    .padding(.top)
-            }
-            .padding()
-            .presentationDetents(
-                [.fraction(0.9), .large],
-                selection: $state.helpSheetDetent
-            )
+            ContactImageHelpView(state: state, helpSheetDetent: $state.helpSheetDetent)
         }
     }
 
     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 +170,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 +197,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)

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

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

+ 19 - 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)
                                 }
 
@@ -74,13 +74,22 @@ extension ContactTrick {
                     }
                     .onDelete(perform: onDelete)
                 }
+
+                Section {} header: {
+                    Text(
+                        "Add one or more contacts to your iOS Contacts to display real-time Trio metrics on your watch face. Be sure to grant Trio full access to your Contacts when prompted."
+                    )
+                    .textCase(nil)
+                    .foregroundStyle(.secondary)
+                }
+
             }.listRowBackground(Color.chart)
         }
 
         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) {

ファイルの差分が大きいため隠しています
+ 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> {

ファイルの差分が大きいため隠しています
+ 57 - 20
FreeAPS/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift


ファイルの差分が大きいため隠しています
+ 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"
                 )
             }

+ 0 - 1
FreeAPS/Sources/Modules/Home/HomeDataFlow.swift

@@ -13,6 +13,5 @@ protocol HomeProvider: Provider {
     func tempTargets(hours: Int) -> [TempTarget]
     func pumpReservoir() -> Decimal?
     func tempTarget() -> TempTarget?
-    func announcement(_ hours: Int) -> [Announcement]
     func getBGTarget() async -> BGTargets
 }

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

@@ -7,7 +7,6 @@ extension Home {
         @Injected() var apsManager: APSManager!
         @Injected() var glucoseStorage: GlucoseStorage!
         @Injected() var tempTargetsStorage: TempTargetsStorage!
-        @Injected() var announcementStorage: AnnouncementsStorage!
 
         func pumpTimeZone() -> TimeZone? {
             apsManager.pumpManager?.status.timeZone
@@ -27,16 +26,10 @@ extension Home {
             tempTargetsStorage.current()
         }
 
-        func announcement(_ hours: Int) -> [Announcement] {
-            announcementStorage.validate().filter {
-                $0.createdAt.addingTimeInterval(hours.hours.timeInterval) > Date()
-            }
-        }
-
         func pumpSettings() -> PumpSettings {
             storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self)
                 ?? 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 - 5
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -20,7 +20,6 @@ extension Home {
         private let timer = DispatchTimer(timeInterval: 5)
         private(set) var filteredHours = 24
         var manualGlucose: [BloodGlucose] = []
-        var announcement: [Announcement] = []
         var uploadStats = false
         var recentGlucose: BloodGlucose?
         var maxBasal: Decimal = 2
@@ -59,7 +58,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
@@ -68,7 +67,6 @@ extension Home {
         var totalBolus: Decimal = 0
         var isStatusPopupPresented: Bool = false
         var isLegendPresented: Bool = false
-        var legendSheetDetent = PresentationDetent.large
         var totalInsulinDisplayType: TotalInsulinDisplayType = .totalDailyDose
         var roundedTotalBolus: String = ""
         var selectedTab: Int = 0
@@ -357,7 +355,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 +572,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

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


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません