Преглед изворни кода

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

Deniz Cengiz пре 1 година
родитељ
комит
ed257d7ce3
55 измењених фајлова са 1001 додато и 343 уклоњено
  1. 1 1
      Model/Classes+Properties/OpenAPS_Battery+CoreDataProperties.swift
  2. 2 2
      Model/Helper/CarbEntryStored+helper.swift
  3. 5 0
      Model/Helper/NSPredicates.swift
  4. 1 1
      Model/TrioCoreDataPersistentContainer.xcdatamodeld/TrioCoreDataPersistentContainer.xcdatamodel/contents
  5. 8 0
      Trio.xcodeproj/project.pbxproj
  6. 1 1
      Trio/Resources/javascript/bundle/autosens.js
  7. 1 1
      Trio/Resources/javascript/bundle/autotune-prep.js
  8. 1 1
      Trio/Resources/javascript/bundle/determine-basal.js
  9. 1 1
      Trio/Resources/javascript/bundle/iob.js
  10. 1 1
      Trio/Resources/javascript/bundle/meal.js
  11. 1 1
      Trio/Resources/javascript/bundle/profile.js
  12. 13 11
      Trio/Sources/APS/APSManager.swift
  13. 1 1
      Trio/Sources/APS/DeviceDataManager.swift
  14. 2 1
      Trio/Sources/APS/FetchGlucoseManager.swift
  15. 38 41
      Trio/Sources/APS/Storage/CarbsStorage.swift
  16. 2 1
      Trio/Sources/APS/Storage/GlucoseStorage.swift
  17. 2 0
      Trio/Sources/Application/TrioApp.swift
  18. 1 1
      Trio/Sources/Models/DecimalPickerSettings.swift
  19. 22 0
      Trio/Sources/Models/GlucoseNotificationsOption.swift
  20. 6 3
      Trio/Sources/Models/TrioSettings.swift
  21. 2 3
      Trio/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+Overrides.swift
  22. 1 1
      Trio/Sources/Modules/Adjustments/View/Overrides/AddOverrideForm.swift
  23. 1 1
      Trio/Sources/Modules/Adjustments/View/Overrides/AdjustmentsRootView+Overrides.swift
  24. 1 1
      Trio/Sources/Modules/Adjustments/View/Overrides/EditOverrideForm.swift
  25. 1 1
      Trio/Sources/Modules/Adjustments/View/TempTargets/AdjustmentsRootView+TempTargets.swift
  26. 11 9
      Trio/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift
  27. 1 1
      Trio/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift
  28. 3 1
      Trio/Sources/Modules/CarbRatioEditor/View/CarbRatioEditorRootView.swift
  29. 337 33
      Trio/Sources/Modules/DataTable/DataTableStateModel.swift
  30. 229 0
      Trio/Sources/Modules/DataTable/View/CarbEntryEditorView.swift
  31. 29 6
      Trio/Sources/Modules/DataTable/View/DataTableRootView.swift
  32. 2 2
      Trio/Sources/Modules/GlucoseNotificationSettings/GlucoseNotificationSettingsStateModel.swift
  33. 111 64
      Trio/Sources/Modules/GlucoseNotificationSettings/View/GlucoseNotificationSettingsRootView.swift
  34. 0 3
      Trio/Sources/Modules/Home/HomeStateModel+Setup/GlucoseTargetSetup.swift
  35. 1 0
      Trio/Sources/Modules/Home/HomeStateModel.swift
  36. 1 1
      Trio/Sources/Modules/Home/View/Chart/ChartElements/CarbView.swift
  37. 2 1
      Trio/Sources/Modules/Home/View/Chart/MainChartView.swift
  38. 1 1
      Trio/Sources/Modules/Home/View/Header/PumpView.swift
  39. 8 1
      Trio/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift
  40. 36 39
      Trio/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift
  41. 6 1
      Trio/Sources/Modules/PumpConfig/View/PumpSetupView.swift
  42. 1 1
      Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift
  43. 2 2
      Trio/Sources/Modules/Settings/SettingItems.swift
  44. 2 7
      Trio/Sources/Modules/Stat/View/StatsView.swift
  45. 22 22
      Trio/Sources/Modules/TargetsEditor/View/TargetsEditorRootView.swift
  46. 2 1
      Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift
  47. 1 1
      Trio/Sources/Modules/Treatments/View/MealPreset/MealPresetView.swift
  48. 19 17
      Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift
  49. 14 16
      Trio/Sources/Modules/WatchConfig/View/WatchConfigGarminView.swift
  50. 5 1
      Trio/Sources/Router/Router.swift
  51. 19 18
      Trio/Sources/Services/HealthKit/HealthKitManager.swift
  52. 1 1
      Trio/Sources/Services/RemoteControl/TrioRemoteControl+Meal.swift
  53. 7 11
      Trio/Sources/Services/UserNotifications/UserNotificationsManager.swift
  54. 11 4
      oref0_source_version.txt
  55. 1 2
      trio-oref/lib/determine-basal/determine-basal.js

+ 1 - 1
Model/Classes+Properties/OpenAPS_Battery+CoreDataProperties.swift

@@ -9,7 +9,7 @@ public extension OpenAPS_Battery {
     @NSManaged var date: Date?
     @NSManaged var date: Date?
     @NSManaged var display: Bool
     @NSManaged var display: Bool
     @NSManaged var id: UUID?
     @NSManaged var id: UUID?
-    @NSManaged var percent: Int16
+    @NSManaged var percent: Double
     @NSManaged var status: String?
     @NSManaged var status: String?
     @NSManaged var voltage: NSDecimalNumber?
     @NSManaged var voltage: NSDecimalNumber?
 }
 }

+ 2 - 2
Model/Helper/CarbEntryStored+helper.swift

@@ -9,13 +9,13 @@ extension NSPredicate {
 
 
     static var carbsForChart: NSPredicate {
     static var carbsForChart: NSPredicate {
         let date = Date.oneDayAgo
         let date = Date.oneDayAgo
-        return NSPredicate(format: "isFPU == false AND date >= %@", date as NSDate)
+        return NSPredicate(format: "isFPU == false AND date >= %@ AND carbs > 0", date as NSDate)
     }
     }
 
 
     static var carbsNotYetUploadedToNightscout: NSPredicate {
     static var carbsNotYetUploadedToNightscout: NSPredicate {
         let date = Date.oneDayAgo
         let date = Date.oneDayAgo
         return NSPredicate(
         return NSPredicate(
-            format: "date >= %@ AND isUploadedToNS == %@ AND isFPU == %@",
+            format: "date >= %@ AND isUploadedToNS == %@ AND isFPU == %@ AND carbs > 0",
             date as NSDate,
             date as NSDate,
             false as NSNumber,
             false as NSNumber,
             false as NSNumber
             false as NSNumber

+ 5 - 0
Model/Helper/NSPredicates.swift

@@ -66,6 +66,11 @@ extension NSPredicate {
         return NSPredicate(format: "date >= %@", date as NSDate)
         return NSPredicate(format: "date >= %@", date as NSDate)
     }
     }
 
 
+    static var carbsHistory: NSPredicate {
+        let date = Date.oneDayAgo
+        return NSPredicate(format: "date >= %@ AND carbs > 0", date as NSDate)
+    }
+
     static var predicateForOneHourAgo: NSPredicate {
     static var predicateForOneHourAgo: NSPredicate {
         let date = Date.oneHourAgo
         let date = Date.oneHourAgo
         return NSPredicate(format: "date >= %@", date as NSDate)
         return NSPredicate(format: "date >= %@", date as NSDate)

+ 1 - 1
Model/TrioCoreDataPersistentContainer.xcdatamodeld/TrioCoreDataPersistentContainer.xcdatamodel/contents

@@ -96,7 +96,7 @@
         <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
         <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
         <attribute name="display" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
         <attribute name="display" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
         <attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
         <attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
-        <attribute name="percent" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
+        <attribute name="percent" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
         <attribute name="status" optional="YES" attributeType="String"/>
         <attribute name="status" optional="YES" attributeType="String"/>
         <attribute name="voltage" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <attribute name="voltage" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <fetchIndex name="byDate">
         <fetchIndex name="byDate">

+ 8 - 0
Trio.xcodeproj/project.pbxproj

@@ -270,6 +270,7 @@
 		6EADD581738D64431902AC0A /* (null) in Sources */ = {isa = PBXBuildFile; };
 		6EADD581738D64431902AC0A /* (null) in Sources */ = {isa = PBXBuildFile; };
 		6FFAE524D1D9C262F2407CAE /* SnoozeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAE81192B118804DCD23034 /* SnoozeProvider.swift */; };
 		6FFAE524D1D9C262F2407CAE /* SnoozeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAE81192B118804DCD23034 /* SnoozeProvider.swift */; };
 		711C0CB42CAABE788916BC9D /* ManualTempBasalDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96653287EDB276A111288305 /* ManualTempBasalDataFlow.swift */; };
 		711C0CB42CAABE788916BC9D /* ManualTempBasalDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96653287EDB276A111288305 /* ManualTempBasalDataFlow.swift */; };
+		715120D22D3C2BB4005D9FB6 /* GlucoseNotificationsOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715120D12D3C2B84005D9FB6 /* GlucoseNotificationsOption.swift */; };
 		71D44AAB2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D44AAA2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift */; };
 		71D44AAB2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D44AAA2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift */; };
 		72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44080E4709E3AE4B73054563 /* ConfigEditorProvider.swift */; };
 		72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44080E4709E3AE4B73054563 /* ConfigEditorProvider.swift */; };
 		7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */; };
 		7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */; };
@@ -324,6 +325,7 @@
 		BDA25F222D26D62800035F34 /* BolusInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA25F212D26D62200035F34 /* BolusInputView.swift */; };
 		BDA25F222D26D62800035F34 /* BolusInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA25F212D26D62200035F34 /* BolusInputView.swift */; };
 		BDA6CC882CAF219B00F942F9 /* TempTargetSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA6CC872CAF219800F942F9 /* TempTargetSetup.swift */; };
 		BDA6CC882CAF219B00F942F9 /* TempTargetSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA6CC872CAF219800F942F9 /* TempTargetSetup.swift */; };
 		BDAE40002D372BAD009C12B1 /* WatchState+Requests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDAE3FFF2D372BA8009C12B1 /* WatchState+Requests.swift */; };
 		BDAE40002D372BAD009C12B1 /* WatchState+Requests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDAE3FFF2D372BA8009C12B1 /* WatchState+Requests.swift */; };
+		BDA7593E2D37CFC400E649A4 /* CarbEntryEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA7593D2D37CFC000E649A4 /* CarbEntryEditorView.swift */; };
 		BDB3C1192C03DD1000CEEAA1 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */; };
 		BDB3C1192C03DD1000CEEAA1 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */; };
 		BDB899882C564509006F3298 /* ForecastChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB899872C564509006F3298 /* ForecastChart.swift */; };
 		BDB899882C564509006F3298 /* ForecastChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB899872C564509006F3298 /* ForecastChart.swift */; };
 		BDB8998A2C565D0C006F3298 /* CarbsGlucose+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */; };
 		BDB8998A2C565D0C006F3298 /* CarbsGlucose+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */; };
@@ -990,6 +992,7 @@
 		6B1A8D252B14D91700E76752 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		6B1A8D252B14D91700E76752 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		6B1A8D2D2B156EEF00E76752 /* LiveActivityBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityBridge.swift; sourceTree = "<group>"; };
 		6B1A8D2D2B156EEF00E76752 /* LiveActivityBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityBridge.swift; sourceTree = "<group>"; };
 		6BCF84DC2B16843A003AD46E /* LiveActitiyAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActitiyAttributes.swift; sourceTree = "<group>"; };
 		6BCF84DC2B16843A003AD46E /* LiveActitiyAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActitiyAttributes.swift; sourceTree = "<group>"; };
+		715120D12D3C2B84005D9FB6 /* GlucoseNotificationsOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationsOption.swift; sourceTree = "<group>"; };
 		71D44AAA2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertPermissionsChecker.swift; sourceTree = "<group>"; };
 		71D44AAA2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertPermissionsChecker.swift; sourceTree = "<group>"; };
 		79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorDataFlow.swift; sourceTree = "<group>"; };
 		79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorDataFlow.swift; sourceTree = "<group>"; };
 		7E22146D3DF4853786C78132 /* CarbRatioEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CarbRatioEditorDataFlow.swift; sourceTree = "<group>"; };
 		7E22146D3DF4853786C78132 /* CarbRatioEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CarbRatioEditorDataFlow.swift; sourceTree = "<group>"; };
@@ -1042,6 +1045,7 @@
 		BDA25F212D26D62200035F34 /* BolusInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusInputView.swift; sourceTree = "<group>"; };
 		BDA25F212D26D62200035F34 /* BolusInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusInputView.swift; sourceTree = "<group>"; };
 		BDA6CC872CAF219800F942F9 /* TempTargetSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetSetup.swift; sourceTree = "<group>"; };
 		BDA6CC872CAF219800F942F9 /* TempTargetSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetSetup.swift; sourceTree = "<group>"; };
 		BDAE3FFF2D372BA8009C12B1 /* WatchState+Requests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WatchState+Requests.swift"; sourceTree = "<group>"; };
 		BDAE3FFF2D372BA8009C12B1 /* WatchState+Requests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WatchState+Requests.swift"; sourceTree = "<group>"; };
+		BDA7593D2D37CFC000E649A4 /* CarbEntryEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbEntryEditorView.swift; sourceTree = "<group>"; };
 		BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtension.swift; sourceTree = "<group>"; };
 		BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtension.swift; sourceTree = "<group>"; };
 		BDB899872C564509006F3298 /* ForecastChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastChart.swift; sourceTree = "<group>"; };
 		BDB899872C564509006F3298 /* ForecastChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastChart.swift; sourceTree = "<group>"; };
 		BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CarbsGlucose+helper.swift"; sourceTree = "<group>"; };
 		BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CarbsGlucose+helper.swift"; sourceTree = "<group>"; };
@@ -1402,6 +1406,7 @@
 		0EE66DD474AFFD4FD787D5B9 /* View */ = {
 		0EE66DD474AFFD4FD787D5B9 /* View */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				BDA7593D2D37CFC000E649A4 /* CarbEntryEditorView.swift */,
 				881E04BA5E0A003DE8E0A9C6 /* DataTableRootView.swift */,
 				881E04BA5E0A003DE8E0A9C6 /* DataTableRootView.swift */,
 			);
 			);
 			path = View;
 			path = View;
@@ -2050,6 +2055,7 @@
 				BD54A9722D281A9C00F9C1EE /* TempTargetPresetWatch.swift */,
 				BD54A9722D281A9C00F9C1EE /* TempTargetPresetWatch.swift */,
 				BD54A95A2D28087700F9C1EE /* OverridePresetWatch.swift */,
 				BD54A95A2D28087700F9C1EE /* OverridePresetWatch.swift */,
 				BDA25EFC2D261BF200035F34 /* WatchState.swift */,
 				BDA25EFC2D261BF200035F34 /* WatchState.swift */,
+				715120D12D3C2B84005D9FB6 /* GlucoseNotificationsOption.swift */,
 				DD07CA132CE80B73002D45A9 /* TimeInRangeChartStyle.swift */,
 				DD07CA132CE80B73002D45A9 /* TimeInRangeChartStyle.swift */,
 				DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */,
 				DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */,
 				DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */,
 				DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */,
@@ -3919,6 +3925,7 @@
 				DD32CF9E2CC824C5003686D6 /* TrioRemoteControl+Override.swift in Sources */,
 				DD32CF9E2CC824C5003686D6 /* TrioRemoteControl+Override.swift in Sources */,
 				23888883D4EA091C88480FF2 /* TreatmentsProvider.swift in Sources */,
 				23888883D4EA091C88480FF2 /* TreatmentsProvider.swift in Sources */,
 				38E98A2D25F52DC400C0CED0 /* NSLocking+Extensions.swift in Sources */,
 				38E98A2D25F52DC400C0CED0 /* NSLocking+Extensions.swift in Sources */,
+				715120D22D3C2BB4005D9FB6 /* GlucoseNotificationsOption.swift in Sources */,
 				BDBAACFA2C2D439700370AAE /* OverrideData.swift in Sources */,
 				BDBAACFA2C2D439700370AAE /* OverrideData.swift in Sources */,
 				DD9ECB682CA99F4500AA7C45 /* TrioRemoteControl.swift in Sources */,
 				DD9ECB682CA99F4500AA7C45 /* TrioRemoteControl.swift in Sources */,
 				38569353270B5E350002C50D /* CGMRootView.swift in Sources */,
 				38569353270B5E350002C50D /* CGMRootView.swift in Sources */,
@@ -3939,6 +3946,7 @@
 				BD1661312B82ADAB00256551 /* CustomProgressView.swift in Sources */,
 				BD1661312B82ADAB00256551 /* CustomProgressView.swift in Sources */,
 				C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */,
 				C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */,
 				38E4453B274E411700EC9A94 /* Disk+VolumeInformation.swift in Sources */,
 				38E4453B274E411700EC9A94 /* Disk+VolumeInformation.swift in Sources */,
+				BDA7593E2D37CFC400E649A4 /* CarbEntryEditorView.swift in Sources */,
 				118DF76A2C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift in Sources */,
 				118DF76A2C5ECBC60067FEB7 /* ApplyOverridePresetIntent.swift in Sources */,
 				58645B992CA2D1A4008AFCE7 /* GlucoseSetup.swift in Sources */,
 				58645B992CA2D1A4008AFCE7 /* GlucoseSetup.swift in Sources */,
 				7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */,
 				7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */,

Разлика између датотеке није приказан због своје велике величине
+ 1 - 1
Trio/Resources/javascript/bundle/autosens.js


Разлика између датотеке није приказан због своје велике величине
+ 1 - 1
Trio/Resources/javascript/bundle/autotune-prep.js


Разлика између датотеке није приказан због своје велике величине
+ 1 - 1
Trio/Resources/javascript/bundle/determine-basal.js


Разлика између датотеке није приказан због своје велике величине
+ 1 - 1
Trio/Resources/javascript/bundle/iob.js


Разлика између датотеке није приказан због своје велике величине
+ 1 - 1
Trio/Resources/javascript/bundle/meal.js


Разлика између датотеке није приказан због своје велике величине
+ 1 - 1
Trio/Resources/javascript/bundle/profile.js


+ 13 - 11
Trio/Sources/APS/APSManager.swift

@@ -1173,16 +1173,18 @@ final class BaseAPSManager: APSManager, Injectable {
             let hbA1cDisplayUnit = self.settingsManager.settings.hbA1cDisplayUnit
             let hbA1cDisplayUnit = self.settingsManager.settings.hbA1cDisplayUnit
 
 
             let hbs = Durations(
             let hbs = Durations(
-                day: ((units == .mmolL && hbA1cDisplayUnit == .mmolMol) || (units == .mgdL && hbA1cDisplayUnit == .percent)) ?
-                    self.roundDecimal(Decimal(oneDayGlucose.ifcc), 1) : self.roundDecimal(Decimal(oneDayGlucose.ngsp), 1),
-                week: ((units == .mmolL && hbA1cDisplayUnit == .mmolMol) || (units == .mgdL && hbA1cDisplayUnit == .percent)) ?
-                    self.roundDecimal(Decimal(sevenDaysGlucose.ifcc), 1) : self
-                    .roundDecimal(Decimal(sevenDaysGlucose.ngsp), 1),
-                month: ((units == .mmolL && hbA1cDisplayUnit == .mmolMol) || (units == .mgdL && hbA1cDisplayUnit == .percent)) ?
-                    self.roundDecimal(Decimal(thirtyDaysGlucose.ifcc), 1) : self
-                    .roundDecimal(Decimal(thirtyDaysGlucose.ngsp), 1),
-                total: ((units == .mmolL && hbA1cDisplayUnit == .mmolMol) || (units == .mgdL && hbA1cDisplayUnit == .percent)) ?
-                    self.roundDecimal(Decimal(totalDaysGlucose.ifcc), 1) : self.roundDecimal(Decimal(totalDaysGlucose.ngsp), 1)
+                day: hbA1cDisplayUnit == .mmolMol ?
+                    self.roundDecimal(Decimal(oneDayGlucose.ifcc), 1) :
+                    self.roundDecimal(Decimal(oneDayGlucose.ngsp), 1),
+                week: hbA1cDisplayUnit == .mmolMol ?
+                    self.roundDecimal(Decimal(sevenDaysGlucose.ifcc), 1) :
+                    self.roundDecimal(Decimal(sevenDaysGlucose.ngsp), 1),
+                month: hbA1cDisplayUnit == .mmolMol ?
+                    self.roundDecimal(Decimal(thirtyDaysGlucose.ifcc), 1) :
+                    self.roundDecimal(Decimal(thirtyDaysGlucose.ngsp), 1),
+                total: hbA1cDisplayUnit == .mmolMol ?
+                    self.roundDecimal(Decimal(totalDaysGlucose.ifcc), 1) :
+                    self.roundDecimal(Decimal(totalDaysGlucose.ngsp), 1)
             )
             )
 
 
             var oneDay_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
             var oneDay_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
@@ -1392,7 +1394,7 @@ extension BaseAPSManager: PumpManagerStatusObserver {
                 }
                 }
 
 
                 batteryToStore.date = Date()
                 batteryToStore.date = Date()
-                batteryToStore.percent = Int16(percent)
+                batteryToStore.percent = Double(percent)
                 batteryToStore.voltage = nil
                 batteryToStore.voltage = nil
                 batteryToStore.status = percent > 10 ? "normal" : "low"
                 batteryToStore.status = percent > 10 ? "normal" : "low"
                 batteryToStore.display = status.pumpBatteryChargeRemaining != nil
                 batteryToStore.display = status.pumpBatteryChargeRemaining != nil

+ 1 - 1
Trio/Sources/APS/DeviceDataManager.swift

@@ -132,7 +132,7 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
                             let saveBatteryToCoreData = OpenAPS_Battery(context: self.privateContext)
                             let saveBatteryToCoreData = OpenAPS_Battery(context: self.privateContext)
                             saveBatteryToCoreData.id = UUID()
                             saveBatteryToCoreData.id = UUID()
                             saveBatteryToCoreData.date = Date()
                             saveBatteryToCoreData.date = Date()
-                            saveBatteryToCoreData.percent = Int16(batteryPercent)
+                            saveBatteryToCoreData.percent = Double(batteryPercent)
                             saveBatteryToCoreData.voltage = nil
                             saveBatteryToCoreData.voltage = nil
                             saveBatteryToCoreData.status = batteryPercent >= 10 ? BatteryState.normal.rawValue : BatteryState
                             saveBatteryToCoreData.status = batteryPercent >= 10 ? BatteryState.normal.rawValue : BatteryState
                                 .low.rawValue
                                 .low.rawValue

+ 2 - 1
Trio/Sources/APS/FetchGlucoseManager.swift

@@ -213,7 +213,8 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
                     unfiltered: Decimal(result.glucose),
                     unfiltered: Decimal(result.glucose),
                     filtered: Decimal(result.glucose),
                     filtered: Decimal(result.glucose),
                     noise: nil,
                     noise: nil,
-                    glucose: Int(result.glucose)
+                    glucose: Int(result.glucose),
+                    type: "sgv"
                 )
                 )
             }
             }
         }
         }

+ 38 - 41
Trio/Sources/APS/Storage/CarbsStorage.swift

@@ -11,12 +11,11 @@ protocol CarbsObserver {
 protocol CarbsStorage {
 protocol CarbsStorage {
     var updatePublisher: AnyPublisher<Void, Never> { get }
     var updatePublisher: AnyPublisher<Void, Never> { get }
     func storeCarbs(_ carbs: [CarbsEntry], areFetchedFromRemote: Bool) async
     func storeCarbs(_ carbs: [CarbsEntry], areFetchedFromRemote: Bool) async
-    func deleteCarbs(_ treatmentObjectID: NSManagedObjectID) async
+    func deleteCarbsEntryStored(_ treatmentObjectID: NSManagedObjectID) async
     func syncDate() -> Date
     func syncDate() -> Date
     func recent() -> [CarbsEntry]
     func recent() -> [CarbsEntry]
     func getCarbsNotYetUploadedToNightscout() async -> [NightscoutTreatment]
     func getCarbsNotYetUploadedToNightscout() async -> [NightscoutTreatment]
     func getFPUsNotYetUploadedToNightscout() async -> [NightscoutTreatment]
     func getFPUsNotYetUploadedToNightscout() async -> [NightscoutTreatment]
-    func deleteCarbs(at uniqueID: String, fpuID: String, complex: Bool)
     func getCarbsNotYetUploadedToHealth() async -> [CarbsEntry]
     func getCarbsNotYetUploadedToHealth() async -> [CarbsEntry]
     func getCarbsNotYetUploadedToTidepool() async -> [CarbsEntry]
     func getCarbsNotYetUploadedToTidepool() async -> [CarbsEntry]
 }
 }
@@ -46,8 +45,29 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
             entriesToStore = await filterRemoteEntries(entries: entriesToStore)
             entriesToStore = await filterRemoteEntries(entries: entriesToStore)
         }
         }
 
 
-        await saveCarbsToCoreData(entries: entriesToStore, areFetchedFromRemote: areFetchedFromRemote)
+        // Check for FPU-only entries (fat/protein without carbs)
+        let fpuOnlyEntries = entriesToStore.filter { entry in
+            entry.carbs == 0 && (entry.fat ?? 0 > 0 || entry.protein ?? 0 > 0)
+        }
+
+        // Create additional Carb (non-FPU) entries with fat/protein amounts and carbs == 0
+        for entry in fpuOnlyEntries {
+            let additionalEntry = CarbsEntry(
+                id: entry.id,
+                createdAt: entry.createdAt,
+                actualDate: entry.actualDate,
+                carbs: Decimal(0),
+                fat: entry.fat,
+                protein: entry.protein,
+                note: entry.note,
+                enteredBy: entry.enteredBy,
+                isFPU: false, // it should be a Carb entry
+                fpuID: entry.fpuID
+            )
+            entriesToStore.append(additionalEntry)
+        }
 
 
+        await saveCarbsToCoreData(entries: entriesToStore, areFetchedFromRemote: areFetchedFromRemote)
         await saveCarbEquivalents(entries: entriesToStore, areFetchedFromRemote: areFetchedFromRemote)
         await saveCarbEquivalents(entries: entriesToStore, areFetchedFromRemote: areFetchedFromRemote)
     }
     }
 
 
@@ -195,7 +215,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
     }
     }
 
 
     private func saveCarbsToCoreData(entries: [CarbsEntry], areFetchedFromRemote: Bool) async {
     private func saveCarbsToCoreData(entries: [CarbsEntry], areFetchedFromRemote: Bool) async {
-        guard let entry = entries.last, entry.carbs != 0 else { return }
+        guard let entry = entries.last else { return }
 
 
         await coredataContext.perform {
         await coredataContext.perform {
             let newItem = CarbEntryStored(context: self.coredataContext)
             let newItem = CarbEntryStored(context: self.coredataContext)
@@ -265,22 +285,26 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
         storage.retrieve(OpenAPS.Monitor.carbHistory, as: [CarbsEntry].self)?.reversed() ?? []
         storage.retrieve(OpenAPS.Monitor.carbHistory, as: [CarbsEntry].self)?.reversed() ?? []
     }
     }
 
 
-    func deleteCarbs(_ treatmentObjectID: NSManagedObjectID) async {
+    func deleteCarbsEntryStored(_ treatmentObjectID: NSManagedObjectID) async {
         let taskContext = CoreDataStack.shared.newTaskContext()
         let taskContext = CoreDataStack.shared.newTaskContext()
         taskContext.name = "deleteContext"
         taskContext.name = "deleteContext"
         taskContext.transactionAuthor = "deleteCarbs"
         taskContext.transactionAuthor = "deleteCarbs"
 
 
-        var carbEntry: CarbEntryStored?
+        var carbEntryFromCoreData: CarbEntryStored?
 
 
         await taskContext.perform {
         await taskContext.perform {
             do {
             do {
-                carbEntry = try taskContext.existingObject(with: treatmentObjectID) as? CarbEntryStored
-                guard let carbEntry = carbEntry else {
+                carbEntryFromCoreData = try taskContext.existingObject(with: treatmentObjectID) as? CarbEntryStored
+                guard let carbEntry = carbEntryFromCoreData else {
                     debugPrint("Carb entry for batch delete not found. \(DebuggingIdentifiers.failed)")
                     debugPrint("Carb entry for batch delete not found. \(DebuggingIdentifiers.failed)")
                     return
                     return
                 }
                 }
 
 
-                if carbEntry.isFPU, let fpuID = carbEntry.fpuID {
+                // entry has fpuID
+                // case 1: carb equivalent entry
+                // case 2: "parent" entry, but containing fat and/or protein, and possibly carbs
+                // => use fpuID ID to delete all corresponding entries via batch delete
+                if let fpuID = carbEntry.fpuID {
                     // fetch request for all carb entries with the same id
                     // fetch request for all carb entries with the same id
                     let fetchRequest: NSFetchRequest<NSFetchRequestResult> = CarbEntryStored.fetchRequest()
                     let fetchRequest: NSFetchRequest<NSFetchRequestResult> = CarbEntryStored.fetchRequest()
                     fetchRequest.predicate = NSPredicate(format: "fpuID == %@", fpuID as CVarArg)
                     fetchRequest.predicate = NSPredicate(format: "fpuID == %@", fpuID as CVarArg)
@@ -295,14 +319,17 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
 
 
                     // Notifiy subscribers of the batch delete
                     // Notifiy subscribers of the batch delete
                     self.updateSubject.send(())
                     self.updateSubject.send(())
-                } else {
+                }
+                // entry has no fpuID
+                // => it's a carb-only entry. use its ID to for deletion
+                else {
                     taskContext.delete(carbEntry)
                     taskContext.delete(carbEntry)
 
 
                     guard taskContext.hasChanges else { return }
                     guard taskContext.hasChanges else { return }
                     try taskContext.save()
                     try taskContext.save()
 
 
                     debugPrint(
                     debugPrint(
-                        "Data Table State: \(#function) \(DebuggingIdentifiers.succeeded) deleted carb entry from core data"
+                        "CarbsStorage: \(#function) \(DebuggingIdentifiers.succeeded) deleted carb entry from core data"
                     )
                     )
                 }
                 }
 
 
@@ -312,36 +339,6 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
         }
         }
     }
     }
 
 
-    func deleteCarbs(at uniqueID: String, fpuID: String, complex: Bool) {
-        processQueue.sync {
-            var allValues = storage.retrieve(OpenAPS.Monitor.carbHistory, as: [CarbsEntry].self) ?? []
-
-            if fpuID != "" {
-                if allValues.firstIndex(where: { $0.fpuID == fpuID }) == nil {
-                    debug(.default, "Didn't find any carb equivalents to delete. ID to search for: " + fpuID.description)
-                } else {
-                    allValues.removeAll(where: { $0.fpuID == fpuID })
-                    storage.save(allValues, as: OpenAPS.Monitor.carbHistory)
-                    broadcaster.notify(CarbsObserver.self, on: processQueue) {
-                        $0.carbsDidUpdate(allValues)
-                    }
-                }
-            }
-
-            if fpuID == "" || complex {
-                if allValues.firstIndex(where: { $0.id == uniqueID }) == nil {
-                    debug(.default, "Didn't find any carb entries to delete. ID to search for: " + uniqueID.description)
-                } else {
-                    allValues.removeAll(where: { $0.id == uniqueID })
-                    storage.save(allValues, as: OpenAPS.Monitor.carbHistory)
-                    broadcaster.notify(CarbsObserver.self, on: processQueue) {
-                        $0.carbsDidUpdate(allValues)
-                    }
-                }
-            }
-        }
-    }
-
     func getCarbsNotYetUploadedToNightscout() async -> [NightscoutTreatment] {
     func getCarbsNotYetUploadedToNightscout() async -> [NightscoutTreatment] {
         let results = await CoreDataStack.shared.fetchEntitiesAsync(
         let results = await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             ofType: CarbEntryStored.self,

+ 2 - 1
Trio/Sources/APS/Storage/GlucoseStorage.swift

@@ -299,7 +299,8 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
                     unfiltered: Decimal(result.glucose),
                     unfiltered: Decimal(result.glucose),
                     filtered: Decimal(result.glucose),
                     filtered: Decimal(result.glucose),
                     noise: nil,
                     noise: nil,
-                    glucose: Int(result.glucose)
+                    glucose: Int(result.glucose),
+                    type: "sgv"
                 )
                 )
             }
             }
         }
         }

+ 2 - 0
Trio/Sources/Application/TrioApp.swift

@@ -48,6 +48,8 @@ import Swinject
         _ = resolver.resolve(FetchTreatmentsManager.self)!
         _ = resolver.resolve(FetchTreatmentsManager.self)!
         _ = resolver.resolve(CalendarManager.self)!
         _ = resolver.resolve(CalendarManager.self)!
         _ = resolver.resolve(UserNotificationsManager.self)!
         _ = resolver.resolve(UserNotificationsManager.self)!
+        _ = resolver.resolve(WatchManager.self)!
+        _ = resolver.resolve(ContactImageManager.self)!
         _ = resolver.resolve(HealthKitManager.self)!
         _ = resolver.resolve(HealthKitManager.self)!
         _ = resolver.resolve(WatchManager.self)!
         _ = resolver.resolve(WatchManager.self)!
         _ = resolver.resolve(ContactImageManager.self)!
         _ = resolver.resolve(ContactImageManager.self)!

+ 1 - 1
Trio/Sources/Models/DecimalPickerSettings.swift

@@ -136,7 +136,7 @@ struct DecimalPickerSettings {
     var hours = PickerSetting(value: 6, step: 0.5, min: 2, max: 24, type: PickerSetting.PickerSettingType.hour)
     var hours = PickerSetting(value: 6, step: 0.5, min: 2, max: 24, type: PickerSetting.PickerSettingType.hour)
     var dia = PickerSetting(value: 10, step: 0.5, min: 5, max: 10, type: PickerSetting.PickerSettingType.hour)
     var dia = PickerSetting(value: 10, step: 0.5, min: 5, max: 10, type: PickerSetting.PickerSettingType.hour)
     var maxBolus = PickerSetting(value: 10, step: 0.5, min: 1, max: 30, type: PickerSetting.PickerSettingType.insulinUnit)
     var maxBolus = PickerSetting(value: 10, step: 0.5, min: 1, max: 30, type: PickerSetting.PickerSettingType.insulinUnit)
-    var maxBasal = PickerSetting(value: 10, step: 0.5, min: 1, max: 30, type: PickerSetting.PickerSettingType.insulinUnit)
+    var maxBasal = PickerSetting(value: 10, step: 0.5, min: 0.5, max: 30, type: PickerSetting.PickerSettingType.insulinUnit)
 }
 }
 
 
 struct PickerSetting {
 struct PickerSetting {

+ 22 - 0
Trio/Sources/Models/GlucoseNotificationsOption.swift

@@ -0,0 +1,22 @@
+//
+//  GlucoseNotificationOption.swift
+//  FreeAPS
+//
+//  Created by Kimberlie Skandis on 1/18/25.
+//
+import Foundation
+
+public enum GlucoseNotificationsOption: String, JSON, CaseIterable, Identifiable, Codable, Hashable {
+    public var id: String { rawValue }
+    case disabled
+    case alwaysEveryCGM
+    case onlyAlarmLimits
+
+    var displayName: String {
+        switch self {
+        case .disabled: return "Disabled"
+        case .alwaysEveryCGM: return "Always"
+        case .onlyAlarmLimits: return "Only Alarm Limits"
+        }
+    }
+}

+ 6 - 3
Trio/Sources/Models/TrioSettings.swift

@@ -34,7 +34,7 @@ struct TrioSettings: JSON, Equatable {
     var notificationsCgm: Bool = true
     var notificationsCgm: Bool = true
     var notificationsCarb: Bool = true
     var notificationsCarb: Bool = true
     var notificationsAlgorithm: Bool = true
     var notificationsAlgorithm: Bool = true
-    var glucoseNotificationsAlways: Bool = false
+    var glucoseNotificationsOption: GlucoseNotificationsOption = .onlyAlarmLimits
     var useAlarmSound: Bool = false
     var useAlarmSound: Bool = false
     var addSourceInfoToGlucoseNotifications: Bool = false
     var addSourceInfoToGlucoseNotifications: Bool = false
     var lowGlucose: Decimal = 72
     var lowGlucose: Decimal = 72
@@ -200,8 +200,11 @@ extension TrioSettings: Decodable {
             settings.notificationsAlgorithm = notificationsAlgorithm
             settings.notificationsAlgorithm = notificationsAlgorithm
         }
         }
 
 
-        if let glucoseNotificationsAlways = try? container.decode(Bool.self, forKey: .glucoseNotificationsAlways) {
-            settings.glucoseNotificationsAlways = glucoseNotificationsAlways
+        if let glucoseNotificationsOption = try? container.decode(
+            GlucoseNotificationsOption.self,
+            forKey: .glucoseNotificationsOption
+        ) {
+            settings.glucoseNotificationsOption = glucoseNotificationsOption
         }
         }
 
 
         if let useAlarmSound = try? container.decode(Bool.self, forKey: .useAlarmSound) {
         if let useAlarmSound = try? container.decode(Bool.self, forKey: .useAlarmSound) {

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

@@ -189,13 +189,12 @@ extension Adjustments.StateModel {
     func updateLatestOverrideConfiguration() {
     func updateLatestOverrideConfiguration() {
         Task { [weak self] in
         Task { [weak self] in
             guard let self = self else { return }
             guard let self = self else { return }
-            
+
             let id = await self.overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 1)
             let id = await self.overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 1)
-            
+
             // execute sequentially instead of concurrently
             // execute sequentially instead of concurrently
             await self.updateLatestOverrideConfigurationOfState(from: id)
             await self.updateLatestOverrideConfigurationOfState(from: id)
             await self.setCurrentOverride(from: id)
             await self.setCurrentOverride(from: id)
-            
         }
         }
     }
     }
 
 

+ 1 - 1
Trio/Sources/Modules/Adjustments/View/Overrides/AddOverrideForm.swift

@@ -74,7 +74,7 @@ struct AddOverrideForm: View {
             Section(footer: state.percentageDescription(state.overridePercentage)) {
             Section(footer: state.percentageDescription(state.overridePercentage)) {
                 // Percentage Picker
                 // Percentage Picker
                 HStack {
                 HStack {
-                    Text("Change Basal Rate by")
+                    Text("Basal Rate Adjustment")
                     Spacer()
                     Spacer()
                     Text("\(state.overridePercentage.formatted(.number)) %")
                     Text("\(state.overridePercentage.formatted(.number)) %")
                         .foregroundColor(!displayPickerPercentage ? .primary : .accentColor)
                         .foregroundColor(!displayPickerPercentage ? .primary : .accentColor)

+ 1 - 1
Trio/Sources/Modules/Adjustments/View/Overrides/AdjustmentsRootView+Overrides.swift

@@ -94,7 +94,7 @@ extension Adjustments.RootView {
                 selectedOverride = preset
                 selectedOverride = preset
                 isConfirmDeletePresented = true
                 isConfirmDeletePresented = true
             } label: {
             } label: {
-                Label("Delete", systemImage: "trash")
+                Label("Delete", systemImage: "trash.fill")
                     .tint(.red)
                     .tint(.red)
             }
             }
             Button(action: {
             Button(action: {

+ 1 - 1
Trio/Sources/Modules/Adjustments/View/Overrides/EditOverrideForm.swift

@@ -140,7 +140,7 @@ struct EditOverrideForm: View {
             // Percentage Picker
             // Percentage Picker
             Section(footer: state.percentageDescription(percentage)) {
             Section(footer: state.percentageDescription(percentage)) {
                 HStack {
                 HStack {
-                    Text("Change Basal Rate by")
+                    Text("Basal Rate Adjustment")
                     Spacer()
                     Spacer()
                     Text("\(percentage.formatted(.number)) %")
                     Text("\(percentage.formatted(.number)) %")
                         .foregroundColor(!displayPickerPercentage ? .primary : .accentColor)
                         .foregroundColor(!displayPickerPercentage ? .primary : .accentColor)

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

@@ -82,7 +82,7 @@ extension Adjustments.RootView {
                     isConfirmDeletePresented = true
                     isConfirmDeletePresented = true
                 }
                 }
             } label: {
             } label: {
-                Label("Delete", systemImage: "trash")
+                Label("Delete", systemImage: "trash.fill")
                     .tint(.red)
                     .tint(.red)
             }
             }
             Button(action: {
             Button(action: {

+ 11 - 9
Trio/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift

@@ -84,23 +84,25 @@ extension BasalProfileEditor {
 
 
                 Group {
                 Group {
                     HStack {
                     HStack {
-                        Button {
+                        Button(action: {
                             let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
                             let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
                             impactHeavy.impactOccurred()
                             impactHeavy.impactOccurred()
                             state.save()
                             state.save()
-                        } label: {
+                        }, label: {
                             HStack {
                             HStack {
                                 if state.syncInProgress {
                                 if state.syncInProgress {
                                     ProgressView().padding(.trailing, 10)
                                     ProgressView().padding(.trailing, 10)
                                 }
                                 }
                                 Text(state.syncInProgress ? "Saving..." : "Save")
                                 Text(state.syncInProgress ? "Saving..." : "Save")
-                            }.padding(10)
-                        }
-                        .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
-                        .disabled(shouldDisableButton)
-                        .background(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
-                        .tint(.white)
-                        .clipShape(RoundedRectangle(cornerRadius: 8))
+                            }
+                            .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
+                            .padding(10)
+                        })
+                            .frame(width: UIScreen.main.bounds.width * 0.9, height: 40, alignment: .center)
+                            .disabled(shouldDisableButton)
+                            .background(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
+                            .tint(.white)
+                            .clipShape(RoundedRectangle(cornerRadius: 8))
                     }
                     }
                 }.padding(5)
                 }.padding(5)
             }
             }

+ 1 - 1
Trio/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift

@@ -138,7 +138,7 @@ extension BolusCalculatorConfig {
                     verboseHint:
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
                         Text("Default: OFF").bold()
-                        Text("Default Percent: 200%").bold()
+                        Text("Default Percent: 100%").bold()
                         Text("Do not enable this feature until you have optimized your CR (carb ratio) setting.").bold()
                         Text("Do not enable this feature until you have optimized your CR (carb ratio) setting.").bold()
                         Text(
                         Text(
                             "Enabling this setting adds a \"Super Bolus\" option to the bolus calculator. Once this feature is enabled, a percentage setting will appear for you to select."
                             "Enabling this setting adds a \"Super Bolus\" option to the bolus calculator. Once this feature is enabled, a percentage setting will appear for you to select."

+ 3 - 1
Trio/Sources/Modules/CarbRatioEditor/View/CarbRatioEditorRootView.swift

@@ -53,7 +53,9 @@ extension CarbRatioEditor {
                                         ProgressView().padding(.trailing, 10)
                                         ProgressView().padding(.trailing, 10)
                                     }
                                     }
                                     Text(state.shouldDisplaySaving ? "Saving..." : "Save")
                                     Text(state.shouldDisplaySaving ? "Saving..." : "Save")
-                                }.padding(10)
+                                }
+                                .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
+                                .padding(10)
                             }
                             }
                         }
                         }
                         .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
                         .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)

+ 337 - 33
Trio/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -18,8 +18,6 @@ extension DataTable {
 
 
         var mode: Mode = .treatments
         var mode: Mode = .treatments
         var treatments: [Treatment] = []
         var treatments: [Treatment] = []
-        var glucose: [Glucose] = []
-        var meals: [Treatment] = []
         var manualGlucose: Decimal = 0
         var manualGlucose: Decimal = 0
         var waitForSuggestion: Bool = false
         var waitForSuggestion: Bool = false
 
 
@@ -28,18 +26,24 @@ extension DataTable {
 
 
         var units: GlucoseUnits = .mgdL
         var units: GlucoseUnits = .mgdL
 
 
+        var carbEntryToEdit: CarbEntryStored?
+        var showCarbEntryEditor = false
+
         override func subscribe() {
         override func subscribe() {
             units = settingsManager.settings.units
             units = settingsManager.settings.units
             broadcaster.register(DeterminationObserver.self, observer: self)
             broadcaster.register(DeterminationObserver.self, observer: self)
             broadcaster.register(SettingsObserver.self, observer: self)
             broadcaster.register(SettingsObserver.self, observer: self)
         }
         }
 
 
+        /// Checks if the glucose data is fresh based on the given date
+        /// - Parameter glucoseDate: The date to check
+        /// - Returns: Boolean indicating if the data is fresh
         func isGlucoseDataFresh(_ glucoseDate: Date?) -> Bool {
         func isGlucoseDataFresh(_ glucoseDate: Date?) -> Bool {
             glucoseStorage.isGlucoseDataFresh(glucoseDate)
             glucoseStorage.isGlucoseDataFresh(glucoseDate)
         }
         }
 
 
-        // Glucose deletion from history and from remote services
-        /// -**Parameter**: NSManagedObjectID to be able to transfer the object safely from one thread to another thread
+        /// Initiates the glucose deletion process asynchronously
+        /// - Parameter treatmentObjectID: NSManagedObjectID to be able to transfer the object safely from one thread to another thread
         func invokeGlucoseDeletionTask(_ treatmentObjectID: NSManagedObjectID) {
         func invokeGlucoseDeletionTask(_ treatmentObjectID: NSManagedObjectID) {
             Task {
             Task {
                 await deleteGlucose(treatmentObjectID)
                 await deleteGlucose(treatmentObjectID)
@@ -99,9 +103,9 @@ extension DataTable {
 
 
         // Carb and FPU deletion from history
         // Carb and FPU deletion from history
         /// - **Parameter**: NSManagedObjectID to be able to transfer the object safely from one thread to another thread
         /// - **Parameter**: NSManagedObjectID to be able to transfer the object safely from one thread to another thread
-        func invokeCarbDeletionTask(_ treatmentObjectID: NSManagedObjectID) {
+        func invokeCarbDeletionTask(_ treatmentObjectID: NSManagedObjectID, isFpuOrComplexMeal: Bool = false) {
             Task {
             Task {
-                await deleteCarbs(treatmentObjectID)
+                await deleteCarbs(treatmentObjectID, isFpuOrComplexMeal: isFpuOrComplexMeal)
 
 
                 await MainActor.run {
                 await MainActor.run {
                     carbEntryDeleted = true
                     carbEntryDeleted = true
@@ -110,34 +114,58 @@ extension DataTable {
             }
             }
         }
         }
 
 
-        func deleteCarbs(_ treatmentObjectID: NSManagedObjectID) async {
-            // Delete from Apple Health/Tidepool
-            await deleteCarbsFromServices(treatmentObjectID)
+        func deleteCarbs(_ treatmentObjectID: NSManagedObjectID, isFpuOrComplexMeal: Bool = false) async {
+            // Delete from Nightscout/Apple Health/Tidepool
+            await deleteFromServices(treatmentObjectID, isFPUDeletion: isFpuOrComplexMeal)
 
 
             // Delete carbs from Core Data
             // Delete carbs from Core Data
-            await carbsStorage.deleteCarbs(treatmentObjectID)
+            await carbsStorage.deleteCarbsEntryStored(treatmentObjectID)
 
 
             // Perform a determine basal sync to update cob
             // Perform a determine basal sync to update cob
             await apsManager.determineBasalSync()
             await apsManager.determineBasalSync()
         }
         }
 
 
-        func deleteCarbsFromServices(_ treatmentObjectID: NSManagedObjectID) async {
+        /// Deletes carb and FPU entries from all connected services (Nightscout, HealthKit, Tidepool)
+        /// - Parameters:
+        ///   - treatmentObjectID: The Core Data object ID of the entry to delete
+        ///   - isFPUDeletion: Flag indicating if this is a FPU deletion that requires special handling
+        ///     - If true: Will first fetch the corresponding carb entry and then delete both FPU and carb entries
+        ///     - If false: Will delete the entry directly as a standard carb deletion
+        /// - Note: This function handles three scenarios:
+        ///   1. Standard carb deletion (isFPUDeletion = false)
+        ///   2. FPU-only deletion (isFPUDeletion = true)
+        ///   3. Combined carb+FPU deletion (isFPUDeletion = true)
+        func deleteFromServices(_ treatmentObjectID: NSManagedObjectID, isFPUDeletion: Bool = false) async {
             let taskContext = CoreDataStack.shared.newTaskContext()
             let taskContext = CoreDataStack.shared.newTaskContext()
             taskContext.name = "deleteContext"
             taskContext.name = "deleteContext"
             taskContext.transactionAuthor = "deleteCarbsFromServices"
             taskContext.transactionAuthor = "deleteCarbsFromServices"
 
 
             var carbEntry: CarbEntryStored?
             var carbEntry: CarbEntryStored?
+            var objectIDToDelete = treatmentObjectID
+
+            // For FPU deletions, first get the corresponding carb entry
+            if isFPUDeletion {
+                guard let correspondingEntry: (
+                    entryValues: (carbs: Decimal, fat: Decimal, protein: Decimal, note: String, date: Date)?,
+                    entryID: NSManagedObjectID?
+                ) = await handleFPUEntry(treatmentObjectID),
+                    let nsManagedObjectID = correspondingEntry.entryID
+                else { return }
+
+                objectIDToDelete = nsManagedObjectID
+            }
 
 
-            // Delete carbs or FPUs from Nightscout
+            // Delete entries from all services
             await taskContext.perform {
             await taskContext.perform {
                 do {
                 do {
-                    carbEntry = try taskContext.existingObject(with: treatmentObjectID) as? CarbEntryStored
+                    carbEntry = try taskContext.existingObject(with: objectIDToDelete) as? CarbEntryStored
                     guard let carbEntry = carbEntry else {
                     guard let carbEntry = carbEntry else {
                         debugPrint("Carb entry for deletion not found. \(DebuggingIdentifiers.failed)")
                         debugPrint("Carb entry for deletion not found. \(DebuggingIdentifiers.failed)")
                         return
                         return
                     }
                     }
 
 
-                    if carbEntry.isFPU, let fpuID = carbEntry.fpuID {
+                    // Delete FPU related entries if they exist
+                    if let fpuID = carbEntry.fpuID {
                         // Delete Fat and Protein entries from Nightscout
                         // Delete Fat and Protein entries from Nightscout
                         self.provider.deleteCarbsFromNightscout(withID: fpuID.uuidString)
                         self.provider.deleteCarbsFromNightscout(withID: fpuID.uuidString)
 
 
@@ -152,29 +180,26 @@ extension DataTable {
                                 self.provider.deleteMealDataFromHealth(byID: fpuID.uuidString, sampleType: validSampleType)
                                 self.provider.deleteMealDataFromHealth(byID: fpuID.uuidString, sampleType: validSampleType)
                             }
                             }
                         }
                         }
-                    } else {
-                        // Delete carbs from Nightscout
-                        if let id = carbEntry.id, let entryDate = carbEntry.date {
-                            self.provider.deleteCarbsFromNightscout(withID: id.uuidString)
-
-                            // Delete carbs from Apple Health
-                            if let sampleType = AppleHealthConfig.healthCarbObject {
-                                self.provider.deleteMealDataFromHealth(byID: id.uuidString, sampleType: sampleType)
-                            }
+                    }
+
+                    // Delete carb entries if they exist
+                    if let id = carbEntry.id, let entryDate = carbEntry.date {
+                        self.provider.deleteCarbsFromNightscout(withID: id.uuidString)
 
 
-                            self.provider.deleteCarbsFromTidepool(
-                                withSyncId: id,
-                                carbs: Decimal(carbEntry.carbs),
-                                at: entryDate,
-                                enteredBy: CarbsEntry.local
-                            )
+                        // Delete carbs from Apple Health
+                        if let sampleType = AppleHealthConfig.healthCarbObject {
+                            self.provider.deleteMealDataFromHealth(byID: id.uuidString, sampleType: sampleType)
                         }
                         }
-                    }
 
 
+                        self.provider.deleteCarbsFromTidepool(
+                            withSyncId: id,
+                            carbs: Decimal(carbEntry.carbs),
+                            at: entryDate,
+                            enteredBy: CarbsEntry.local
+                        )
+                    }
                 } catch {
                 } catch {
-                    debugPrint(
-                        "\(DebuggingIdentifiers.failed) Error deleting carb entry from remote service(s) (Nightscout, Apple Health, Tidepool) with error: \(error.localizedDescription)"
-                    )
+                    debugPrint("\(DebuggingIdentifiers.failed) Error deleting entries: \(error.localizedDescription)")
                 }
                 }
             }
             }
         }
         }
@@ -246,6 +271,285 @@ extension DataTable {
                 }
                 }
             }
             }
         }
         }
+
+        // MARK: - Entry Management
+
+        /// Updates a carb/FPU entry with new values and handles the necessary cleanup and recreation of FPU entries
+        /// - Parameters:
+        ///   - treatmentObjectID: The ID of the entry to update
+        ///   - newCarbs: The new carbs value
+        ///   - newFat: The new fat value
+        ///   - newProtein: The new protein value
+        ///   - newNote: The new note text
+        ///   - newDate: The new date for the entry
+        func updateEntry(
+            _ treatmentObjectID: NSManagedObjectID,
+            newCarbs: Decimal,
+            newFat: Decimal,
+            newProtein: Decimal,
+            newNote: String,
+            newDate: Date
+        ) {
+            Task {
+                // Get original date from entry to re-create the entry later with the updated values and the same date
+                guard let originalEntry = await getOriginalEntryValues(treatmentObjectID) else { return }
+
+                // Deletion logic for carb and FPU entries
+                await deleteOldEntries(
+                    treatmentObjectID,
+                    originalEntry: originalEntry,
+                    newCarbs: newCarbs,
+                    newFat: newFat,
+                    newProtein: newProtein,
+                    newNote: newNote
+                )
+
+                await createNewEntries(
+                    originalDate: newDate,
+                    newCarbs: newCarbs,
+                    newFat: newFat,
+                    newProtein: newProtein,
+                    newNote: newNote
+                )
+
+                await syncWithServices()
+            }
+        }
+
+        private func createNewEntries(
+            originalDate: Date,
+            newCarbs: Decimal,
+            newFat: Decimal,
+            newProtein: Decimal,
+            newNote: String
+        ) async {
+            let newEntry = CarbsEntry(
+                id: UUID().uuidString,
+                createdAt: Date(),
+                actualDate: originalDate,
+                carbs: newCarbs,
+                fat: newFat,
+                protein: newProtein,
+                note: newNote,
+                enteredBy: CarbsEntry.local,
+                isFPU: false,
+                fpuID: newFat > 0 || newProtein > 0 ? UUID().uuidString : nil
+            )
+
+            // Handles internally whether to create fake carbs or not based on whether fat > 0 or protein > 0
+            await carbsStorage.storeCarbs([newEntry], areFetchedFromRemote: false)
+        }
+
+        /// Deletes the old carb/ FPU entries and creates new ones with updated values
+        /// - Parameters:
+        ///   - treatmentObjectID: The ID of the entry to delete
+        ///   - originalDate: The original date to preserve
+        ///   - newCarbs: The new carbs value
+        ///   - newFat: The new fat value
+        ///   - newProtein: The new protein value
+        ///   - newNote: The new note text
+        private func deleteOldEntries(
+            _ treatmentObjectID: NSManagedObjectID,
+            originalEntry: (
+                entryValues: (date: Date, carbs: Double, fat: Double, protein: Double)?,
+                entryId: NSManagedObjectID
+            ),
+            newCarbs _: Decimal,
+            newFat _: Decimal,
+            newProtein _: Decimal,
+            newNote _: String
+        ) async {
+            if ((originalEntry.entryValues?.carbs ?? 0) == 0 && (originalEntry.entryValues?.fat ?? 0) > 0) ||
+                ((originalEntry.entryValues?.carbs ?? 0) == 0 && (originalEntry.entryValues?.protein ?? 0) > 0)
+            {
+                // Delete the zero-carb-entry and all its carb equivalents connected by the same fpuID from remote services and Core Data
+                // Use fpuID
+                await deleteCarbs(treatmentObjectID, isFpuOrComplexMeal: true)
+            } else if ((originalEntry.entryValues?.carbs ?? 0) > 0 && (originalEntry.entryValues?.fat ?? 0) > 0) ||
+                ((originalEntry.entryValues?.carbs ?? 0) > 0 && (originalEntry.entryValues?.protein ?? 0) > 0)
+            {
+                // Delete carb entry and carb equivalents that are all connected by the same fpuID from remote services and Core Data
+                // Use fpuID
+                await deleteCarbs(treatmentObjectID, isFpuOrComplexMeal: true)
+
+            } else {
+                // Delete just the carb entry since there are no carb equivalents
+                // Use NSManagedObjectID
+                await deleteCarbs(treatmentObjectID)
+            }
+        }
+
+        /// Retrieves the original entry values
+        /// - Parameter objectID: The ID of the entry
+        /// - Returns: A tuple of the old entry values and its original date and the objectID or nil
+        private func getOriginalEntryValues(_ objectID: NSManagedObjectID) async
+            -> (entryValues: (date: Date, carbs: Double, fat: Double, protein: Double)?, entryId: NSManagedObjectID)?
+        {
+            let context = CoreDataStack.shared.newTaskContext()
+            context.name = "updateContext"
+            context.transactionAuthor = "updateEntry"
+
+            return await context.perform {
+                do {
+                    guard let entry = try context.existingObject(with: objectID) as? CarbEntryStored, let entryDate = entry.date
+                    else { return nil }
+
+                    return (
+                        entryValues: (date: entryDate, carbs: entry.carbs, fat: entry.fat, protein: entry.protein),
+                        entryId: entry.objectID
+                    )
+                } catch let error as NSError {
+                    debugPrint("\(DebuggingIdentifiers.failed) Failed to get original date with error: \(error.userInfo)")
+                    return nil
+                }
+            }
+        }
+
+        /// Synchronizes the FPU/ Carb entry with all remote services in parallel
+        private func syncWithServices() async {
+            async let nightscoutUpload: () = provider.nightscoutManager.uploadCarbs()
+            async let healthKitUpload: () = provider.healthkitManager.uploadCarbs()
+            async let tidepoolUpload: () = provider.tidepoolManager.uploadCarbs()
+
+            _ = await [nightscoutUpload, healthKitUpload, tidepoolUpload]
+        }
+
+        // MARK: - Entry Loading
+
+        /// Loads the values of a carb or FPU entry from Core Data
+        /// - Parameter objectID: The ID of the entry to load
+        /// - Returns: A tuple containing the entry's values, or nil if not found
+        func loadEntryValues(from objectID: NSManagedObjectID) async
+            -> (carbs: Decimal, fat: Decimal, protein: Decimal, note: String, date: Date)?
+        {
+            let context = CoreDataStack.shared.persistentContainer.viewContext
+
+            return await context.perform {
+                do {
+                    guard let entry = try context.existingObject(with: objectID) as? CarbEntryStored,
+                          let entryDate = entry.date
+                    else { return nil }
+
+                    return (
+                        carbs: Decimal(entry.carbs),
+                        fat: Decimal(entry.fat),
+                        protein: Decimal(entry.protein),
+                        note: entry.note ?? "",
+                        date: entryDate
+                    )
+                } catch {
+                    debugPrint("\(DebuggingIdentifiers.failed) Failed to load entry: \(error.localizedDescription)")
+                    return nil
+                }
+            }
+        }
+
+        // MARK: - FPU Entry Handling
+
+        /// Handles the loading of FPU entries based on their type
+        /// If the user taps on an FPU entry in the DataTable list, there are two cases:
+        /// - the User has entered this FPU entry WITH carbs
+        /// - the User has entered this FPU entry WITHOUT carbs
+        /// In the first case, we simply need to load the corresponding carb entry. For this case THIS is the entry we want to edit.
+        /// In the second case, we need to load the zero-carb entry that actually holds the FPU values (and the carbs). For this case THIS is the entry we want to edit.
+        /// - Parameter objectID: The ID of the FPU entry
+        /// - Returns: A tuple containing the entry values and ID, or nil if not found
+        func handleFPUEntry(_ objectID: NSManagedObjectID) async
+            -> (
+                entryValues: (carbs: Decimal, fat: Decimal, protein: Decimal, note: String, date: Date)?,
+                entryID: NSManagedObjectID?
+            )?
+        {
+            // Case 1: FPU entry WITH carbs
+            if let correspondingCarbEntryID = await getCorrespondingCarbEntry(objectID) {
+                if let values = await loadEntryValues(from: correspondingCarbEntryID) {
+                    return (values, correspondingCarbEntryID)
+                }
+            }
+            // Case 2: FPU entry WITHOUT carbs
+            else if let originalEntryID = await getZeroCarbNonFPUEntry(objectID) {
+                if let values = await loadEntryValues(from: originalEntryID) {
+                    return (values, originalEntryID)
+                }
+            }
+            return nil
+        }
+
+        /// Retrieves the original zero-carb non-FPU entry for a given FPU entry.
+        /// This is used when the user has entered a FPU entry WITHOUT carbs.
+        /// - Parameter treatmentObjectID: The ID of the FPU entry
+        /// - Returns: The ID of the original entry, or nil if not found
+        func getZeroCarbNonFPUEntry(_ treatmentObjectID: NSManagedObjectID) async -> NSManagedObjectID? {
+            let context = CoreDataStack.shared.newTaskContext()
+            context.name = "fpuContext"
+
+            return await context.perform {
+                do {
+                    // Get the fpuID from the selected entry
+                    guard let selectedEntry = try context.existingObject(with: treatmentObjectID) as? CarbEntryStored,
+                          let fpuID = selectedEntry.fpuID
+                    else { return nil }
+
+                    // Fetch the original zero-carb entry (non-FPU) with the same fpuID
+                    let last24Hours = Date().addingTimeInterval(-60 * 60 * 24)
+                    let request = CarbEntryStored.fetchRequest()
+                    request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
+                        NSPredicate(format: "date >= %@", last24Hours as NSDate),
+                        NSPredicate(format: "fpuID == %@", fpuID as CVarArg),
+                        NSPredicate(format: "isFPU == NO"),
+                        NSPredicate(format: "carbs == 0")
+                    ])
+                    request.fetchLimit = 1
+
+                    let originalEntry = try context.fetch(request).first
+                    debugPrint("FPU fetch result: \(originalEntry != nil ? "Entry found" : "No entry found")")
+                    return originalEntry?.objectID
+
+                } catch let error as NSError {
+                    debugPrint("\(DebuggingIdentifiers.failed) Failed to fetch original FPU entry: \(error.userInfo)")
+                    return nil
+                }
+            }
+        }
+
+        /// Retrieves the corresponding carb entry for a given FPU entry.
+        /// This is used when the user has entered a carb entry WITH FPUs all at once.
+        /// - Parameter treatmentObjectID: The ID of the FPU entry
+        /// - Returns: The ID of the corresponding carb entry, or nil if not found
+        func getCorrespondingCarbEntry(_ treatmentObjectID: NSManagedObjectID) async -> NSManagedObjectID? {
+            let context = CoreDataStack.shared.newTaskContext()
+            context.name = "carbContext"
+
+            return await context.perform {
+                do {
+                    // Get the fpuID from the selected entry
+                    guard let selectedEntry = try context.existingObject(with: treatmentObjectID) as? CarbEntryStored,
+                          let fpuID = selectedEntry.fpuID
+                    else { return nil }
+
+                    // Fetch the corresponding carb entry with the same fpuID
+                    let last24Hours = Date().addingTimeInterval(-24.hours.timeInterval)
+                    let request = CarbEntryStored.fetchRequest()
+                    request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
+                        NSPredicate(format: "date >= %@", last24Hours as NSDate),
+                        NSPredicate(format: "fpuID == %@", fpuID as CVarArg),
+                        NSPredicate(format: "isFPU == NO"),
+                        NSPredicate(format: "(carbs > 0) OR (fat > 0) OR (protein > 0)")
+                    ])
+                    request.fetchLimit = 1
+
+                    let correspondingCarbEntry = try context.fetch(request).first
+                    debugPrint(
+                        "Corresponding carb entry fetch result: \(correspondingCarbEntry != nil ? "Entry found" : "No entry found")"
+                    )
+                    return correspondingCarbEntry?.objectID
+
+                } catch let error as NSError {
+                    debugPrint("\(DebuggingIdentifiers.failed) Failed to fetch corresponding carb entry: \(error.userInfo)")
+                    return nil
+                }
+            }
+        }
     }
     }
 }
 }
 
 

+ 229 - 0
Trio/Sources/Modules/DataTable/View/CarbEntryEditorView.swift

@@ -0,0 +1,229 @@
+//
+//  CarbEntryEditorView.swift
+//  FreeAPS
+//
+//  Created by Marvin Polscheit on 15.01.25.
+//
+import CoreData
+import SwiftUI
+
+struct CarbEntryEditorView: View {
+    @Environment(\.dismiss) private var dismiss
+    @Environment(\.colorScheme) var colorScheme
+    @Environment(AppState.self) var appState
+
+    var state: DataTable.StateModel
+    let carbEntry: CarbEntryStored
+
+    /*
+     This is the objectID of the entry that the user is editing. It is NOT always the `carbEntry: CarbEntryStored` that we pass to the `CarbEntryEditorView`.
+     We need this because FPUs and carbs are treated completely different and that complicates the update process.
+     */
+    @State private var entryToEdit: NSManagedObjectID?
+
+    @State private var editedCarbs: Decimal
+    @State private var editedFat: Decimal
+    @State private var editedProtein: Decimal
+    @State private var editedNote: String
+    @State private var isFPU: Bool
+    @State private var editedDate: Date
+
+    init(state: DataTable.StateModel, carbEntry: CarbEntryStored) {
+        self.state = state
+        self.carbEntry = carbEntry
+        _editedCarbs = State(initialValue: 0) // gets updated in the task block
+        _editedFat = State(initialValue: 0) // gets updated in the task block
+        _editedProtein = State(initialValue: 0) // gets updated in the task block
+        _editedNote = State(initialValue: carbEntry.note ?? "")
+        _isFPU = State(initialValue: carbEntry.isFPU)
+        _entryToEdit = State(initialValue: nil)
+        _editedDate = State(initialValue: Date())
+    }
+
+    private var carbLimitExceeded: Bool {
+        editedCarbs > state.settingsManager.settings.maxCarbs
+    }
+
+    private var fatLimitExceeded: Bool {
+        editedFat > state.settingsManager.settings.maxFat
+    }
+
+    private var proteinLimitExceeded: Bool {
+        editedProtein > state.settingsManager.settings.maxProtein
+    }
+
+    private var limitExceeded: Bool {
+        carbLimitExceeded || fatLimitExceeded || proteinLimitExceeded
+    }
+
+    private var isButtonDisabled: Bool {
+        editedCarbs == 0 && editedFat == 0 && editedProtein == 0
+    }
+
+    private var buttonLabel: some View {
+        if carbLimitExceeded {
+            return Text("Max Carbs of \(state.settingsManager.settings.maxCarbs.description) g Exceeded")
+        } else if fatLimitExceeded {
+            return Text("Max Fat of \(state.settingsManager.settings.maxFat.description) g Exceeded")
+        } else if proteinLimitExceeded {
+            return Text("Max Protein of \(state.settingsManager.settings.maxProtein.description) g Exceeded")
+        }
+
+        return Text("Save and Update")
+    }
+
+    private var buttonBackgroundColor: Color {
+        var treatmentButtonBackground = Color(.systemBlue)
+        if limitExceeded {
+            treatmentButtonBackground = Color(.systemRed)
+        } else if isButtonDisabled {
+            treatmentButtonBackground = Color(.systemGray)
+        }
+
+        return treatmentButtonBackground
+    }
+
+    var stickyButton: some View {
+        ZStack {
+            Rectangle()
+                .frame(width: UIScreen.main.bounds.width, height: 65)
+                .foregroundStyle(colorScheme == .dark ? Color.bgDarkerDarkBlue : Color.white)
+                .background(.thinMaterial)
+                .opacity(0.8)
+                .clipShape(Rectangle())
+
+            Button(
+                action: {
+                    guard let entryToEdit = entryToEdit else { return }
+
+                    state.updateEntry(
+                        entryToEdit,
+                        newCarbs: editedCarbs,
+                        newFat: editedFat,
+                        newProtein: editedProtein,
+                        newNote: editedNote,
+                        newDate: editedDate
+                    )
+                    dismiss()
+                }, label: {
+                    buttonLabel
+                        .font(.headline)
+                        .frame(maxWidth: .infinity, maxHeight: .infinity)
+                        .padding(10)
+                }
+            )
+            .frame(width: UIScreen.main.bounds.width * 0.9, height: 40, alignment: .center)
+            .disabled(isButtonDisabled)
+            .background(buttonBackgroundColor)
+            .tint(.white)
+            .clipShape(RoundedRectangle(cornerRadius: 8))
+        }
+    }
+
+    var body: some View {
+        NavigationView {
+            Form {
+                Section {
+                    HStack {
+                        Text("Carbs")
+                        TextFieldWithToolBar(
+                            text: $editedCarbs,
+                            placeholder: "0",
+                            keyboardType: .numberPad,
+                            numberFormatter: Formatter.decimalFormatterWithOneFractionDigit
+                        )
+                        Text("g").foregroundStyle(.secondary)
+                    }
+
+                    if state.settingsManager.settings.useFPUconversion {
+                        HStack {
+                            Text("Protein")
+                            TextFieldWithToolBar(
+                                text: $editedProtein,
+                                placeholder: "0",
+                                keyboardType: .numberPad,
+                                numberFormatter: Formatter.decimalFormatterWithOneFractionDigit
+                            )
+                            Text("g").foregroundStyle(.secondary)
+                        }
+
+                        HStack {
+                            Text("Fat")
+                            TextFieldWithToolBar(
+                                text: $editedFat,
+                                placeholder: "0",
+                                keyboardType: .numberPad,
+                                numberFormatter: Formatter.decimalFormatterWithOneFractionDigit
+                            )
+                            Text("g").foregroundStyle(.secondary)
+                        }
+                    }
+
+                    HStack {
+                        Image(systemName: "square.and.pencil")
+                        TextFieldWithToolBarString(text: $editedNote, placeholder: "Note...", maxLength: 25)
+                    }
+                }.listRowBackground(Color.chart)
+
+                Section {
+                    DatePicker(
+                        "Time",
+                        selection: $editedDate,
+                        displayedComponents: [.date, .hourAndMinute]
+                    )
+                }
+            }
+            .safeAreaInset(
+                edge: .bottom,
+                spacing: 30
+            ) {
+                stickyButton
+            }
+            .scrollContentBackground(.hidden)
+            .background(appState.trioBackgroundColor(for: colorScheme))
+            .navigationTitle("Edit Meal")
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .topBarLeading) {
+                    Button("Cancel") {
+                        dismiss()
+                    }
+                }
+            }
+        }
+        .task {
+            /*
+             User taps on a FPU entry in the DataTable list. There are two cases:
+             - the User has entered this FPU entry WITH carbs
+             - the User has entered this FPU entry WITHOUT carbs
+             In the first case, we simply need to load the corresponding carb entry. For this case THIS is the entry we want to edit.
+             In the second case, we need to load the zero-carb entry that actualy holds the FPU values (and the carbs). For this case THIS is the entry we want to edit.
+             */
+            if carbEntry.isFPU {
+                if let result = await state.handleFPUEntry(carbEntry.objectID) {
+                    editedCarbs = result.entryValues?.carbs ?? 0
+                    editedFat = result.entryValues?.fat ?? 0
+                    editedProtein = result.entryValues?.protein ?? 0
+                    editedNote = result.entryValues?.note ?? ""
+                    entryToEdit = result.entryID
+                    editedDate = result.entryValues?.date ?? Date()
+                }
+                /*
+                 User taps on a carb entry in the DataTable list. There are again two cases which don't need explicit handling:
+                 - the User has only entered carbs
+                 - the User has entered carbs with FPU
+                 In both cases, we need to simply load the carb entry that holds all the necessary values for us. This is the entry we want to edit.
+                 */
+            } else {
+                if let values = await state.loadEntryValues(from: carbEntry.objectID) {
+                    editedCarbs = values.carbs
+                    editedFat = values.fat
+                    editedProtein = values.protein
+                    editedNote = values.note
+                    editedDate = values.date
+                    entryToEdit = carbEntry.objectID
+                }
+            }
+        }
+    }
+}

+ 29 - 6
Trio/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -40,7 +40,7 @@ extension DataTable {
         @FetchRequest(
         @FetchRequest(
             entity: CarbEntryStored.entity(),
             entity: CarbEntryStored.entity(),
             sortDescriptors: [NSSortDescriptor(keyPath: \CarbEntryStored.date, ascending: false)],
             sortDescriptors: [NSSortDescriptor(keyPath: \CarbEntryStored.date, ascending: false)],
-            predicate: NSPredicate.predicateForOneDayAgo,
+            predicate: NSPredicate.carbsHistory,
             animation: .bouncy
             animation: .bouncy
         ) var carbEntryStored: FetchedResults<CarbEntryStored>
         ) var carbEntryStored: FetchedResults<CarbEntryStored>
 
 
@@ -120,6 +120,11 @@ extension DataTable {
                 .sheet(isPresented: $showManualGlucose) {
                 .sheet(isPresented: $showManualGlucose) {
                     addGlucoseView()
                     addGlucoseView()
                 }
                 }
+                .sheet(isPresented: $state.showCarbEntryEditor) {
+                    if let carbEntry = state.carbEntryToEdit {
+                        CarbEntryEditorView(state: state, carbEntry: carbEntry)
+                    }
+                }
         }
         }
 
 
         @ViewBuilder func addButton(_ action: @escaping () -> Void) -> some View {
         @ViewBuilder func addButton(_ action: @escaping () -> Void) -> some View {
@@ -584,20 +589,35 @@ extension DataTable {
                     action: {
                     action: {
                         alertCarbEntryToDelete = meal
                         alertCarbEntryToDelete = meal
 
 
-                        if !meal.isFPU {
+                        // meal is carb-only
+                        if meal.fpuID == nil {
                             alertTitle = "Delete Carbs?"
                             alertTitle = "Delete Carbs?"
                             alertMessage = Formatter.dateFormatter
                             alertMessage = Formatter.dateFormatter
                                 .string(from: meal.date ?? Date()) + ", " +
                                 .string(from: meal.date ?? Date()) + ", " +
                                 (Formatter.decimalFormatterWithTwoFractionDigits.string(for: meal.carbs) ?? "0") +
                                 (Formatter.decimalFormatterWithTwoFractionDigits.string(for: meal.carbs) ?? "0") +
                                 NSLocalizedString(" g", comment: "gram of carbs")
                                 NSLocalizedString(" g", comment: "gram of carbs")
-                        } else {
-                            alertTitle = "Delete Carb Equivalents?"
-                            alertMessage = "All FPUs of the meal will be deleted."
+                        }
+                        // meal is complex-meal or fpu-only
+                        else {
+                            alertTitle = meal.isFPU ? "Delete Carbs Equivalents?" : "Delete Carbs?"
+                            alertMessage = "All FPUs and the carbs of the meal will be deleted."
                         }
                         }
 
 
                         isRemoveHistoryItemAlertPresented = true
                         isRemoveHistoryItemAlertPresented = true
                     }
                     }
                 ).tint(.red)
                 ).tint(.red)
+
+                Button(
+                    "Edit",
+                    systemImage: "pencil",
+                    role: .none,
+                    action: {
+                        state.carbEntryToEdit = meal
+                        state.showCarbEntryEditor = true
+                    }
+                )
+                .tint(!state.settingsManager.settings.useFPUconversion && meal.isFPU ? Color(.systemGray4) : Color.blue)
+                .disabled(!state.settingsManager.settings.useFPUconversion && meal.isFPU)
             }
             }
             .alert(
             .alert(
                 Text(NSLocalizedString(alertTitle, comment: "")),
                 Text(NSLocalizedString(alertTitle, comment: "")),
@@ -611,7 +631,10 @@ extension DataTable {
                     }
                     }
                     let treatmentObjectID = carbEntryToDelete.objectID
                     let treatmentObjectID = carbEntryToDelete.objectID
 
 
-                    state.invokeCarbDeletionTask(treatmentObjectID)
+                    state.invokeCarbDeletionTask(
+                        treatmentObjectID,
+                        isFpuOrComplexMeal: carbEntryToDelete.isFPU || carbEntryToDelete.fat > 0 || carbEntryToDelete.protein > 0
+                    )
                 }
                 }
             } message: {
             } message: {
                 Text("\n" + NSLocalizedString(alertMessage, comment: ""))
                 Text("\n" + NSLocalizedString(alertMessage, comment: ""))

+ 2 - 2
Trio/Sources/Modules/GlucoseNotificationSettings/GlucoseNotificationSettingsStateModel.swift

@@ -3,7 +3,7 @@ import SwiftUI
 extension GlucoseNotificationSettings {
 extension GlucoseNotificationSettings {
     final class StateModel: BaseStateModel<Provider> {
     final class StateModel: BaseStateModel<Provider> {
         @Published var glucoseBadge = false
         @Published var glucoseBadge = false
-        @Published var glucoseNotificationsAlways = false
+        @Published var glucoseNotificationsOption: GlucoseNotificationsOption = .onlyAlarmLimits
         @Published var useAlarmSound = false
         @Published var useAlarmSound = false
         @Published var addSourceInfoToGlucoseNotifications = false
         @Published var addSourceInfoToGlucoseNotifications = false
         @Published var lowGlucose: Decimal = 0
         @Published var lowGlucose: Decimal = 0
@@ -26,7 +26,7 @@ extension GlucoseNotificationSettings {
             subscribeSetting(\.notificationsAlgorithm, on: $notificationsAlgorithm) { notificationsAlgorithm = $0 }
             subscribeSetting(\.notificationsAlgorithm, on: $notificationsAlgorithm) { notificationsAlgorithm = $0 }
 
 
             subscribeSetting(\.glucoseBadge, on: $glucoseBadge) { glucoseBadge = $0 }
             subscribeSetting(\.glucoseBadge, on: $glucoseBadge) { glucoseBadge = $0 }
-            subscribeSetting(\.glucoseNotificationsAlways, on: $glucoseNotificationsAlways) { glucoseNotificationsAlways = $0 }
+            subscribeSetting(\.glucoseNotificationsOption, on: $glucoseNotificationsOption) { glucoseNotificationsOption = $0 }
             subscribeSetting(\.useAlarmSound, on: $useAlarmSound) { useAlarmSound = $0 }
             subscribeSetting(\.useAlarmSound, on: $useAlarmSound) { useAlarmSound = $0 }
             subscribeSetting(\.addSourceInfoToGlucoseNotifications, on: $addSourceInfoToGlucoseNotifications) {
             subscribeSetting(\.addSourceInfoToGlucoseNotifications, on: $addSourceInfoToGlucoseNotifications) {
                 addSourceInfoToGlucoseNotifications = $0 }
                 addSourceInfoToGlucoseNotifications = $0 }

+ 111 - 64
Trio/Sources/Modules/GlucoseNotificationSettings/View/GlucoseNotificationSettingsRootView.swift

@@ -42,6 +42,28 @@ extension GlucoseNotificationSettings {
             List {
             List {
                 SettingInputSection(
                 SettingInputSection(
                     decimalValue: $decimalPlaceholder,
                     decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.useAlarmSound,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    selectedVerboseHint: Binding(
+                        get: { selectedVerboseHint },
+                        set: {
+                            selectedVerboseHint = $0.map { AnyView($0) }
+                            hintLabel = "Play Alarm Sound"
+                        }
+                    ),
+                    units: state.units,
+                    type: .boolean,
+                    label: "Play Alarm Sound",
+                    miniHint: "Alarm with every Trio notification.",
+                    verboseHint: VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        Text(
+                            "This will cause a sound to be triggered by Trio notifications for Carbs Required, and Glucose Low/High Alarms."
+                        )
+                    }
+                )
+                SettingInputSection(
+                    decimalValue: $decimalPlaceholder,
                     booleanValue: $state.notificationsPump,
                     booleanValue: $state.notificationsPump,
                     shouldDisplayHint: $shouldDisplayHint,
                     shouldDisplayHint: $shouldDisplayHint,
                     selectedVerboseHint: Binding(
                     selectedVerboseHint: Binding(
@@ -161,75 +183,94 @@ extension GlucoseNotificationSettings {
                     miniHint: "Show your current glucose on Trio app icon.",
                     miniHint: "Show your current glucose on Trio app icon.",
                     verboseHint: VStack(alignment: .leading, spacing: 10) {
                     verboseHint: VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
                         Text("Default: OFF").bold()
-                        Text("This will add your current glucose on the top right of your Trio icon as a red notification badge.")
+                        Text(
+                            "This will add your current glucose on the top right of your Trio icon as a red notification badge. Changing setting takes effect on next Glucose reading."
+                        )
                     },
                     },
                     headerText: "Various Glucose Notifications"
                     headerText: "Various Glucose Notifications"
                 )
                 )
 
 
-                SettingInputSection(
-                    decimalValue: $decimalPlaceholder,
-                    booleanValue: $state.glucoseNotificationsAlways,
-                    shouldDisplayHint: $shouldDisplayHint,
-                    selectedVerboseHint: Binding(
-                        get: { selectedVerboseHint },
-                        set: {
-                            selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = "Always Notify Glucose"
-                        }
-                    ),
-                    units: state.units,
-                    type: .boolean,
-                    label: "Always Notify Glucose",
-                    miniHint: "Trigger a notification every time your glucose is updated.",
-                    verboseHint: VStack(alignment: .leading, spacing: 10) {
-                        Text("Default: OFF").bold()
-                        Text("A notification will be triggered every time your glucose is updated in Trio.")
-                    }
-                )
+                Section {
+                    VStack {
+                        Picker(
+                            selection: $state.glucoseNotificationsOption,
+                            label: Text("Glucose Notifications")
+                        ) {
+                            ForEach(GlucoseNotificationsOption.allCases) { selection in
+                                Text(selection.displayName).tag(selection)
+                            }
+                        }.padding(.top)
 
 
-                SettingInputSection(
-                    decimalValue: $decimalPlaceholder,
-                    booleanValue: $state.useAlarmSound,
-                    shouldDisplayHint: $shouldDisplayHint,
-                    selectedVerboseHint: Binding(
-                        get: { selectedVerboseHint },
-                        set: {
-                            selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = "Play Alarm Sound"
-                        }
-                    ),
-                    units: state.units,
-                    type: .boolean,
-                    label: "Play Alarm Sound",
-                    miniHint: "Alarm with every Trio notification.",
-                    verboseHint: VStack(alignment: .leading, spacing: 10) {
-                        Text("Default: OFF").bold()
-                        Text("This will cause a sound to be triggered by every Trio notification.")
-                    }
-                )
+                        HStack(alignment: .center) {
+                            Text(
+                                "Choose glucose notifications option. See hint for more details."
+                            )
+                            .font(.footnote)
+                            .foregroundColor(.secondary)
+                            .lineLimit(nil)
+                            Spacer()
+                            Button(
+                                action: {
+                                    hintLabel = "Glucose Notifications"
+                                    selectedVerboseHint =
+                                        AnyView(
+                                            VStack(alignment: .leading, spacing: 10) {
+                                                Text(
+                                                    "Set the Glucose Notifications Option. Descriptions for each option found below."
+                                                )
+                                                VStack(alignment: .leading, spacing: 5) {
+                                                    Text("Disabled:").bold()
+                                                    Text("No Glucose Notificatitons will be triggered.")
+                                                }
+                                                VStack(alignment: .leading, spacing: 5) {
+                                                    Text("Always:").bold()
+                                                    Text(
+                                                        "A notification will be triggered every time your glucose is updated in Trio."
+                                                    )
+                                                }
+                                                VStack(alignment: .leading, spacing: 5) {
+                                                    Text("Only Alarm Limits:").bold()
+                                                    Text(
+                                                        "A notification will be triggered only when glucose levels are below the LOW limit or above the HIGH limit, as specified in Glucose Alarm Limits below."
+                                                    )
+                                                }
+                                            }
+                                        )
+                                    shouldDisplayHint.toggle()
+                                },
+                                label: {
+                                    HStack {
+                                        Image(systemName: "questionmark.circle")
+                                    }
+                                }
+                            ).buttonStyle(BorderlessButtonStyle())
+                        }.padding(.top)
+                    }.padding(.bottom)
+                }.listRowBackground(Color.chart)
 
 
-                SettingInputSection(
-                    decimalValue: $decimalPlaceholder,
-                    booleanValue: $state.addSourceInfoToGlucoseNotifications,
-                    shouldDisplayHint: $shouldDisplayHint,
-                    selectedVerboseHint: Binding(
-                        get: { selectedVerboseHint },
-                        set: {
-                            selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = "Add Glucose Source to Alarm"
+                if state.glucoseNotificationsOption != GlucoseNotificationsOption.disabled {
+                    self.lowAndHighGlucoseAlertSection
+                    SettingInputSection(
+                        decimalValue: $decimalPlaceholder,
+                        booleanValue: $state.addSourceInfoToGlucoseNotifications,
+                        shouldDisplayHint: $shouldDisplayHint,
+                        selectedVerboseHint: Binding(
+                            get: { selectedVerboseHint },
+                            set: {
+                                selectedVerboseHint = $0.map { AnyView($0) }
+                                hintLabel = "Add Glucose Source to Alarm"
+                            }
+                        ),
+                        units: state.units,
+                        type: .boolean,
+                        label: "Add Glucose Source to Alarm",
+                        miniHint: "Source of the glucose reading will be added to the notification.",
+                        verboseHint: VStack(alignment: .leading, spacing: 10) {
+                            Text("Default: OFF").bold()
+                            Text("The source of the glucose reading will be added to the notification.")
                         }
                         }
-                    ),
-                    units: state.units,
-                    type: .boolean,
-                    label: "Add Glucose Source to Alarm",
-                    miniHint: "Source of the glucose reading will be added to the notification.",
-                    verboseHint: VStack(alignment: .leading, spacing: 10) {
-                        Text("Default: OFF").bold()
-                        Text("The source of the glucose reading will be added to the notification.")
-                    }
-                )
-
-                self.lowAndHighGlucoseAlertSection
+                    )
+                }
             }
             }
             .listSectionSpacing(sectionSpacing)
             .listSectionSpacing(sectionSpacing)
             .sheet(isPresented: $shouldDisplayHint) {
             .sheet(isPresented: $shouldDisplayHint) {
@@ -336,8 +377,14 @@ extension GlucoseNotificationSettings {
                                 hintLabel = "Low and High Glucose Alarm Limits"
                                 hintLabel = "Low and High Glucose Alarm Limits"
                                 selectedVerboseHint =
                                 selectedVerboseHint =
                                     AnyView(VStack(alignment: .leading, spacing: 10) {
                                     AnyView(VStack(alignment: .leading, spacing: 10) {
-                                        Text("Low Default: 70 mg/dL").bold()
-                                        Text("High Default: 180 mg/dL").bold()
+                                        let low: Decimal = 70
+                                        let high: Decimal = 180
+                                        let labelLow = (state.units == .mgdL ? low.description : low.formattedAsMmolL) + " " +
+                                            state.units.rawValue
+                                        let labelHigh = (state.units == .mgdL ? high.description : high.formattedAsMmolL) + " " +
+                                            state.units.rawValue
+                                        Text("Low Default: " + labelLow).bold()
+                                        Text("High Default: " + labelHigh).bold()
                                         VStack(alignment: .leading, spacing: 10) {
                                         VStack(alignment: .leading, spacing: 10) {
                                             Text(
                                             Text(
                                                 "These two settings determine the range outside of which you will be notified via push notifications."
                                                 "These two settings determine the range outside of which you will be notified via push notifications."

+ 0 - 3
Trio/Sources/Modules/Home/HomeStateModel+Setup/GlucoseTargetSetup.swift

@@ -78,9 +78,6 @@ extension Home.StateModel {
             )
             )
         }
         }
 
 
-        //TODO: - remove this after bug is fixed
-        debugPrint("\(DebuggingIdentifiers.inProgress) printing target profiles: \(targetProfiles)")
-        
         return targetProfiles
         return targetProfiles
     }
     }
 }
 }

+ 1 - 0
Trio/Sources/Modules/Home/HomeStateModel.swift

@@ -580,6 +580,7 @@ extension Home.StateModel:
         highGlucose = settingsManager.settings.high
         highGlucose = settingsManager.settings.high
         Task {
         Task {
             await getCurrentGlucoseTarget()
             await getCurrentGlucoseTarget()
+            await setupGlucoseTargets()
         }
         }
         hbA1cDisplayUnit = settingsManager.settings.hbA1cDisplayUnit
         hbA1cDisplayUnit = settingsManager.settings.hbA1cDisplayUnit
         glucoseColorScheme = settingsManager.settings.glucoseColorScheme
         glucoseColorScheme = settingsManager.settings.glucoseColorScheme

+ 1 - 1
Trio/Sources/Modules/Home/View/Chart/ChartElements/CarbView.swift

@@ -50,7 +50,7 @@ struct CarbView: ChartContent {
         ForEach(fpuData, id: \.id) { fpu in
         ForEach(fpuData, id: \.id) { fpu in
             let fpuAmount = fpu.carbs
             let fpuAmount = fpu.carbs
             let size = (MainChartHelper.Config.fpuSize + CGFloat(fpuAmount) * MainChartHelper.Config.carbsScale) * 1.8
             let size = (MainChartHelper.Config.fpuSize + CGFloat(fpuAmount) * MainChartHelper.Config.carbsScale) * 1.8
-            let yPosition = minValue
+            let yPosition = minValue // value is parsed to mmol/L when passed into struct based on user settings
 
 
             PointMark(
             PointMark(
                 x: .value("Time", fpu.date ?? Date(), unit: .second),
                 x: .value("Time", fpu.date ?? Date(), unit: .second),

+ 2 - 1
Trio/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -157,7 +157,8 @@ extension MainChartView {
                     units: state.units,
                     units: state.units,
                     carbData: state.carbsFromPersistence,
                     carbData: state.carbsFromPersistence,
                     fpuData: state.fpusFromPersistence,
                     fpuData: state.fpusFromPersistence,
-                    minValue: state.minYAxisValue
+                    minValue: units == .mgdL ? state.minYAxisValue : state.minYAxisValue
+                        .asMmolL
                 )
                 )
 
 
                 ForecastView(
                 ForecastView(

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

@@ -91,7 +91,7 @@ struct PumpView: View {
                         Image(systemName: "battery.100")
                         Image(systemName: "battery.100")
                             .font(.callout)
                             .font(.callout)
                             .foregroundStyle(batteryColor)
                             .foregroundStyle(batteryColor)
-                        Text("\(Int(battery.first?.percent ?? 100)) %")
+                        Text("\(Formatter.integerFormatter.string(for: battery.first?.percent ?? 100) ?? "100") %")
                             .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                             .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
                     }
                 }
                 }

+ 8 - 1
Trio/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift

@@ -46,7 +46,14 @@ extension ISFEditor {
                                     state.shouldDisplaySaving = false
                                     state.shouldDisplaySaving = false
                                 }
                                 }
                             } label: {
                             } label: {
-                                Text(state.shouldDisplaySaving ? "Saving..." : "Save").padding(10)
+                                HStack {
+                                    if state.shouldDisplaySaving {
+                                        ProgressView().padding(.trailing, 10)
+                                    }
+                                    Text(state.shouldDisplaySaving ? "Saving..." : "Save")
+                                }
+                                .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
+                                .padding(10)
                             }
                             }
                         }
                         }
                         .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
                         .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)

+ 36 - 39
Trio/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift

@@ -57,25 +57,6 @@ extension PumpConfig {
                                         Spacer()
                                         Spacer()
                                         Button(
                                         Button(
                                             action: {
                                             action: {
-                                                hintLabel = "Pump Pairing to Trio"
-                                                selectedVerboseHint =
-                                                    AnyView(
-                                                        VStack(alignment: .leading, spacing: 10) {
-                                                            Text(
-                                                                "Current Pump Models Supported:"
-                                                            )
-                                                            VStack(alignment: .leading) {
-                                                                Text("• Medtronic")
-                                                                Text("• Omnipod Eros")
-                                                                Text("• Omnipod Dash")
-                                                                Text("• Dana (RS/-i)")
-                                                                Text("• Pump Simulator")
-                                                            }
-                                                            Text(
-                                                                "Note: If using a pump simulator, you will not have continuous readings from the CGM in Trio. Using a pump simulator is only advisable for becoming familiar with the app user interface. It will not give you insight on how the algorithm will respond."
-                                                            )
-                                                        }
-                                                    )
                                                 shouldDisplayHint.toggle()
                                                 shouldDisplayHint.toggle()
                                             },
                                             },
                                             label: {
                                             label: {
@@ -97,12 +78,46 @@ extension PumpConfig {
                 .navigationTitle("Insulin Pump")
                 .navigationTitle("Insulin Pump")
                 .navigationBarTitleDisplayMode(.automatic)
                 .navigationBarTitleDisplayMode(.automatic)
                 .navigationBarItems(leading: displayClose ? Button("Close", action: state.hideModal) : nil)
                 .navigationBarItems(leading: displayClose ? Button("Close", action: state.hideModal) : nil)
+                .sheet(isPresented: $state.setupPump) {
+                    if let pumpManager = state.provider.apsManager.pumpManager {
+                        PumpSettingsView(
+                            pumpManager: pumpManager,
+                            bluetoothManager: state.provider.apsManager.bluetoothManager!,
+                            completionDelegate: state,
+                            setupDelegate: state
+                        )
+                    } else {
+                        PumpSetupView(
+                            pumpType: state.setupPumpType,
+                            pumpInitialSettings: state.initialSettings,
+                            bluetoothManager: state.provider.apsManager.bluetoothManager!,
+                            completionDelegate: state,
+                            setupDelegate: state
+                        )
+                    }
+                }
                 .sheet(isPresented: $shouldDisplayHint) {
                 .sheet(isPresented: $shouldDisplayHint) {
                     SettingInputHintView(
                     SettingInputHintView(
                         hintDetent: $hintDetent,
                         hintDetent: $hintDetent,
                         shouldDisplayHint: $shouldDisplayHint,
                         shouldDisplayHint: $shouldDisplayHint,
-                        hintLabel: hintLabel ?? "",
-                        hintText: selectedVerboseHint ?? AnyView(EmptyView()),
+                        hintLabel: "Pump Pairing to Trio",
+                        hintText: AnyView(
+                            VStack(alignment: .leading, spacing: 10) {
+                                Text(
+                                    "Current Pump Models Supported:"
+                                )
+                                VStack(alignment: .leading) {
+                                    Text("• Medtronic")
+                                    Text("• Omnipod Eros")
+                                    Text("• Omnipod Dash")
+                                    Text("• Dana (RS/-i)")
+                                    Text("• Pump Simulator")
+                                }
+                                Text(
+                                    "Note: If using a pump simulator, you will not have continuous readings from the CGM in Trio. Using a pump simulator is only advisable for becoming familiar with the app user interface. It will not give you insight on how the algorithm will respond."
+                                )
+                            }
+                        ),
                         sheetTitle: "Help"
                         sheetTitle: "Help"
                     )
                     )
                 }
                 }
@@ -114,24 +129,6 @@ extension PumpConfig {
                     Button("Pump Simulator") { state.addPump(.simulator) }
                     Button("Pump Simulator") { state.addPump(.simulator) }
                 } message: { Text("Select Pump Model") }
                 } message: { Text("Select Pump Model") }
             }
             }
-            .sheet(isPresented: $state.setupPump) {
-                if let pumpManager = state.provider.apsManager.pumpManager {
-                    PumpSettingsView(
-                        pumpManager: pumpManager,
-                        bluetoothManager: state.provider.apsManager.bluetoothManager!,
-                        completionDelegate: state,
-                        setupDelegate: state
-                    )
-                } else {
-                    PumpSetupView(
-                        pumpType: state.setupPumpType,
-                        pumpInitialSettings: state.initialSettings,
-                        bluetoothManager: state.provider.apsManager.bluetoothManager!,
-                        completionDelegate: state,
-                        setupDelegate: state
-                    )
-                }
-            }
         }
         }
     }
     }
 }
 }

+ 6 - 1
Trio/Sources/Modules/PumpConfig/View/PumpSetupView.swift

@@ -87,7 +87,12 @@ extension PumpConfig {
             case let .createdAndOnboarded(pumpManagerUI):
             case let .createdAndOnboarded(pumpManagerUI):
                 debug(.default, "Pump manager  created and onboarded")
                 debug(.default, "Pump manager  created and onboarded")
                 setupDelegate?.pumpManagerOnboarding(didCreatePumpManager: pumpManagerUI)
                 setupDelegate?.pumpManagerOnboarding(didCreatePumpManager: pumpManagerUI)
-                return UIViewController()
+                var vc = pumpManagerUI.settingsViewController(
+                    bluetoothProvider: bluetoothManager,
+                    pumpManagerOnboardingDelegate: setupDelegate
+                )
+                vc.completionDelegate = completionDelegate
+                return vc
             }
             }
         }
         }
 
 

+ 1 - 1
Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift

@@ -306,7 +306,7 @@ extension SMBSettings {
                         }
                         }
                         VStack(alignment: .leading, spacing: 10) {
                         VStack(alignment: .leading, spacing: 10) {
                             Text(
                             Text(
-                                "Warning: Increasing this value above 90 minutes may impact Trio's ability to effectively zero temp and prevent lows."
+                                "Warning: Increasing this value above 60 minutes may impact Trio's ability to effectively zero temp and prevent lows."
                             ).bold()
                             ).bold()
                             Text("Note: UAM SMBs must be enabled to use this limit.")
                             Text("Note: UAM SMBs must be enabled to use this limit.")
                         }
                         }

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

@@ -219,13 +219,13 @@ enum SettingItems {
             title: "Trio Notifications",
             title: "Trio Notifications",
             view: .glucoseNotificationSettings,
             view: .glucoseNotificationSettings,
             searchContents: [
             searchContents: [
+                "Play Alarm Sound",
                 "Always Notify Pump",
                 "Always Notify Pump",
                 "Always Notify CGM",
                 "Always Notify CGM",
                 "Always Notify Carb",
                 "Always Notify Carb",
                 "Always Notify Algorithm",
                 "Always Notify Algorithm",
                 "Show Glucose App Badge",
                 "Show Glucose App Badge",
-                "Always Notify Glucose",
-                "Play Alarm Sound",
+                "Glucose Notifications",
                 "Add Glucose Source to Alarm",
                 "Add Glucose Source to Alarm",
                 "Low Glucose Alarm Limit",
                 "Low Glucose Alarm Limit",
                 "High Glucose Alarm Limit"
                 "High Glucose Alarm Limit"

+ 2 - 7
Trio/Sources/Modules/Stat/View/StatsView.swift

@@ -128,13 +128,8 @@ struct StatsView: View {
     var hba1c: some View {
     var hba1c: some View {
         HStack(spacing: 50) {
         HStack(spacing: 50) {
             let useUnit: GlucoseUnits = {
             let useUnit: GlucoseUnits = {
-                if units == .mmolL && hbA1cDisplayUnit == .mmolMol {
-                    return .mgdL
-                } else if (units == .mgdL && hbA1cDisplayUnit == .mmolMol) || units == .mmolL {
-                    return .mmolL
-                } else {
-                    return .mgdL
-                }
+                if hbA1cDisplayUnit == .mmolMol { return .mmolL }
+                else { return .mgdL }
             }()
             }()
 
 
             let hba1cs = glucoseStats()
             let hba1cs = glucoseStats()

+ 22 - 22
Trio/Sources/Modules/TargetsEditor/View/TargetsEditorRootView.swift

@@ -31,30 +31,30 @@ extension TargetsEditor {
 
 
                 Group {
                 Group {
                     HStack {
                     HStack {
-                        HStack {
-                            Button {
-                                let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
-                                impactHeavy.impactOccurred()
-                                state.save()
-
-                                // deactivate saving display after 1.25 seconds
-                                DispatchQueue.main.asyncAfter(deadline: .now() + 1.25) {
-                                    state.shouldDisplaySaving = false
+                        Button(action: {
+                            let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
+                            impactHeavy.impactOccurred()
+                            state.save()
+
+                            // deactivate saving display after 1.25 seconds
+                            DispatchQueue.main.asyncAfter(deadline: .now() + 1.25) {
+                                state.shouldDisplaySaving = false
+                            }
+                        }, label: {
+                            HStack {
+                                if state.shouldDisplaySaving {
+                                    ProgressView().padding(.trailing, 10)
                                 }
                                 }
-                            } label: {
-                                HStack {
-                                    if state.shouldDisplaySaving {
-                                        ProgressView().padding(.trailing, 10)
-                                    }
-                                    Text(state.shouldDisplaySaving ? "Saving..." : "Save")
-                                }.padding(10)
+                                Text(state.shouldDisplaySaving ? "Saving..." : "Save")
                             }
                             }
-                        }
-                        .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
-                        .disabled(shouldDisableButton)
-                        .background(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
-                        .tint(.white)
-                        .clipShape(RoundedRectangle(cornerRadius: 8))
+                            .frame(width: UIScreen.main.bounds.width * 0.9, alignment: .center)
+                            .padding(10)
+                        })
+                            .frame(width: UIScreen.main.bounds.width * 0.9, height: 40, alignment: .center)
+                            .disabled(shouldDisableButton)
+                            .background(shouldDisableButton ? Color(.systemGray4) : Color(.systemBlue))
+                            .tint(.white)
+                            .clipShape(RoundedRectangle(cornerRadius: 8))
                     }
                     }
                 }.padding(5)
                 }.padding(5)
             }
             }

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

@@ -553,7 +553,8 @@ extension Treatments {
                 protein: protein,
                 protein: protein,
                 note: note,
                 note: note,
                 enteredBy: CarbsEntry.local,
                 enteredBy: CarbsEntry.local,
-                isFPU: false, fpuID: UUID().uuidString
+                isFPU: false,
+                fpuID: fat > 0 || protein > 0 ? UUID().uuidString : nil
             )]
             )]
             await carbsStorage.storeCarbs(carbsToStore, areFetchedFromRemote: false)
             await carbsStorage.storeCarbs(carbsToStore, areFetchedFromRemote: false)
 
 

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

@@ -177,7 +177,7 @@ struct MealPresetView: View {
     }
     }
 
 
     private var noPresetChosen: Bool {
     private var noPresetChosen: Bool {
-        state.selection == nil || carbs == 0 // || (state.useFPUconversion && (fat == 0 || protein == 0))
+        state.selection == nil || state.summation.isEmpty
     }
     }
 
 
     @ViewBuilder private func dishInfos() -> some View {
     @ViewBuilder private func dishInfos() -> some View {

+ 19 - 17
Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift

@@ -79,31 +79,31 @@ extension Treatments {
         @ViewBuilder private func proteinAndFat() -> some View {
         @ViewBuilder private func proteinAndFat() -> some View {
             HStack {
             HStack {
                 HStack {
                 HStack {
-                    Text("Fat")
+                    Text("Protein")
+
                     TextFieldWithToolBar(
                     TextFieldWithToolBar(
-                        text: $state.fat,
+                        text: $state.protein,
                         placeholder: "0",
                         placeholder: "0",
                         keyboardType: .numberPad,
                         keyboardType: .numberPad,
                         numberFormatter: mealFormatter,
                         numberFormatter: mealFormatter,
                         previousTextField: { focusOnPreviousTextField(index: 2) },
                         previousTextField: { focusOnPreviousTextField(index: 2) },
                         nextTextField: { focusOnNextTextField(index: 2) }
                         nextTextField: { focusOnNextTextField(index: 2) }
-                    ).focused($focusedField, equals: .fat)
+                    ).focused($focusedField, equals: .protein)
                     Text("g").foregroundColor(.secondary)
                     Text("g").foregroundColor(.secondary)
                 }
                 }
 
 
                 Divider().foregroundStyle(.primary).fontWeight(.bold).frame(width: 10)
                 Divider().foregroundStyle(.primary).fontWeight(.bold).frame(width: 10)
 
 
                 HStack {
                 HStack {
-                    Text("Protein")
-
+                    Text("Fat")
                     TextFieldWithToolBar(
                     TextFieldWithToolBar(
-                        text: $state.protein,
+                        text: $state.fat,
                         placeholder: "0",
                         placeholder: "0",
                         keyboardType: .numberPad,
                         keyboardType: .numberPad,
                         numberFormatter: mealFormatter,
                         numberFormatter: mealFormatter,
                         previousTextField: { focusOnPreviousTextField(index: 3) },
                         previousTextField: { focusOnPreviousTextField(index: 3) },
                         nextTextField: { focusOnNextTextField(index: 3) }
                         nextTextField: { focusOnNextTextField(index: 3) }
-                    ).focused($focusedField, equals: .protein)
+                    ).focused($focusedField, equals: .fat)
                     Text("g").foregroundColor(.secondary)
                     Text("g").foregroundColor(.secondary)
                 }
                 }
             }
             }
@@ -328,15 +328,17 @@ extension Treatments {
                         Text("Close")
                         Text("Close")
                     }
                     }
                 }
                 }
-                ToolbarItem(placement: .topBarTrailing) {
-                    Button(action: {
-                        showPresetSheet = true
-                    }, label: {
-                        HStack {
-                            Text("Presets")
-                            Image(systemName: "plus")
-                        }
-                    })
+                if state.displayPresets {
+                    ToolbarItem(placement: .topBarTrailing) {
+                        Button(action: {
+                            showPresetSheet = true
+                        }, label: {
+                            HStack {
+                                Text("Presets")
+                                Image(systemName: "plus")
+                            }
+                        })
+                    }
                 }
                 }
             })
             })
             .onAppear {
             .onAppear {
@@ -408,7 +410,7 @@ extension Treatments {
 
 
         private var taskButtonLabel: some View {
         private var taskButtonLabel: some View {
             if pumpBolusLimitExceeded {
             if pumpBolusLimitExceeded {
-                return Text("Max Bolus of \(state.maxBolus.description) U E== 0xceeded")
+                return Text("Max Bolus of \(state.maxBolus.description) U Exceeded")
             } else if externalBolusLimitExceeded {
             } else if externalBolusLimitExceeded {
                 return Text("Max External Bolus of \(state.maxExternal.description) U Exceeded")
                 return Text("Max External Bolus of \(state.maxExternal.description) U Exceeded")
             } else if carbLimitExceeded {
             } else if carbLimitExceeded {

+ 14 - 16
Trio/Sources/Modules/WatchConfig/View/WatchConfigGarminView.swift

@@ -19,7 +19,7 @@ struct WatchConfigGarminView: View {
     }
     }
 
 
     var body: some View {
     var body: some View {
-        List {
+        Form {
             Section(
             Section(
                 header: Text("Garmin Configuration"),
                 header: Text("Garmin Configuration"),
                 content:
                 content:
@@ -43,13 +43,6 @@ struct WatchConfigGarminView: View {
                             Spacer()
                             Spacer()
                             Button(
                             Button(
                                 action: {
                                 action: {
-                                    hintLabel = "Add Device"
-                                    selectedVerboseHint =
-                                        AnyView(
-                                            Text(
-                                                "Add Garmin Device to Trio. Please look at the docs to see which devices are supported."
-                                            )
-                                        )
                                     shouldDisplayHint.toggle()
                                     shouldDisplayHint.toggle()
                                 },
                                 },
                                 label: {
                                 label: {
@@ -64,14 +57,17 @@ struct WatchConfigGarminView: View {
             ).listRowBackground(Color.chart)
             ).listRowBackground(Color.chart)
 
 
             if !state.devices.isEmpty {
             if !state.devices.isEmpty {
-                Section(header: Text("Garmin Watch")) {
-                    List {
-                        ForEach(state.devices, id: \.uuid) { device in
-                            Text(device.friendlyName)
+                Section(
+                    header: Text("Garmin Watch"),
+                    content: {
+                        List {
+                            ForEach(state.devices, id: \.uuid) { device in
+                                Text(device.friendlyName)
+                            }
+                            .onDelete(perform: onDelete)
                         }
                         }
-                        .onDelete(perform: onDelete)
                     }
                     }
-                }.listRowBackground(Color.chart)
+                ).listRowBackground(Color.chart)
             }
             }
         }
         }
         .listSectionSpacing(sectionSpacing)
         .listSectionSpacing(sectionSpacing)
@@ -79,8 +75,10 @@ struct WatchConfigGarminView: View {
             SettingInputHintView(
             SettingInputHintView(
                 hintDetent: $hintDetent,
                 hintDetent: $hintDetent,
                 shouldDisplayHint: $shouldDisplayHint,
                 shouldDisplayHint: $shouldDisplayHint,
-                hintLabel: hintLabel ?? "",
-                hintText: selectedVerboseHint ?? AnyView(EmptyView()),
+                hintLabel: "Add Device",
+                hintText: Text(
+                    "Add Garmin Device to Trio. Please look at the docs to see which devices are supported."
+                ),
                 sheetTitle: "Help"
                 sheetTitle: "Help"
             )
             )
         }
         }

+ 5 - 1
Trio/Sources/Router/Router.swift

@@ -60,7 +60,11 @@ final class BaseRouter: Router {
         case .carb:
         case .carb:
             guard settings.notificationsCarb else { return false }
             guard settings.notificationsCarb else { return false }
         case .glucose:
         case .glucose:
-            guard settings.glucoseNotificationsAlways else { return false }
+            guard (
+                message.type == .warning &&
+                    settings.glucoseNotificationsOption == GlucoseNotificationsOption.onlyAlarmLimits
+            ) ||
+                settings.glucoseNotificationsOption == GlucoseNotificationsOption.alwaysEveryCGM else { return false }
         case .algorithm:
         case .algorithm:
             guard settings.notificationsAlgorithm else { return false }
             guard settings.notificationsAlgorithm else { return false }
         case .misc:
         case .misc:

+ 19 - 18
Trio/Sources/Services/HealthKit/HealthKitManager.swift

@@ -245,26 +245,27 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
             for allSamples in carbs {
             for allSamples in carbs {
                 guard let id = allSamples.id else { continue }
                 guard let id = allSamples.id else { continue }
                 let fpuID = allSamples.fpuID ?? id
                 let fpuID = allSamples.fpuID ?? id
-
                 let startDate = allSamples.actualDate ?? Date()
                 let startDate = allSamples.actualDate ?? Date()
 
 
-                // Carbs Sample
+                // Carbs Sample (only if value is greater than 0)
                 let carbValue = allSamples.carbs
                 let carbValue = allSamples.carbs
-                let carbSample = HKQuantitySample(
-                    type: carbSampleType,
-                    quantity: HKQuantity(unit: .gram(), doubleValue: Double(carbValue)),
-                    start: startDate,
-                    end: startDate,
-                    metadata: [
-                        HKMetadataKeyExternalUUID: id,
-                        HKMetadataKeySyncIdentifier: id,
-                        HKMetadataKeySyncVersion: 1
-                    ]
-                )
-                samples.append(carbSample)
+                if carbValue > 0 {
+                    let carbSample = HKQuantitySample(
+                        type: carbSampleType,
+                        quantity: HKQuantity(unit: .gram(), doubleValue: Double(carbValue)),
+                        start: startDate,
+                        end: startDate,
+                        metadata: [
+                            HKMetadataKeyExternalUUID: id,
+                            HKMetadataKeySyncIdentifier: id,
+                            HKMetadataKeySyncVersion: 1
+                        ]
+                    )
+                    samples.append(carbSample)
+                }
 
 
-                // Fat Sample (if available)
-                if let fatValue = allSamples.fat {
+                // Fat Sample (only if value is greater than 0)
+                if let fatValue = allSamples.fat, fatValue > 0 {
                     let fatSample = HKQuantitySample(
                     let fatSample = HKQuantitySample(
                         type: fatSampleType,
                         type: fatSampleType,
                         quantity: HKQuantity(unit: .gram(), doubleValue: Double(fatValue)),
                         quantity: HKQuantity(unit: .gram(), doubleValue: Double(fatValue)),
@@ -279,8 +280,8 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
                     samples.append(fatSample)
                     samples.append(fatSample)
                 }
                 }
 
 
-                // Protein Sample (if available)
-                if let proteinValue = allSamples.protein {
+                // Protein Sample (only if value is greater than 0)
+                if let proteinValue = allSamples.protein, proteinValue > 0 {
                     let proteinSample = HKQuantitySample(
                     let proteinSample = HKQuantitySample(
                         type: proteinSampleType,
                         type: proteinSampleType,
                         quantity: HKQuantity(unit: .gram(), doubleValue: Double(proteinValue)),
                         quantity: HKQuantity(unit: .gram(), doubleValue: Double(proteinValue)),

+ 1 - 1
Trio/Sources/Services/RemoteControl/TrioRemoteControl+Meal.swift

@@ -69,7 +69,7 @@ extension TrioRemoteControl {
             note: "Remote meal command",
             note: "Remote meal command",
             enteredBy: CarbsEntry.local,
             enteredBy: CarbsEntry.local,
             isFPU: false,
             isFPU: false,
-            fpuID: nil
+            fpuID: fatDecimal ?? 0 > 0 || proteinDecimal ?? 0 > 0 ? UUID().uuidString : nil
         )
         )
 
 
         await carbsStorage.storeCarbs([mealEntry], areFetchedFromRemote: false)
         await carbsStorage.storeCarbs([mealEntry], areFetchedFromRemote: false)

+ 7 - 11
Trio/Sources/Services/UserNotifications/UserNotificationsManager.swift

@@ -128,7 +128,7 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
     private func addAppBadge(glucose: Int?) {
     private func addAppBadge(glucose: Int?) {
         guard let glucose = glucose, settingsManager.settings.glucoseBadge else {
         guard let glucose = glucose, settingsManager.settings.glucoseBadge else {
             DispatchQueue.main.async {
             DispatchQueue.main.async {
-                self.center.setBadgeCount(-1) { error in
+                self.center.setBadgeCount(0) { error in
                     guard let error else {
                     guard let error else {
                         return
                         return
                     }
                     }
@@ -270,8 +270,6 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
 
 
             addAppBadge(glucose: (glucoseObjects.first?.glucose).map { Int($0) })
             addAppBadge(glucose: (glucoseObjects.first?.glucose).map { Int($0) })
 
 
-            guard glucoseStorage.alarm != nil || settingsManager.settings.glucoseNotificationsAlways else { return }
-
             var titles: [String] = []
             var titles: [String] = []
             var notificationAlarm = false
             var notificationAlarm = false
             var messageType = MessageType.info
             var messageType = MessageType.info
@@ -421,24 +419,22 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
             trigger: trigger,
             trigger: trigger,
             action: action
             action: action
         )
         )
-        if alertPermissionsChecker.notificationsDisabled {
-            router.alertMessage.send(messageCont)
-            return
-        }
-        guard router.allowNotify(messageCont, settingsManager.settings) else { return }
-
         var alertIdentifier = identifier.rawValue
         var alertIdentifier = identifier.rawValue
         alertIdentifier = identifier == .pumpNotification ? alertIdentifier + content
         alertIdentifier = identifier == .pumpNotification ? alertIdentifier + content
             .title : (identifier == .alertMessageNotification ? alertIdentifier + content.body : alertIdentifier)
             .title : (identifier == .alertMessageNotification ? alertIdentifier + content.body : alertIdentifier)
-        let request = UNNotificationRequest(identifier: alertIdentifier, content: content, trigger: trigger)
-
         if deleteOld {
         if deleteOld {
             DispatchQueue.main.async {
             DispatchQueue.main.async {
                 self.center.removeDeliveredNotifications(withIdentifiers: [alertIdentifier])
                 self.center.removeDeliveredNotifications(withIdentifiers: [alertIdentifier])
                 self.center.removePendingNotificationRequests(withIdentifiers: [alertIdentifier])
                 self.center.removePendingNotificationRequests(withIdentifiers: [alertIdentifier])
             }
             }
         }
         }
+        if alertPermissionsChecker.notificationsDisabled {
+            router.alertMessage.send(messageCont)
+            return
+        }
+        guard router.allowNotify(messageCont, settingsManager.settings) else { return }
 
 
+        let request = UNNotificationRequest(identifier: alertIdentifier, content: content, trigger: trigger)
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
             self.center.add(request) { error in
             self.center.add(request) { error in
                 if let error = error {
                 if let error = error {

+ 11 - 4
oref0_source_version.txt

@@ -1,9 +1,16 @@
-oref0 branch: dev - git version: 22603e2
+oref0 branch: tdd-pumpdataCheck - git version: 46deecb
 
 
 Last commits:
 Last commits:
-22603e2 skip_NetralTemps only if SMB's somehow disabled
-0ff47a3 fix weightedAverage, always calculate TDD
-e274bb0 revert to standard HBT calculation
+46deecb remove dynisf check on pumpdata calc
+2f258b2 Merge pull request #36 from mountrcg/fixTDDcheck
+4998a09 fix condition  to use weightedAverage as TDD
+ff54c14 Merge pull request #35 from mountrcg/skipNTfix
+bbdf258 Merge pull request #34 from mountrcg/TToref-reset
+6e4c8ce Merge pull request #31 from simonp22/calc_tdd
+c5f9a6a Use weightedAverage as tdd if weightPercentage > 1
+7153b43 skip_NetralTemps only if SMB's somehow disabled
+e0caaa0 revert to standard HBT calculation
+06ca64b Always calculate TDD
 363fd11 Merge pull request #28 from bjornoleh/harmonise_defaults
 363fd11 Merge pull request #28 from bjornoleh/harmonise_defaults
 2d695e1 index.js: set enableUAM to false, and remove whitespace in L11
 2d695e1 index.js: set enableUAM to false, and remove whitespace in L11
 8f5f820 Harmonise profile defaults with openaps/oref0
 8f5f820 Harmonise profile defaults with openaps/oref0

+ 1 - 2
trio-oref/lib/determine-basal/determine-basal.js

@@ -383,7 +383,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
         console.log("Pumphistory is empty!");
         console.log("Pumphistory is empty!");
         dynISFenabled = false;
         dynISFenabled = false;
         enableDynamicCR = false;
         enableDynamicCR = false;
-    } else if (dynISFenabled) {
+    } else {
         let phLastEntry = pumphistory.length - 1;
         let phLastEntry = pumphistory.length - 1;
         var endDate = new Date(pumphistory[phLastEntry].timestamp);
         var endDate = new Date(pumphistory[phLastEntry].timestamp);
         var startDate = new Date(pumphistory[0].timestamp);
         var startDate = new Date(pumphistory[0].timestamp);
@@ -559,7 +559,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
 
 
     } else { tddReason = ", TDD: Not enough pumpData (< 21h)"; }
     } else { tddReason = ", TDD: Not enough pumpData (< 21h)"; }
 
 
-
     var tdd_before = tdd;
     var tdd_before = tdd;
 
 
     // -------------------- END OF TDD ----------------------------------------------------
     // -------------------- END OF TDD ----------------------------------------------------