浏览代码

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

Deniz Cengiz 1 年之前
父节点
当前提交
dffe63b961
共有 64 个文件被更改,包括 470 次插入824 次删除
  1. 12 12
      FreeAPS.xcodeproj/project.pbxproj
  2. 0 1
      FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json
  3. 0 97
      FreeAPS/Sources/APS/APSManager.swift
  4. 0 52
      FreeAPS/Sources/APS/FetchAnnouncementsManager.swift
  5. 0 2
      FreeAPS/Sources/APS/OpenAPS/Constants.swift
  6. 0 80
      FreeAPS/Sources/APS/Storage/AnnouncementsStorage.swift
  7. 1 1
      FreeAPS/Sources/APS/Storage/OverrideStorage.swift
  8. 0 1
      FreeAPS/Sources/Application/FreeAPSApp.swift
  9. 0 1
      FreeAPS/Sources/Assemblies/APSAssembly.swift
  10. 0 1
      FreeAPS/Sources/Assemblies/StorageAssembly.swift
  11. 0 6
      FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings
  12. 0 6
      FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings
  13. 0 6
      FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings
  14. 0 6
      FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings
  15. 0 6
      FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings
  16. 0 6
      FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings
  17. 0 6
      FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings
  18. 0 6
      FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings
  19. 0 6
      FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings
  20. 0 6
      FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings
  21. 0 6
      FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings
  22. 0 6
      FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings
  23. 0 6
      FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings
  24. 0 6
      FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings
  25. 0 6
      FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings
  26. 0 6
      FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings
  27. 0 6
      FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings
  28. 0 6
      FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings
  29. 0 6
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  30. 0 6
      FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings
  31. 0 6
      FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings
  32. 0 6
      FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings
  33. 0 6
      FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings
  34. 0 57
      FreeAPS/Sources/Models/Announcement.swift
  35. 0 5
      FreeAPS/Sources/Models/FreeAPSSettings.swift
  36. 7 3
      FreeAPS/Sources/Modules/Adjustments/View/AdjustmentsRootView.swift
  37. 19 29
      FreeAPS/Sources/Modules/Adjustments/View/Overrides/AddOverrideForm.swift
  38. 19 29
      FreeAPS/Sources/Modules/Adjustments/View/Overrides/EditOverrideForm.swift
  39. 53 0
      FreeAPS/Sources/Modules/Adjustments/View/Overrides/OverrideHelpView.swift
  40. 4 22
      FreeAPS/Sources/Modules/Adjustments/View/TempTargets/AddTempTargetForm.swift
  41. 16 3
      FreeAPS/Sources/Modules/Adjustments/View/TempTargets/EditTempTargetForm.swift
  42. 39 0
      FreeAPS/Sources/Modules/Adjustments/View/TempTargets/TempTargetHelpView.swift
  43. 3 3
      FreeAPS/Sources/Modules/Adjustments/View/ViewElements/TargetPicker.swift
  44. 1 17
      FreeAPS/Sources/Modules/ContactImage/View/AddContactImageSheet.swift
  45. 1 17
      FreeAPS/Sources/Modules/ContactImage/View/ContactImageDetailView.swift
  46. 75 0
      FreeAPS/Sources/Modules/ContactImage/View/ContactImageHelpView.swift
  47. 9 0
      FreeAPS/Sources/Modules/ContactImage/View/ContactImageRootView.swift
  48. 0 1
      FreeAPS/Sources/Modules/Home/HomeDataFlow.swift
  49. 0 7
      FreeAPS/Sources/Modules/Home/HomeProvider.swift
  50. 0 1
      FreeAPS/Sources/Modules/Home/HomeStateModel.swift
  51. 46 25
      FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/BasalChart.swift
  52. 1 1
      FreeAPS/Sources/Modules/Home/View/Chart/ChartElements/OverrideView.swift
  53. 56 53
      FreeAPS/Sources/Modules/Home/View/Header/LoopStatusView.swift
  54. 8 4
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  55. 0 2
      FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift
  56. 1 1
      FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift
  57. 2 35
      FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutFetchView.swift
  58. 1 0
      FreeAPS/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift
  59. 0 4
      FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift
  60. 24 0
      FreeAPS/Sources/Modules/Treatments/TreatmentsStateModel.swift
  61. 31 13
      FreeAPS/Sources/Modules/Treatments/View/TreatmentsRootView.swift
  62. 0 35
      FreeAPS/Sources/Services/Network/Nightscout/NightscoutAPI.swift
  63. 41 70
      FreeAPS/Sources/Services/Network/Nightscout/NightscoutManager.swift
  64. 0 1
      LiveActivity/Views/WidgetItems/LiveActivityIOBLabelView.swift

+ 12 - 12
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 */; };
@@ -486,8 +483,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 */; };
+		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 */; };
 		DDA6E2852D2361F800C2988C /* LoopStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6E2842D2361F800C2988C /* LoopStatusView.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 */; };
@@ -819,8 +819,6 @@
 		38569346270B5DFB0002C50D /* AppGroupSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppGroupSource.swift; sourceTree = "<group>"; };
 		38569352270B5E350002C50D /* CGMRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMRootView.swift; sourceTree = "<group>"; };
 		385CEA8125F23DFD002D6D5B /* NightscoutStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutStatus.swift; sourceTree = "<group>"; };
-		385CEAC025F2EA52002D6D5B /* Announcement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Announcement.swift; sourceTree = "<group>"; };
-		385CEAC325F2F154002D6D5B /* AnnouncementsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementsStorage.swift; sourceTree = "<group>"; };
 		3862CC2D2743F9F700BF832C /* CalendarManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarManager.swift; sourceTree = "<group>"; };
 		3870FF4225EC13F40088248F /* BloodGlucose.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BloodGlucose.swift; sourceTree = "<group>"; };
 		3871F39B25ED892B0013ECB5 /* TempTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTarget.swift; sourceTree = "<group>"; };
@@ -848,7 +846,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>"; };
@@ -1193,8 +1190,11 @@
 		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>"; };
 		DDA6E2842D2361F800C2988C /* LoopStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStatusView.swift; sourceTree = "<group>"; };
+		DDA6E3562D25988500C2988C /* ContactImageHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageHelpView.swift; sourceTree = "<group>"; };
 		DDB37CC22D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		DDB37CC32D05044D00D99BF4 /* ContactTrickEntryStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactTrickEntryStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageStorage.swift; sourceTree = "<group>"; };
@@ -1826,7 +1826,6 @@
 				CE95BF562BA5F5FE00DC3DE3 /* PluginManager.swift */,
 				3811DF0F25CAAAE200A708ED /* APSManager.swift */,
 				38BF021E25E7F0DE00579895 /* DeviceDataManager.swift */,
-				38A43597262E0E4900E80935 /* FetchAnnouncementsManager.swift */,
 				38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */,
 				38192E06261BA9960094D973 /* FetchTreatmentsManager.swift */,
 				3856933F270B57A00002C50D /* CGM */,
@@ -2005,7 +2004,6 @@
 				DD07CA132CE80B73002D45A9 /* TimeInRangeChartStyle.swift */,
 				DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */,
 				DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */,
-				385CEAC025F2EA52002D6D5B /* Announcement.swift */,
 				388E5A5F25B6F2310019842D /* Autosens.swift */,
 				38A00B1E25FC00F7006BC0B0 /* Autotune.swift */,
 				388358C725EEF6D200E024B2 /* BasalProfileEntry.swift */,
@@ -2099,7 +2097,6 @@
 			isa = PBXGroup;
 			children = (
 				CE82E02428E867BA00473A9C /* AlertStorage.swift */,
-				385CEAC325F2F154002D6D5B /* AnnouncementsStorage.swift */,
 				38AEE75625F0F18E0013F05B /* CarbsStorage.swift */,
 				DDB37CC42D05048F00D99BF4 /* ContactImageStorage.swift */,
 				5864E8582C42CFAE00294306 /* DeterminationStorage.swift */,
@@ -2496,6 +2493,7 @@
 		BD793CAD2CE7660C00D669AC /* Overrides */ = {
 			isa = PBXGroup;
 			children = (
+				DDA6E31F2D258E0500C2988C /* OverrideHelpView.swift */,
 				DDD1631B2C4C697400CD525A /* AddOverrideForm.swift */,
 				DDD163192C4C695E00CD525A /* EditOverrideForm.swift */,
 			);
@@ -2505,6 +2503,7 @@
 		BD793CAE2CE7661D00D669AC /* TempTargets */ = {
 			isa = PBXGroup;
 			children = (
+				DDA6E3212D25901100C2988C /* TempTargetHelpView.swift */,
 				58A3D5392C96D4DE003F90FC /* AddTempTargetForm.swift */,
 				5825A1BD2C97335C0046467E /* EditTempTargetForm.swift */,
 			);
@@ -3009,6 +3008,7 @@
 		E592A3722CEEC038009A472C /* View */ = {
 			isa = PBXGroup;
 			children = (
+				DDA6E3562D25988500C2988C /* ContactImageHelpView.swift */,
 				E592A3712CEEC038009A472C /* ContactImageRootView.swift */,
 				BDC531112D1060FA00088832 /* ContactImageDetailView.swift */,
 				BDC531132D10611D00088832 /* AddContactImageSheet.swift */,
@@ -3436,7 +3436,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 */,
@@ -3558,9 +3557,11 @@
 				DD1745482C55C61D00211FAC /* AutosensSettingsStateModel.swift in Sources */,
 				DD1745462C55C61500211FAC /* AutosensSettingsProvider.swift in Sources */,
 				DDA6E2852D2361F800C2988C /* LoopStatusView.swift in Sources */,
+				DDA6E3202D258E0500C2988C /* OverrideHelpView.swift in Sources */,
 				DDA6E2502D22187500C2988C /* ChartLegendView.swift in Sources */,
 				3811DEAF25C9D88300A708ED /* KeyValueStorage.swift in Sources */,
 				DDD6D4D32CDE90720029439A /* HbA1cDisplayUnit.swift in Sources */,
+				DDA6E3572D25988500C2988C /* ContactImageHelpView.swift in Sources */,
 				38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */,
 				388358C825EEF6D200E024B2 /* BasalProfileEntry.swift in Sources */,
 				3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */,
@@ -3691,8 +3692,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 */,
@@ -3720,7 +3721,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 */,

+ 0 - 1
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,

+ 0 - 97
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!
@@ -557,101 +555,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,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 0 - 5
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
@@ -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
         }

+ 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 - 17
FreeAPS/Sources/Modules/ContactImage/View/AddContactImageSheet.swift

@@ -164,23 +164,7 @@ struct AddContactImageSheet: View {
                 }
             }
             .sheet(isPresented: $state.isHelpSheetPresented) {
-                NavigationStack {
-                    List {
-                        Text("Lorem Ipsum Dolor Sit Amet")
-                    }
-                    .padding(.trailing, 10)
-                    .navigationBarTitle("Help", displayMode: .inline)
-
-                    Button { state.isHelpSheetPresented.toggle() }
-                    label: { Text("Got it!").frame(maxWidth: .infinity, alignment: .center) }
-                        .buttonStyle(.bordered)
-                        .padding(.top)
-                }
-                .padding()
-                .presentationDetents(
-                    [.fraction(0.9), .large],
-                    selection: $state.helpSheetDetent
-                )
+                ContactImageHelpView(state: state, helpSheetDetent: $state.helpSheetDetent)
             }
         }
     }

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

@@ -133,23 +133,7 @@ struct ContactImageDetailView: View {
             }
         }
         .sheet(isPresented: $state.isHelpSheetPresented) {
-            NavigationStack {
-                List {
-                    Text("Lorem Ipsum Dolor Sit Amet")
-                }
-                .padding(.trailing, 10)
-                .navigationBarTitle("Help", displayMode: .inline)
-
-                Button { state.isHelpSheetPresented.toggle() }
-                label: { Text("Got it!").frame(maxWidth: .infinity, alignment: .center) }
-                    .buttonStyle(.bordered)
-                    .padding(.top)
-            }
-            .padding()
-            .presentationDetents(
-                [.fraction(0.9), .large],
-                selection: $state.helpSheetDetent
-            )
+            ContactImageHelpView(state: state, helpSheetDetent: $state.helpSheetDetent)
         }
     }
 

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

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

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

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

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

+ 0 - 7
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,12 +26,6 @@ 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))

+ 0 - 1
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

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

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

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

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

+ 56 - 53
FreeAPS/Sources/Modules/Home/View/Header/LoopStatusView.swift

@@ -165,11 +165,12 @@ struct LoopStatusView: View {
     }
 
     // TODO: Consolidate all mmol parsing methods (in TagCloudView, NightscoutManager and HomeRootView) to one central func
-    private func parseReasonConclusion(_ reasonConclusion: String, isMmolL _: Bool) -> String {
+    private func parseReasonConclusion(_ reasonConclusion: String, isMmolL: Bool) -> String {
         let patterns = [
-            "minGuardBG\\s*-?\\d+\\.?\\d*<-?\\d+\\.?\\d*",
-            "Eventual BG\\s*-?\\d+\\.?\\d*\\s*>=\\s*-?\\d+\\.?\\d*",
-            "\\S+\\s+-?\\d+\\.?\\d*\\s*>\\s*\\d+%\\s+of\\s+BG\\s+-?\\d+\\.?\\d*"
+            "minGuardBG\\s*-?\\d+\\.?\\d*<-?\\d+\\.?\\d*", // minGuardBG x<y
+            "Eventual BG\\s*-?\\d+\\.?\\d*\\s*>=\\s*-?\\d+\\.?\\d*", // Eventual BG x >= target
+            "Eventual BG\\s*-?\\d+\\.?\\d*\\s*<\\s*-?\\d+\\.?\\d*", // Eventual BG x < target
+            "(\\S+)\\s+(-?\\d+\\.?\\d*)\\s*>\\s*(\\d+)%\\s+of\\s+BG\\s+(-?\\d+\\.?\\d*)" // maxDelta x > y% of BG z
         ]
         let pattern = patterns.joined(separator: "|")
         let regex = try! NSRegularExpression(pattern: pattern)
@@ -192,61 +193,63 @@ struct LoopStatusView: View {
             guard let range = Range(match.range, in: reasonConclusion) else { continue }
             let matchedString = String(reasonConclusion[range])
 
-            if matchedString.contains("<") {
-                // Handle "minGuardBG x<y" pattern
-                let parts = matchedString.components(separatedBy: "<")
-                if parts.count == 2,
-                   let firstValue = Double(
-                       parts[0]
-                           .components(separatedBy: CharacterSet(charactersIn: "0123456789.-").inverted).joined()
-                   ),
-                   let secondValue = Double(
-                       parts[1]
-                           .components(separatedBy: CharacterSet(charactersIn: "0123456789.-").inverted).joined()
-                   )
-                {
-                    let formattedFirstValue = convertToMmolL(String(firstValue))
-                    let formattedSecondValue = convertToMmolL(String(secondValue))
-                    let formattedString = "minGuardBG \(formattedFirstValue)<\(formattedSecondValue)"
-                    updatedConclusion.replaceSubrange(range, with: formattedString)
-                }
-            } else if matchedString.contains(">=") {
-                // Handle "Eventual BG x >= target" pattern
-                let parts = matchedString.components(separatedBy: " >= ")
-                if parts.count == 2,
-                   let firstValue = Double(
-                       parts[0]
-                           .components(separatedBy: CharacterSet(charactersIn: "0123456789.-").inverted).joined()
-                   ),
-                   let secondValue = Double(
-                       parts[1]
-                           .components(separatedBy: CharacterSet(charactersIn: "0123456789.-").inverted).joined()
-                   )
-                {
-                    let formattedFirstValue = convertToMmolL(String(firstValue))
-                    let formattedSecondValue = convertToMmolL(String(secondValue))
-                    let formattedString = "Eventual BG \(formattedFirstValue) >= \(formattedSecondValue)"
-                    updatedConclusion.replaceSubrange(range, with: formattedString)
-                }
-            } else if matchedString.contains(">") {
-                // Handle "maxDelta 37 > 20% of BG 95" style
-                let pattern = "(\\S+)\\s+(-?\\d+\\.?\\d*)\\s*>\\s*(\\d+)%\\s+of\\s+BG\\s+(-?\\d+\\.?\\d*)"
-                let localRegex = try! NSRegularExpression(pattern: pattern)
-                if let localMatch = localRegex.firstMatch(
+            if isMmolL {
+                if matchedString.contains("<"), matchedString.contains("Eventual BG"), !matchedString.contains("=") {
+                    // Handle "Eventual BG x < target" pattern
+                    let parts = matchedString.components(separatedBy: "<")
+                    if parts.count == 2 {
+                        let bgPart = parts[0].replacingOccurrences(of: "Eventual BG", with: "")
+                            .trimmingCharacters(in: .whitespaces)
+                        let targetValue = parts[1].trimmingCharacters(in: .whitespaces)
+                        let formattedBGPart = convertToMmolL(bgPart)
+                        let formattedTargetValue = convertToMmolL(targetValue)
+                        let formattedString = "Eventual BG \(formattedBGPart)<\(formattedTargetValue)"
+                        updatedConclusion.replaceSubrange(range, with: formattedString)
+                    }
+                } else if matchedString.contains("<"), matchedString.contains("minGuardBG") {
+                    // Handle "minGuardBG x<y" pattern
+                    let parts = matchedString.components(separatedBy: "<")
+                    if parts.count == 2 {
+                        let firstValue = parts[0].trimmingCharacters(in: .whitespaces)
+                        let secondValue = parts[1].trimmingCharacters(in: .whitespaces)
+                        let formattedFirstValue = convertToMmolL(firstValue)
+                        let formattedSecondValue = convertToMmolL(secondValue)
+                        let formattedString = "minGuardBG \(formattedFirstValue)<\(formattedSecondValue)"
+                        updatedConclusion.replaceSubrange(range, with: formattedString)
+                    }
+                } else if matchedString.contains(">=") {
+                    // Handle "Eventual BG x >= target" pattern
+                    let parts = matchedString.components(separatedBy: " >= ")
+                    if parts.count == 2 {
+                        let firstValue = parts[0].replacingOccurrences(of: "Eventual BG", with: "")
+                            .trimmingCharacters(in: .whitespaces)
+                        let secondValue = parts[1].trimmingCharacters(in: .whitespaces)
+                        let formattedFirstValue = convertToMmolL(firstValue)
+                        let formattedSecondValue = convertToMmolL(secondValue)
+                        let formattedString = "Eventual BG \(formattedFirstValue) >= \(formattedSecondValue)"
+                        updatedConclusion.replaceSubrange(range, with: formattedString)
+                    }
+                } else if let localMatch = regex.firstMatch(
                     in: matchedString,
                     range: NSRange(matchedString.startIndex..., in: matchedString)
                 ) {
-                    let metric = String(matchedString[Range(localMatch.range(at: 1), in: matchedString)!])
-                    let firstValue = String(matchedString[Range(localMatch.range(at: 2), in: matchedString)!])
-                    let percentage = String(matchedString[Range(localMatch.range(at: 3), in: matchedString)!])
-                    let bgValue = String(matchedString[Range(localMatch.range(at: 4), in: matchedString)!])
+                    // Handle "maxDelta 37 > 20% of BG 95" style
+                    if match.numberOfRanges == 5 {
+                        let metric = String(matchedString[Range(localMatch.range(at: 1), in: matchedString)!])
+                        let firstValue = String(matchedString[Range(localMatch.range(at: 2), in: matchedString)!])
+                        let percentage = String(matchedString[Range(localMatch.range(at: 3), in: matchedString)!])
+                        let bgValue = String(matchedString[Range(localMatch.range(at: 4), in: matchedString)!])
 
-                    let formattedFirstValue = convertToMmolL(firstValue)
-                    let formattedBGValue = convertToMmolL(bgValue)
+                        let formattedFirstValue = convertToMmolL(firstValue)
+                        let formattedBGValue = convertToMmolL(bgValue)
 
-                    let formattedString = "\(metric) \(formattedFirstValue) > \(percentage)% of BG \(formattedBGValue)"
-                    updatedConclusion.replaceSubrange(range, with: formattedString)
+                        let formattedString = "\(metric) \(formattedFirstValue) > \(percentage)% of BG \(formattedBGValue)"
+                        updatedConclusion.replaceSubrange(range, with: formattedString)
+                    }
                 }
+            } else {
+                // When isMmolL is false, ensure the original value is retained without duplication
+                updatedConclusion.replaceSubrange(range, with: matchedString)
             }
         }
 

+ 8 - 4
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -732,7 +732,10 @@ extension Home {
 
                 }.padding(.horizontal, 10).padding(.bottom, UIDevice.adjustPadding(min: nil, max: 10))
                     .overlay(alignment: .bottom) {
-                        bolusProgressBar(progress).padding(.horizontal, 18).offset(y: 48)
+                        // Use a geo-based offset here to position progress bar independent of device size
+                        let offset = geo.size.height * 0.0725
+                        bolusProgressBar(progress).padding(.horizontal, 18)
+                            .offset(y: offset)
                     }.clipShape(RoundedRectangle(cornerRadius: 15))
             }
         }
@@ -935,13 +938,14 @@ extension Home {
                             .font(.system(size: 40))
                             .foregroundStyle(Color.tabBar)
                             .padding(.bottom, 1)
-                            .padding(.horizontal, 20)
+                            .padding(.horizontal, 22.5)
                     }
                 )
             }.ignoresSafeArea(.keyboard, edges: .bottom).blur(radius: state.waitForSuggestion ? 8 : 0)
                 .onChange(of: selectedTab) {
-                    print("current path is empty: \(settingsPath.isEmpty)")
-                    settingsPath = NavigationPath()
+                    if !settingsPath.isEmpty {
+                        settingsPath = NavigationPath()
+                    }
                 }
         }
 

+ 0 - 2
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -32,7 +32,6 @@ extension NightscoutConfig {
         @Published var dia: Decimal = 6
         @Published var maxBasal: Decimal = 2
         @Published var maxBolus: Decimal = 10
-        @Published var allowAnnouncements: Bool = false
         @Published var isConnectedToNS: Bool = false
 
         @Published var isImportResultReviewPresented: Bool = false
@@ -57,7 +56,6 @@ extension NightscoutConfig {
             maxBolus = settingsManager.pumpSettings.maxBolus
             changeUploadGlucose = (cgmManager.cgmGlucoseSourceType != CGMType.plugin)
 
-            subscribeSetting(\.allowAnnouncements, on: $allowAnnouncements) { allowAnnouncements = $0 }
             subscribeSetting(\.isUploadEnabled, on: $isUploadEnabled) { isUploadEnabled = $0 }
             subscribeSetting(\.isDownloadEnabled, on: $isDownloadEnabled) { isDownloadEnabled = $0 }
             subscribeSetting(\.useLocalGlucoseSource, on: $useLocalSource) { useLocalSource = $0 }

+ 1 - 1
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift

@@ -41,7 +41,7 @@ extension NightscoutConfig {
                                 }
                             })
                             NavigationLink("Upload", destination: NightscoutUploadView(state: state))
-                            NavigationLink("Fetch & Remote Control", destination: NightscoutFetchView(state: state))
+                            NavigationLink("Fetch", destination: NightscoutFetchView(state: state))
                         }
                     ).listRowBackground(Color.chart)
 

+ 2 - 35
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutFetchView.swift

@@ -37,41 +37,8 @@ struct NightscoutFetchView: View {
                         "The Fetch Treatments toggle enables fetching of carbs and temp targets entered in Careportal or by another uploading device than Trio from Nightscout."
                     )
                 },
-                headerText: "Remote & Fetch Capabilities"
+                headerText: "Fetch NS Care Portal Data"
             )
-
-            if state.isDownloadEnabled {
-                SettingInputSection(
-                    decimalValue: $decimalPlaceholder,
-                    booleanValue: $state.allowAnnouncements,
-                    shouldDisplayHint: $shouldDisplayHint,
-                    selectedVerboseHint: Binding(
-                        get: { selectedVerboseHint },
-                        set: {
-                            selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = "Allow Remote Control of Trio"
-                        }
-                    ),
-                    units: state.units,
-                    type: .boolean,
-                    label: "Allow Remote Control of Trio",
-                    miniHint: "Enables selected remote control capabilities via Nightscout.",
-                    verboseHint: VStack(alignment: .leading, spacing: 10) {
-                        Text("Default: OFF").bold()
-                        Text("When enabled you allow the following remote functions through announcements from Nightscout:")
-                        VStack(alignment: .leading) {
-                            Text("• Suspend/Resume Pump")
-                            Text("• Opening/Closing Loop")
-                            Text("• Set Temp Basal")
-                            Text("• Enact Bolus")
-                        }
-                    }
-                )
-            } else {
-                Section {
-                    Text("'Allow Fetching from Nightscout' must be enabled to allow for Trio Remote Control.")
-                }.listRowBackground(Color.tabBar)
-            }
         }
         .listSectionSpacing(sectionSpacing)
         .sheet(isPresented: $shouldDisplayHint) {
@@ -83,7 +50,7 @@ struct NightscoutFetchView: View {
                 sheetTitle: "Help"
             )
         }
-        .navigationTitle("Fetch & Remote")
+        .navigationTitle("Fetch")
         .navigationBarTitleDisplayMode(.automatic)
         .scrollContentBackground(.hidden)
         .background(appState.trioBackgroundColor(for: colorScheme))

+ 1 - 0
FreeAPS/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift

@@ -68,6 +68,7 @@ extension PumpConfig {
                                                                 Text("• Medtronic")
                                                                 Text("• Omnipod Eros")
                                                                 Text("• Omnipod Dash")
+                                                                Text("• Dana (RS/-i)")
                                                                 Text("• Pump Simulator")
                                                             }
                                                             Text(

+ 0 - 4
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -266,10 +266,6 @@ extension Settings {
 //                                .navigationLink(to: .configEditor(file: OpenAPS.Settings.profile), from: self)
 //                            //                            Text("Carbs")
 //                            //                                .navigationLink(to: .configEditor(file: OpenAPS.Monitor.carbHistory), from: self)
-//                            //                            Text("Announcements")
-//                            //                                .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.announcements), from: self)
-//                            //                            Text("Enacted announcements")
-//                            //                                .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.announcementsEnacted), from: self)
 //                            Text("Autotune")
 //                                .navigationLink(to: .configEditor(file: OpenAPS.Settings.autotune), from: self)
 //                        }

+ 24 - 0
FreeAPS/Sources/Modules/Treatments/TreatmentsStateModel.swift

@@ -126,6 +126,9 @@ extension Treatments {
 
         typealias PumpEvent = PumpEventStored.EventType
 
+        var isBolusInProgress: Bool = false
+        private var bolusProgressCancellable: AnyCancellable?
+
         func unsubscribe() {
             subscriptions.forEach { $0.cancel() }
             subscriptions.removeAll()
@@ -145,6 +148,7 @@ extension Treatments {
             registerHandlers()
             registerSubscribers()
             setupBolusStateConcurrently()
+            subscribeToBolusProgress()
         }
 
         deinit {
@@ -155,6 +159,8 @@ extension Treatments {
             // Cancel Combine subscriptions
             unsubscribe()
 
+            bolusProgressCancellable?.cancel()
+
             debug(.bolusState, "Bolus.StateModel deinitialized")
         }
 
@@ -191,6 +197,24 @@ extension Treatments {
             }
         }
 
+        /// Observes changes to the `bolusProgress` published by the `apsManager` to update the `isBolusInProgress` property in real time.
+        ///
+        /// - Important:
+        ///   - `apsManager.bolusProgress` is a `CurrentValueSubject<Decimal?, Never>`.
+        ///   - When a bolus starts, this subject emits `0` (or a fraction like `0.1, 0.5, etc.`).
+        ///   - When the bolus finishes, the subject is typically set to `nil`.
+        ///   - This treats ANY non-nil value as “bolus in progress.”
+        ///
+        private func subscribeToBolusProgress() {
+            bolusProgressCancellable = apsManager.bolusProgress
+                .receive(on: DispatchQueue.main)
+                .sink { [weak self] progressValue in
+                    guard let self = self else { return }
+                    // If progressValue is non-nil, a bolus is in progress.
+                    self.isBolusInProgress = (progressValue != nil)
+                }
+        }
+
         // MARK: - Basal
 
         private enum SettingType {

+ 31 - 13
FreeAPS/Sources/Modules/Treatments/View/TreatmentsRootView.swift

@@ -365,28 +365,38 @@ extension Treatments {
         }
 
         var treatmentButton: some View {
-            Button {
+            var treatmentButtonBackground = Color(.systemBlue)
+            if limitExceeded {
+                treatmentButtonBackground = Color(.systemRed)
+            } else if disableTaskButton {
+                treatmentButtonBackground = Color(.systemGray)
+            }
+
+            return Button {
                 state.invokeTreatmentsTask()
             } label: {
-                taskButtonLabel
-                    .font(.headline)
-                    .foregroundStyle(Color.white)
-                    .frame(maxWidth: .infinity, alignment: .center)
-                    .frame(height: 35)
+                HStack {
+                    if state.isBolusInProgress && state
+                        .amount > 0 && !state.externalInsulin && (state.carbs == 0 || state.fat == 0 || state.protein == 0)
+                    {
+                        ProgressView()
+                    }
+                    taskButtonLabel
+                }
+                .font(.headline)
+                .foregroundStyle(Color.white)
+                .frame(maxWidth: .infinity, alignment: .center)
+                .frame(height: 35)
             }
             .disabled(disableTaskButton)
-            .listRowBackground(
-                limitExceeded ? Color(.systemRed) :
-                    disableTaskButton ? Color(.systemGray) :
-                    Color(.systemBlue)
-            )
+            .listRowBackground(treatmentButtonBackground)
             .shadow(radius: 3)
             .clipShape(RoundedRectangle(cornerRadius: 8))
         }
 
         private var taskButtonLabel: some View {
             if pumpBolusLimitExceeded {
-                return Text("Max Bolus of \(state.maxBolus.description) U Exceeded")
+                return Text("Max Bolus of \(state.maxBolus.description) U E== 0xceeded")
             } else if externalBolusLimitExceeded {
                 return Text("Max External Bolus of \(state.maxExternal.description) U Exceeded")
             } else if carbLimitExceeded {
@@ -402,6 +412,10 @@ extension Treatments {
             let hasFatOrProtein = state.fat > 0 || state.protein > 0
             let bolusString = state.externalInsulin ? "External Insulin" : "Enact Bolus"
 
+            if state.isBolusInProgress && hasInsulin && !state.externalInsulin && (!hasCarbs || !hasFatOrProtein) {
+                return Text("Bolus In Progress...")
+            }
+
             switch (hasInsulin, hasCarbs, hasFatOrProtein) {
             case (true, true, true):
                 return Text("Log Meal and \(bolusString)")
@@ -447,7 +461,11 @@ extension Treatments {
         }
 
         private var disableTaskButton: Bool {
-            state.addButtonPressed || limitExceeded
+            (
+                state.isBolusInProgress && state
+                    .amount > 0 && !state.externalInsulin && (state.carbs == 0 || state.fat == 0 || state.protein == 0)
+            ) || state
+                .addButtonPressed || limitExceeded
         }
     }
 

+ 0 - 35
FreeAPS/Sources/Services/Network/Nightscout/NightscoutAPI.swift

@@ -286,41 +286,6 @@ extension NightscoutAPI {
         }
     }
 
-    func fetchAnnouncement(sinceDate: Date? = nil) -> AnyPublisher<[Announcement], Swift.Error> {
-        var components = URLComponents()
-        components.scheme = url.scheme
-        components.host = url.host
-        components.port = url.port
-        components.path = Config.treatmentsPath
-        components.queryItems = [
-            URLQueryItem(name: "find[eventType]", value: "Announcement"),
-            URLQueryItem(
-                name: "find[enteredBy]",
-                value: Announcement.remote.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
-            )
-        ]
-        if let date = sinceDate {
-            let dateItem = URLQueryItem(
-                name: "find[created_at][$gte]",
-                value: Formatter.iso8601withFractionalSeconds.string(from: date)
-            )
-            components.queryItems?.append(dateItem)
-        }
-
-        var request = URLRequest(url: components.url!)
-        request.allowsConstrainedNetworkAccess = false
-        request.timeoutInterval = Config.timeout
-
-        if let secret = secret {
-            request.addValue(secret.sha1(), forHTTPHeaderField: "api-secret")
-        }
-
-        return service.run(request)
-            .retry(Config.retryCount)
-            .decode(type: [Announcement].self, decoder: JSONCoding.decoder)
-            .eraseToAnyPublisher()
-    }
-
     func uploadTreatments(_ treatments: [NightscoutTreatment]) async throws {
         var components = URLComponents()
         components.scheme = url.scheme

+ 41 - 70
FreeAPS/Sources/Services/Network/Nightscout/NightscoutManager.swift

@@ -9,7 +9,6 @@ protocol NightscoutManager: GlucoseSource {
     func fetchGlucose(since date: Date) async -> [BloodGlucose]
     func fetchCarbs() async -> [CarbsEntry]
     func fetchTempTargets() async -> [TempTarget]
-    func fetchAnnouncements() -> AnyPublisher<[Announcement], Never>
     func deleteCarbs(withID id: String) async
     func deleteInsulin(withID id: String) async
     func deleteManualGlucose(withID id: String) async
@@ -35,7 +34,6 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
     @Injected() private var carbsStorage: CarbsStorage!
     @Injected() private var pumpHistoryStorage: PumpHistoryStorage!
     @Injected() private var storage: FileStorage!
-    @Injected() private var announcementsStorage: AnnouncementsStorage!
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var reachabilityManager: ReachabilityManager!
@@ -319,23 +317,9 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
     }
 
-    func fetchAnnouncements() -> AnyPublisher<[Announcement], Never> {
-        guard let nightscout = nightscoutAPI, isNetworkReachable, isDownloadEnabled else {
-            return Just([]).eraseToAnyPublisher()
-        }
-
-        let since = announcementsStorage.syncDate()
-        return nightscout.fetchAnnouncement(sinceDate: since)
-            .replaceError(with: [])
-            .eraseToAnyPublisher()
-    }
-
     func deleteCarbs(withID id: String) async {
         guard let nightscout = nightscoutAPI, isUploadEnabled else { return }
 
-        // TODO: - healthkit rewrite, deletion of FPUs
-//        healthkitManager.deleteCarbs(syncID: arg1, fpuID: arg2)
-
         do {
             try await nightscout.deleteCarbs(withId: id)
             debug(.nightscout, "Carbs deleted")
@@ -1155,14 +1139,15 @@ extension BaseNightscoutManager {
     // TODO: Consolidate all mmol parsing methods (in TagCloudView, NightscoutManager and HomeRootView) to one central func
     func parseReasonGlucoseValuesToMmolL(_ reason: String) -> String {
         let patterns = [
-            "ISF:\\s*-?\\d+\\.?\\d*→-?\\d+\\.?\\d*",
-            "Dev:\\s*-?\\d+\\.?\\d*",
-            "BGI:\\s*-?\\d+\\.?\\d*",
-            "Target:\\s*-?\\d+\\.?\\d*",
-            "(?:minPredBG|minGuardBG|IOBpredBG|COBpredBG|UAMpredBG)\\s+-?\\d+\\.?\\d*(?:<-?\\d+\\.?\\d*)?",
-            "minGuardBG\\s+-?\\d+\\.?\\d*<-?\\d+\\.?\\d*",
-            "Eventual BG\\s+-?\\d+\\.?\\d*\\s*>=\\s*-?\\d+\\.?\\d*",
-            "\\S+\\s+\\d+\\s*>\\s*\\d+%\\s+of\\s+BG\\s+\\d+"
+            "ISF:\\s*-?\\d+\\.?\\d*→-?\\d+\\.?\\d*", // ISF with arrow
+            "Dev:\\s*-?\\d+\\.?\\d*", // Dev pattern
+            "BGI:\\s*-?\\d+\\.?\\d*", // BGI pattern
+            "Target:\\s*-?\\d+\\.?\\d*", // Target pattern
+            "(?:minPredBG|minGuardBG|IOBpredBG|COBpredBG|UAMpredBG)\\s+-?\\d+\\.?\\d*(?:<-?\\d+\\.?\\d*)?", // minPredBG, etc.
+            "minGuardBG\\s+-?\\d+\\.?\\d*<-?\\d+\\.?\\d*", // minGuardBG x<y
+            "Eventual BG\\s+-?\\d+\\.?\\d*\\s*>=\\s*-?\\d+\\.?\\d*", // Eventual BG x >= target
+            "Eventual BG\\s+-?\\d+\\.?\\d*\\s*<\\s*-?\\d+\\.?\\d*", // Eventual BG x < target
+            "\\S+\\s+\\d+\\s*>\\s*\\d+%\\s+of\\s+BG\\s+\\d+" // maxDelta x > y% of BG z
         ]
         let pattern = patterns.joined(separator: "|")
         let regex = try! NSRegularExpression(pattern: pattern)
@@ -1183,7 +1168,7 @@ extension BaseNightscoutManager {
             let glucoseValueString = String(reason[range])
 
             if glucoseValueString.contains("→") {
-                // -- Handle ISF: X→Y
+                // Handle ISF: X→Y
                 let values = glucoseValueString.components(separatedBy: "→")
                 let firstNumber = values[0].components(separatedBy: ":")[1].trimmingCharacters(in: .whitespaces)
                 let secondNumber = values[1].trimmingCharacters(in: .whitespaces)
@@ -1192,58 +1177,45 @@ extension BaseNightscoutManager {
                 let formattedString = "ISF: \(firstValue)→\(secondValue)"
                 updatedReason.replaceSubrange(range, with: formattedString)
 
+            } else if glucoseValueString.contains("Eventual BG"), glucoseValueString.contains("<") {
+                // Handle Eventual BG XX < target
+                let parts = glucoseValueString.components(separatedBy: "<")
+                if parts.count == 2 {
+                    let bgPart = parts[0].replacingOccurrences(of: "Eventual BG", with: "").trimmingCharacters(in: .whitespaces)
+                    let targetValue = parts[1].trimmingCharacters(in: .whitespaces)
+                    let formattedBGPart = convertToMmolL(bgPart)
+                    let formattedTargetValue = convertToMmolL(targetValue)
+                    let formattedString = "Eventual BG \(formattedBGPart)<\(formattedTargetValue)"
+                    updatedReason.replaceSubrange(range, with: formattedString)
+                }
+
             } else if glucoseValueString.contains("<") {
-                // -- Handle minGuardBG (or minPredBG, etc.) x < y
-                let components = glucoseValueString
-                    .split(whereSeparator: { "<".contains($0) ||
-                            CharacterSet.whitespaces.contains($0.unicodeScalars.first!) })
-                    .filter { !$0.isEmpty }
-
-                if components.count >= 3 {
-                    let firstValue = convertToMmolL(String(components[1]))
-                    let secondValue = convertToMmolL(String(components[2]))
-                    let formattedString = "\(components[0]) \(firstValue)<\(secondValue)"
+                // Handle minGuardBG (or minPredBG, etc.) x < y
+                let parts = glucoseValueString.components(separatedBy: "<")
+                if parts.count == 2 {
+                    let firstValue = parts[0].trimmingCharacters(in: .whitespaces)
+                    let secondValue = parts[1].trimmingCharacters(in: .whitespaces)
+                    let formattedFirstValue = convertToMmolL(firstValue)
+                    let formattedSecondValue = convertToMmolL(secondValue)
+                    let formattedString = "minGuardBG \(formattedFirstValue)<\(formattedSecondValue)"
                     updatedReason.replaceSubrange(range, with: formattedString)
                 }
 
             } else if glucoseValueString.contains(">=") {
-                // -- Handle "Eventual BG X >= Y"
-                let components = glucoseValueString
-                    .split(whereSeparator: { " >= ".contains($0) ||
-                            CharacterSet.whitespaces.contains($0.unicodeScalars.first!) })
-                    .filter { !$0.isEmpty }
-
-                if components.count == 4 {
-                    let firstValue = convertToMmolL(String(components[2]))
-                    let secondValue = convertToMmolL(String(components[3]))
-                    let formattedString = "\(components[0]) \(components[1]) \(firstValue) >= \(secondValue)"
+                // Handle "Eventual BG X >= Y"
+                let parts = glucoseValueString.components(separatedBy: " >= ")
+                if parts.count == 2 {
+                    let firstValue = parts[0].replacingOccurrences(of: "Eventual BG", with: "")
+                        .trimmingCharacters(in: .whitespaces)
+                    let secondValue = parts[1].trimmingCharacters(in: .whitespaces)
+                    let formattedFirstValue = convertToMmolL(firstValue)
+                    let formattedSecondValue = convertToMmolL(secondValue)
+                    let formattedString = "Eventual BG \(formattedFirstValue) >= \(formattedSecondValue)"
                     updatedReason.replaceSubrange(range, with: formattedString)
                 }
 
-            } else if glucoseValueString.starts(with: "Dev:") {
-                // -- Handle Dev
-                let value = glucoseValueString.components(separatedBy: ":")[1].trimmingCharacters(in: .whitespaces)
-                let formattedValue = convertToMmolL(value)
-                let formattedString = "Dev: \(formattedValue)"
-                updatedReason.replaceSubrange(range, with: formattedString)
-
-            } else if glucoseValueString.starts(with: "BGI:") {
-                // -- Handle BGI
-                let value = glucoseValueString.components(separatedBy: ":")[1].trimmingCharacters(in: .whitespaces)
-                let formattedValue = convertToMmolL(value)
-                let formattedString = "BGI: \(formattedValue)"
-                updatedReason.replaceSubrange(range, with: formattedString)
-
-            } else if glucoseValueString.starts(with: "Target:") {
-                // -- Handle Target
-                let value = glucoseValueString.components(separatedBy: ":")[1].trimmingCharacters(in: .whitespaces)
-                let formattedValue = convertToMmolL(value)
-                let formattedString = "Target: \(formattedValue)"
-                updatedReason.replaceSubrange(range, with: formattedString)
-
             } else if glucoseValueString.contains(">"), glucoseValueString.contains("BG") {
-                // -- Handle "maxDelta 37 > 20% of BG 95" style
-                // Run a local regex that picks out the two BG values
+                // Handle "maxDelta 37 > 20% of BG 95" style
                 let localPattern = "(\\d+) > (\\d+)% of BG (\\d+)"
                 let localRegex = try! NSRegularExpression(pattern: localPattern)
                 let localMatches = localRegex.matches(
@@ -1258,7 +1230,6 @@ extension BaseNightscoutManager {
                     let firstValue = convertToMmolL(String(glucoseValueString[range1]))
                     let thirdValue = convertToMmolL(String(glucoseValueString[range3]))
 
-                    // e.g. "37 > 20% of BG 95" → "2.1 > 20% of BG 5.3"
                     let oldSnippet =
                         "\(glucoseValueString[range1]) > \(glucoseValueString[range2])% of BG \(glucoseValueString[range3])"
                     let newSnippet = "\(firstValue) > \(glucoseValueString[range2])% of BG \(thirdValue)"
@@ -1268,7 +1239,7 @@ extension BaseNightscoutManager {
                 }
 
             } else {
-                // -- Handle everything else, e.g. "minPredBG 39" etc.
+                // Handle everything else, e.g., "minPredBG 39", "COB 29", etc.
                 let parts = glucoseValueString.components(separatedBy: .whitespaces)
                 if parts.count >= 2 {
                     let metric = parts[0]

+ 0 - 1
LiveActivity/Views/WidgetItems/LiveActivityIOBLabelView.swift

@@ -16,7 +16,6 @@ struct LiveActivityIOBLabelView: View {
         let formatter = NumberFormatter()
         formatter.numberStyle = .decimal
         formatter.maximumFractionDigits = 1
-        formatter.decimalSeparator = "."
         return formatter
     }