Forráskód Böngészése

Merge pull request #1127 from nightscout/feat/refactor-history-module

Refactor History Module: split files; cleanup; use confirmation dialogs
Deniz Cengiz 2 hete
szülő
commit
0b2dff5e5b

+ 60 - 0
Trio.xcodeproj/project.pbxproj

@@ -744,6 +744,19 @@
 		FE41E4D629463EE20047FD55 /* NightscoutPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE41E4D529463EE20047FD55 /* NightscoutPreferences.swift */; };
 		FE66D16B291F74F8005D6F77 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */; };
 		FEFFA7A22929FE49007B8193 /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */; };
+		BD175EBE0000100000000001 /* HistoryDataFlow+Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE0000100000000001 /* HistoryDataFlow+Models.swift */; };
+		BD175EBE0000100000000002 /* HistoryDeletionTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE0000100000000002 /* HistoryDeletionTarget.swift */; };
+		BD175EBE0000100000000003 /* HistoryStateModel+Glucose.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE0000100000000003 /* HistoryStateModel+Glucose.swift */; };
+		BD175EBE0000100000000004 /* HistoryStateModel+Carbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE0000100000000004 /* HistoryStateModel+Carbs.swift */; };
+		BD175EBE0000100000000005 /* HistoryStateModel+Insulin.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE0000100000000005 /* HistoryStateModel+Insulin.swift */; };
+		BD175EBE0000100000000006 /* HistoryStateModel+CarbEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE0000100000000006 /* HistoryStateModel+CarbEditing.swift */; };
+		BD175EBE0000100000000007 /* HistoryRootView+Treatments.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE0000100000000007 /* HistoryRootView+Treatments.swift */; };
+		BD175EBE0000100000000008 /* HistoryRootView+Meals.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE0000100000000008 /* HistoryRootView+Meals.swift */; };
+		BD175EBE0000100000000009 /* HistoryRootView+Glucose.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE0000100000000009 /* HistoryRootView+Glucose.swift */; };
+		BD175EBE000010000000000A /* HistoryRootView+Adjustments.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE000010000000000A /* HistoryRootView+Adjustments.swift */; };
+		BD175EBE000010000000000B /* HistoryRootView+Filters.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE000010000000000B /* HistoryRootView+Filters.swift */; };
+		BD175EBE000010000000000C /* HistoryRootView+AddGlucose.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE000010000000000C /* HistoryRootView+AddGlucose.swift */; };
+		BD175EBE000010000000000D /* HistoryRootView+Confirmations.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD175EFE000010000000000D /* HistoryRootView+Confirmations.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -1586,6 +1599,19 @@
 		FE41E4D529463EE20047FD55 /* NightscoutPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutPreferences.swift; sourceTree = "<group>"; };
 		FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = "<group>"; };
 		FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extensions.swift"; sourceTree = "<group>"; };
+		BD175EFE0000100000000001 /* HistoryDataFlow+Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryDataFlow+Models.swift"; sourceTree = "<group>"; };
+		BD175EFE0000100000000002 /* HistoryDeletionTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryDeletionTarget.swift; sourceTree = "<group>"; };
+		BD175EFE0000100000000003 /* HistoryStateModel+Glucose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryStateModel+Glucose.swift"; sourceTree = "<group>"; };
+		BD175EFE0000100000000004 /* HistoryStateModel+Carbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryStateModel+Carbs.swift"; sourceTree = "<group>"; };
+		BD175EFE0000100000000005 /* HistoryStateModel+Insulin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryStateModel+Insulin.swift"; sourceTree = "<group>"; };
+		BD175EFE0000100000000006 /* HistoryStateModel+CarbEditing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryStateModel+CarbEditing.swift"; sourceTree = "<group>"; };
+		BD175EFE0000100000000007 /* HistoryRootView+Treatments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryRootView+Treatments.swift"; sourceTree = "<group>"; };
+		BD175EFE0000100000000008 /* HistoryRootView+Meals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryRootView+Meals.swift"; sourceTree = "<group>"; };
+		BD175EFE0000100000000009 /* HistoryRootView+Glucose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryRootView+Glucose.swift"; sourceTree = "<group>"; };
+		BD175EFE000010000000000A /* HistoryRootView+Adjustments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryRootView+Adjustments.swift"; sourceTree = "<group>"; };
+		BD175EFE000010000000000B /* HistoryRootView+Filters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryRootView+Filters.swift"; sourceTree = "<group>"; };
+		BD175EFE000010000000000C /* HistoryRootView+AddGlucose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryRootView+AddGlucose.swift"; sourceTree = "<group>"; };
+		BD175EFE000010000000000D /* HistoryRootView+Confirmations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryRootView+Confirmations.swift"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFileSystemSynchronizedRootGroup section */
@@ -1717,6 +1743,13 @@
 			children = (
 				BDA7593D2D37CFC000E649A4 /* CarbEntryEditorView.swift */,
 				881E04BA5E0A003DE8E0A9C6 /* HistoryRootView.swift */,
+				BD175EFE0000100000000007 /* HistoryRootView+Treatments.swift */,
+				BD175EFE0000100000000008 /* HistoryRootView+Meals.swift */,
+				BD175EFE0000100000000009 /* HistoryRootView+Glucose.swift */,
+				BD175EFE000010000000000A /* HistoryRootView+Adjustments.swift */,
+				BD175EFE000010000000000B /* HistoryRootView+Filters.swift */,
+				BD175EFE000010000000000C /* HistoryRootView+AddGlucose.swift */,
+				BD175EFE000010000000000D /* HistoryRootView+Confirmations.swift */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -2852,13 +2885,27 @@
 			isa = PBXGroup;
 			children = (
 				A401509D21F7F35D4E109EDA /* HistoryDataFlow.swift */,
+				BD175EFE0000100000000001 /* HistoryDataFlow+Models.swift */,
+				BD175EFE0000100000000002 /* HistoryDeletionTarget.swift */,
 				60744C3E9BB3652895C908CC /* HistoryProvider.swift */,
 				9455FA2D92E77A6C4AFED8A3 /* HistoryStateModel.swift */,
+				BD175EC00000100000000001 /* HistoryStateModel+Deletion */,
 				0EE66DD474AFFD4FD787D5B9 /* View */,
 			);
 			path = History;
 			sourceTree = "<group>";
 		};
+		BD175EC00000100000000001 /* HistoryStateModel+Deletion */ = {
+			isa = PBXGroup;
+			children = (
+				BD175EFE0000100000000003 /* HistoryStateModel+Glucose.swift */,
+				BD175EFE0000100000000004 /* HistoryStateModel+Carbs.swift */,
+				BD175EFE0000100000000005 /* HistoryStateModel+Insulin.swift */,
+				BD175EFE0000100000000006 /* HistoryStateModel+CarbEditing.swift */,
+			);
+			path = "HistoryStateModel+Deletion";
+			sourceTree = "<group>";
+		};
 		A42F1FEDFFD0DDE00AAD54D3 /* BasalProfileEditor */ = {
 			isa = PBXGroup;
 			children = (
@@ -4796,6 +4843,19 @@
 				8194B80890CDD6A3C13B0FEE /* SnoozeStateModel.swift in Sources */,
 				BDA25EE42D260CD500035F34 /* AppleWatchManager.swift in Sources */,
 				0437CE46C12535A56504EC19 /* SnoozeRootView.swift in Sources */,
+				BD175EBE0000100000000001 /* HistoryDataFlow+Models.swift in Sources */,
+				BD175EBE0000100000000002 /* HistoryDeletionTarget.swift in Sources */,
+				BD175EBE0000100000000003 /* HistoryStateModel+Glucose.swift in Sources */,
+				BD175EBE0000100000000004 /* HistoryStateModel+Carbs.swift in Sources */,
+				BD175EBE0000100000000005 /* HistoryStateModel+Insulin.swift in Sources */,
+				BD175EBE0000100000000006 /* HistoryStateModel+CarbEditing.swift in Sources */,
+				BD175EBE0000100000000007 /* HistoryRootView+Treatments.swift in Sources */,
+				BD175EBE0000100000000008 /* HistoryRootView+Meals.swift in Sources */,
+				BD175EBE0000100000000009 /* HistoryRootView+Glucose.swift in Sources */,
+				BD175EBE000010000000000A /* HistoryRootView+Adjustments.swift in Sources */,
+				BD175EBE000010000000000B /* HistoryRootView+Filters.swift in Sources */,
+				BD175EBE000010000000000C /* HistoryRootView+AddGlucose.swift in Sources */,
+				BD175EBE000010000000000D /* HistoryRootView+Confirmations.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 1 - 0
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -149378,6 +149378,7 @@
       }
     },
     "Log Glucose" : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {

+ 161 - 0
Trio/Sources/Modules/History/HistoryDataFlow+Models.swift

@@ -0,0 +1,161 @@
+import Foundation
+import SwiftUI
+
+extension History {
+    class Treatment: Identifiable, Hashable, Equatable {
+        let id: String
+        let idPumpEvent: String?
+        let units: GlucoseUnits
+        let type: DataType
+        let date: Date
+        let amount: Decimal?
+        let secondAmount: Decimal?
+        let duration: Decimal?
+        let isFPU: Bool?
+        let fpuID: String?
+        let note: String?
+        let isSMB: Bool?
+        let isExternal: Bool?
+
+        private var numberFormatter: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.numberStyle = .decimal
+            formatter.maximumFractionDigits = 2
+            return formatter
+        }
+
+        private var tempTargetFormater: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.numberStyle = .decimal
+            formatter.maximumFractionDigits = 1
+            return formatter
+        }
+
+        init(
+            units: GlucoseUnits,
+            type: DataType,
+            date: Date,
+            amount: Decimal? = nil,
+            secondAmount: Decimal? = nil,
+            duration: Decimal? = nil,
+            id: String? = nil,
+            idPumpEvent: String? = nil,
+            isFPU: Bool? = nil,
+            fpuID: String? = nil,
+            note: String? = nil,
+            isSMB: Bool? = nil,
+            isExternal: Bool? = nil
+        ) {
+            self.units = units
+            self.type = type
+            self.date = date
+            self.amount = amount
+            self.secondAmount = secondAmount
+            self.duration = duration
+            self.id = id ?? UUID().uuidString
+            self.idPumpEvent = idPumpEvent
+            self.isFPU = isFPU
+            self.fpuID = fpuID
+            self.note = note
+            self.isSMB = isSMB
+            self.isExternal = isExternal
+        }
+
+        static func == (lhs: Treatment, rhs: Treatment) -> Bool {
+            lhs.id == rhs.id
+        }
+
+        func hash(into hasher: inout Hasher) {
+            hasher.combine(id)
+        }
+
+        var amountText: String {
+            guard let amount = amount else {
+                return ""
+            }
+
+            if amount == 0, duration == 0 {
+                return "Cancel temp"
+            }
+
+            switch type {
+            case .carbs:
+                return numberFormatter
+                    .string(from: amount as NSNumber)! + String(localized: " g", comment: "gram of carbs")
+            case .fpus:
+                return numberFormatter
+                    .string(from: amount as NSNumber)! + String(localized: " g", comment: "gram of carb equilvalents")
+            case .bolus:
+                var bolusText = " "
+                if isSMB ?? false {}
+                else if isExternal ?? false {
+                    bolusText += String(localized: "External", comment: "External Insulin")
+                } else {
+                    bolusText += String(localized: "Manual", comment: "Manual Bolus")
+                }
+
+                return numberFormatter
+                    .string(from: amount as NSNumber)! + String(localized: " U", comment: "Insulin unit") + bolusText
+            case .tempBasal:
+                return numberFormatter
+                    .string(from: amount as NSNumber)! + String(localized: " U/hr", comment: "Unit insulin per hour")
+            case .tempTarget:
+                var converted = amount
+                if units == .mmolL {
+                    converted = converted.asMmolL
+                }
+
+                guard var secondAmount = secondAmount else {
+                    return numberFormatter.string(from: converted as NSNumber)! + " \(units.rawValue)"
+                }
+                if units == .mmolL {
+                    secondAmount = secondAmount.asMmolL
+                }
+
+                return tempTargetFormater.string(from: converted as NSNumber)! + " - " + tempTargetFormater
+                    .string(from: secondAmount as NSNumber)! + " \(units.rawValue)"
+            case .resume,
+                 .suspend:
+                return type.name
+            }
+        }
+
+        var color: Color {
+            switch type {
+            case .carbs:
+                return .loopYellow
+            case .fpus:
+                return .orange.opacity(0.5)
+            case .bolus:
+                return Color.insulin
+            case .tempBasal:
+                return Color.insulin.opacity(0.4)
+            case .resume,
+                 .suspend,
+                 .tempTarget:
+                return .loopGray
+            }
+        }
+
+        var durationText: String? {
+            guard let duration = duration, duration > 0 else {
+                return nil
+            }
+            return numberFormatter.string(from: duration as NSNumber)! + " min"
+        }
+    }
+
+    class Glucose: Identifiable, Hashable, Equatable {
+        static func == (lhs: History.Glucose, rhs: History.Glucose) -> Bool {
+            lhs.glucose == rhs.glucose
+        }
+
+        let glucose: BloodGlucose
+
+        init(glucose: BloodGlucose) {
+            self.glucose = glucose
+        }
+
+        var id: String { glucose.id }
+    }
+}

+ 2 - 157
Trio/Sources/Modules/History/HistoryDataFlow.swift

@@ -82,163 +82,6 @@ enum History {
             }
         }
     }
-
-    class Treatment: Identifiable, Hashable, Equatable {
-        let id: String
-        let idPumpEvent: String?
-        let units: GlucoseUnits
-        let type: DataType
-        let date: Date
-        let amount: Decimal?
-        let secondAmount: Decimal?
-        let duration: Decimal?
-        let isFPU: Bool?
-        let fpuID: String?
-        let note: String?
-        let isSMB: Bool?
-        let isExternal: Bool?
-
-        private var numberFormatter: NumberFormatter {
-            let formatter = NumberFormatter()
-            formatter.numberStyle = .decimal
-            formatter.maximumFractionDigits = 2
-            return formatter
-        }
-
-        private var tempTargetFormater: NumberFormatter {
-            let formatter = NumberFormatter()
-            formatter.numberStyle = .decimal
-            formatter.maximumFractionDigits = 1
-            return formatter
-        }
-
-        init(
-            units: GlucoseUnits,
-            type: DataType,
-            date: Date,
-            amount: Decimal? = nil,
-            secondAmount: Decimal? = nil,
-            duration: Decimal? = nil,
-            id: String? = nil,
-            idPumpEvent: String? = nil,
-            isFPU: Bool? = nil,
-            fpuID: String? = nil,
-            note: String? = nil,
-            isSMB: Bool? = nil,
-            isExternal: Bool? = nil
-        ) {
-            self.units = units
-            self.type = type
-            self.date = date
-            self.amount = amount
-            self.secondAmount = secondAmount
-            self.duration = duration
-            self.id = id ?? UUID().uuidString
-            self.idPumpEvent = idPumpEvent
-            self.isFPU = isFPU
-            self.fpuID = fpuID
-            self.note = note
-            self.isSMB = isSMB
-            self.isExternal = isExternal
-        }
-
-        static func == (lhs: Treatment, rhs: Treatment) -> Bool {
-            lhs.id == rhs.id
-        }
-
-        func hash(into hasher: inout Hasher) {
-            hasher.combine(id)
-        }
-
-        var amountText: String {
-            guard let amount = amount else {
-                return ""
-            }
-
-            if amount == 0, duration == 0 {
-                return "Cancel temp"
-            }
-
-            switch type {
-            case .carbs:
-                return numberFormatter
-                    .string(from: amount as NSNumber)! + String(localized: " g", comment: "gram of carbs")
-            case .fpus:
-                return numberFormatter
-                    .string(from: amount as NSNumber)! + String(localized: " g", comment: "gram of carb equilvalents")
-            case .bolus:
-                var bolusText = " "
-                if isSMB ?? false {}
-                else if isExternal ?? false {
-                    bolusText += String(localized: "External", comment: "External Insulin")
-                } else {
-                    bolusText += String(localized: "Manual", comment: "Manual Bolus")
-                }
-
-                return numberFormatter
-                    .string(from: amount as NSNumber)! + String(localized: " U", comment: "Insulin unit") + bolusText
-            case .tempBasal:
-                return numberFormatter
-                    .string(from: amount as NSNumber)! + String(localized: " U/hr", comment: "Unit insulin per hour")
-            case .tempTarget:
-                var converted = amount
-                if units == .mmolL {
-                    converted = converted.asMmolL
-                }
-
-                guard var secondAmount = secondAmount else {
-                    return numberFormatter.string(from: converted as NSNumber)! + " \(units.rawValue)"
-                }
-                if units == .mmolL {
-                    secondAmount = secondAmount.asMmolL
-                }
-
-                return tempTargetFormater.string(from: converted as NSNumber)! + " - " + tempTargetFormater
-                    .string(from: secondAmount as NSNumber)! + " \(units.rawValue)"
-            case .resume,
-                 .suspend:
-                return type.name
-            }
-        }
-
-        var color: Color {
-            switch type {
-            case .carbs:
-                return .loopYellow
-            case .fpus:
-                return .orange.opacity(0.5)
-            case .bolus:
-                return Color.insulin
-            case .tempBasal:
-                return Color.insulin.opacity(0.4)
-            case .resume,
-                 .suspend,
-                 .tempTarget:
-                return .loopGray
-            }
-        }
-
-        var durationText: String? {
-            guard let duration = duration, duration > 0 else {
-                return nil
-            }
-            return numberFormatter.string(from: duration as NSNumber)! + " min"
-        }
-    }
-
-    class Glucose: Identifiable, Hashable, Equatable {
-        static func == (lhs: History.Glucose, rhs: History.Glucose) -> Bool {
-            lhs.glucose == rhs.glucose
-        }
-
-        let glucose: BloodGlucose
-
-        init(glucose: BloodGlucose) {
-            self.glucose = glucose
-        }
-
-        var id: String { glucose.id }
-    }
 }
 
 protocol HistoryProvider: Provider {
@@ -248,4 +91,6 @@ protocol HistoryProvider: Provider {
     func deleteGlucoseFromHealth(withSyncID id: String)
     func deleteMealDataFromHealth(byID id: String, sampleType: HKSampleType)
     func deleteInsulinFromHealth(withSyncID id: String)
+    func deleteInsulinFromTidepool(withSyncId id: String, amount: Decimal, at: Date)
+    func deleteCarbsFromTidepool(withSyncId id: UUID, carbs: Decimal, at: Date, enteredBy: String)
 }

+ 65 - 0
Trio/Sources/Modules/History/HistoryDeletionTarget.swift

@@ -0,0 +1,65 @@
+import CoreData
+import Foundation
+
+extension History {
+    enum DeletionTarget: Identifiable {
+        case glucose(GlucoseStored)
+        case insulin(PumpEventStored)
+        case carbs(CarbEntryStored)
+
+        var id: NSManagedObjectID {
+            switch self {
+            case let .glucose(glucose): return glucose.objectID
+            case let .insulin(pumpEvent): return pumpEvent.objectID
+            case let .carbs(carbEntry): return carbEntry.objectID
+            }
+        }
+
+        func title(units _: GlucoseUnits) -> String {
+            switch self {
+            case .glucose:
+                return String(localized: "Delete Glucose?", comment: "Alert title for deleting glucose")
+            case .insulin:
+                return String(localized: "Delete Insulin?", comment: "Alert title for deleting insulin")
+            case let .carbs(carbEntry):
+                if carbEntry.fpuID == nil {
+                    return String(localized: "Delete Carbs?", comment: "Alert title for deleting carbs")
+                }
+                return carbEntry.isFPU
+                    ? String(localized: "Delete Carbs Equivalents?", comment: "Alert title for deleting carb equivalents")
+                    : String(localized: "Delete Carbs?", comment: "Alert title for deleting carbs")
+            }
+        }
+
+        func message(units: GlucoseUnits) -> String? {
+            switch self {
+            case let .glucose(glucose):
+                let glucoseToDisplay = units == .mgdL
+                    ? glucose.glucose.description
+                    : Int(glucose.glucose).formattedAsMmolL
+                return Formatter.dateFormatter.string(from: glucose.date ?? Date())
+                    + ", " + glucoseToDisplay + " " + units.rawValue
+            case let .insulin(pumpEvent):
+                var text = Formatter.dateFormatter.string(from: pumpEvent.timestamp ?? Date())
+                    + ", "
+                    + (Formatter.decimalFormatterWithThreeFractionDigits.string(from: pumpEvent.bolus?.amount ?? 0) ?? "0")
+                    + String(localized: " U", comment: "Insulin unit")
+                if let bolus = pumpEvent.bolus, bolus.isSMB {
+                    text += String(localized: " SMB", comment: "Super Micro Bolus indicator in delete alert")
+                }
+                return text
+            case let .carbs(carbEntry):
+                if carbEntry.fpuID == nil {
+                    return Formatter.dateFormatter.string(from: carbEntry.date ?? Date())
+                        + ", "
+                        + (Formatter.decimalFormatterWithTwoFractionDigits.string(for: carbEntry.carbs) ?? "0")
+                        + String(localized: " g", comment: "gram of carbs")
+                }
+                return String(
+                    localized: "All FPUs and the carbs of the meal will be deleted.",
+                    comment: "Alert message for meal deletion"
+                )
+            }
+        }
+    }
+}

+ 291 - 0
Trio/Sources/Modules/History/HistoryStateModel+Deletion/HistoryStateModel+CarbEditing.swift

@@ -0,0 +1,291 @@
+import CoreData
+import Foundation
+
+extension History.StateModel {
+    // 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 {
+            do {
+                // 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
+                try await deleteOldEntries(
+                    treatmentObjectID,
+                    originalEntry: originalEntry,
+                    newCarbs: newCarbs,
+                    newFat: newFat,
+                    newProtein: newProtein,
+                    newNote: newNote
+                )
+
+                try await createNewEntries(
+                    originalDate: newDate,
+                    newCarbs: newCarbs,
+                    newFat: newFat,
+                    newProtein: newProtein,
+                    newNote: newNote
+                )
+
+                await syncWithServices()
+
+                // Perform a determine basal sync to update cob
+                try await apsManager.determineBasalSync()
+
+            } catch {
+                debug(.default, "\(DebuggingIdentifiers.failed) failed to update entry: \(error)")
+            }
+        }
+    }
+
+    private func createNewEntries(
+        originalDate: Date,
+        newCarbs: Decimal,
+        newFat: Decimal,
+        newProtein: Decimal,
+        newNote: String
+    ) async throws {
+        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
+        try 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 throws {
+        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
+            try 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
+            try await deleteCarbs(treatmentObjectID, isFpuOrComplexMeal: true)
+
+        } else {
+            // Delete just the carb entry since there are no carb equivalents
+            // Use NSManagedObjectID
+            try 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)")
+                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
+            }
+        }
+    }
+}

+ 119 - 0
Trio/Sources/Modules/History/HistoryStateModel+Deletion/HistoryStateModel+Carbs.swift

@@ -0,0 +1,119 @@
+import CoreData
+import Foundation
+import HealthKit
+
+extension History.StateModel {
+    // Carb and FPU deletion from history
+    /// - **Parameter**: NSManagedObjectID to be able to transfer the object safely from one thread to another thread
+    func invokeCarbDeletionTask(_ treatmentObjectID: NSManagedObjectID, isFpuOrComplexMeal: Bool = false) {
+        Task {
+            do {
+                /// Set the variables that control the CustomProgressView BEFORE the actual deletion
+                /// otherwise the determineBasalSync gets executed first, sets waitForSuggestion to false and afterwards waitForSuggestion is set in this function to true, leading to an endless animation
+                await MainActor.run {
+                    carbEntryDeleted = true
+                    waitForSuggestion = true
+                }
+
+                try await deleteCarbs(treatmentObjectID, isFpuOrComplexMeal: isFpuOrComplexMeal)
+
+            } catch {
+                debug(.default, "\(DebuggingIdentifiers.failed) Failed to delete carbs: \(error)")
+                await MainActor.run {
+                    carbEntryDeleted = false
+                    waitForSuggestion = false
+                }
+            }
+        }
+    }
+
+    func deleteCarbs(_ treatmentObjectID: NSManagedObjectID, isFpuOrComplexMeal: Bool = false) async throws {
+        // Delete from Nightscout/Apple Health/Tidepool
+        await deleteFromServices(treatmentObjectID, isFPUDeletion: isFpuOrComplexMeal)
+
+        // Delete carbs from Core Data
+        await carbsStorage.deleteCarbsEntryStored(treatmentObjectID)
+
+        // Perform a determine basal sync to update cob
+        try await apsManager.determineBasalSync()
+    }
+
+    /// 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()
+        taskContext.name = "deleteContext"
+        taskContext.transactionAuthor = "deleteCarbsFromServices"
+
+        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 entries from all services
+        await taskContext.perform {
+            do {
+                carbEntry = try taskContext.existingObject(with: objectIDToDelete) as? CarbEntryStored
+                guard let carbEntry = carbEntry else {
+                    debugPrint("Carb entry for deletion not found. \(DebuggingIdentifiers.failed)")
+                    return
+                }
+
+                // Delete FPU related entries if they exist
+                if let fpuID = carbEntry.fpuID {
+                    // Delete Fat and Protein entries from Nightscout
+                    self.provider.deleteCarbsFromNightscout(withID: fpuID.uuidString)
+
+                    // Delete Fat and Protein entries from Apple Health
+                    let healthObjectsToDelete: [HKSampleType?] = [
+                        AppleHealthConfig.healthFatObject,
+                        AppleHealthConfig.healthProteinObject
+                    ]
+
+                    for sampleType in healthObjectsToDelete {
+                        if let validSampleType = sampleType {
+                            self.provider.deleteMealDataFromHealth(byID: fpuID.uuidString, sampleType: validSampleType)
+                        }
+                    }
+                }
+
+                // Delete carb entries if they exist
+                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)
+                    }
+
+                    self.provider.deleteCarbsFromTidepool(
+                        withSyncId: id,
+                        carbs: Decimal(carbEntry.carbs),
+                        at: entryDate,
+                        enteredBy: CarbsEntry.local
+                    )
+                }
+            } catch {
+                debugPrint("\(DebuggingIdentifiers.failed) Error deleting entries: \(error)")
+            }
+        }
+    }
+}

+ 55 - 0
Trio/Sources/Modules/History/HistoryStateModel+Deletion/HistoryStateModel+Glucose.swift

@@ -0,0 +1,55 @@
+import CoreData
+import Foundation
+
+extension History.StateModel {
+    /// 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) {
+        Task {
+            await deleteGlucose(treatmentObjectID)
+        }
+    }
+
+    func deleteGlucose(_ treatmentObjectID: NSManagedObjectID) async {
+        // Delete from Apple Health/Tidepool
+        await deleteGlucoseFromServices(treatmentObjectID)
+
+        // Delete from Core Data
+        await glucoseStorage.deleteGlucose(treatmentObjectID)
+    }
+
+    func deleteGlucoseFromServices(_ treatmentObjectID: NSManagedObjectID) async {
+        let taskContext = CoreDataStack.shared.newTaskContext()
+        taskContext.name = "deleteContext"
+        taskContext.transactionAuthor = "deleteGlucoseFromServices"
+
+        await taskContext.perform {
+            do {
+                let result = try taskContext.existingObject(with: treatmentObjectID) as? GlucoseStored
+
+                guard let glucoseToDelete = result else {
+                    debugPrint("Data Table State: \(#function) \(DebuggingIdentifiers.failed) glucose not found in core data")
+                    return
+                }
+
+                // Delete from Nightscout
+                if let id = glucoseToDelete.id?.uuidString, let date = glucoseToDelete.date {
+                    self.provider.deleteGlucoseFromNightscout(withID: id, withDate: date)
+                }
+
+                // Delete from Apple Health
+                if let id = glucoseToDelete.id?.uuidString {
+                    self.provider.deleteGlucoseFromHealth(withSyncID: id)
+                }
+
+                debugPrint(
+                    "\(#file) \(#function) \(DebuggingIdentifiers.succeeded) deleted glucose from remote service(s) (Nightscout, Apple Health, Tidepool)"
+                )
+            } catch {
+                debugPrint(
+                    "\(#file) \(#function) \(DebuggingIdentifiers.failed) error while deleting glucose remote service(s) (Nightscout, Apple Health, Tidepool) with error: \(error)"
+                )
+            }
+        }
+    }
+}

+ 85 - 0
Trio/Sources/Modules/History/HistoryStateModel+Deletion/HistoryStateModel+Insulin.swift

@@ -0,0 +1,85 @@
+import CoreData
+import Foundation
+
+extension History.StateModel {
+    // Insulin deletion from history
+    /// - **Parameter**: NSManagedObjectID to be able to transfer the object safely from one thread to another thread
+    func invokeInsulinDeletionTask(_ treatmentObjectID: NSManagedObjectID) {
+        Task {
+            do {
+                try await invokeInsulinDeletion(treatmentObjectID)
+            } catch {
+                debug(.default, "\(DebuggingIdentifiers.failed) Failed to delete insulin entry: \(error)")
+            }
+        }
+    }
+
+    func invokeInsulinDeletion(_ treatmentObjectID: NSManagedObjectID) async throws {
+        do {
+            let authenticated = try await unlockmanager.unlock()
+
+            guard authenticated else {
+                debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Authentication Error")
+                return
+            }
+
+            /// Set variables that control the CustomProgressView to true AFTER the authentication and BEFORE the actual determineBasalSync
+            /// We definitely need to set the variables BEFORE the actual sync
+            /// otherwise the determineBasalSync gets executed first, sets waitForSuggestion to false and afterwards waitForSuggestion is set in this function to true, leading to an endless animation
+            /// But we also want it AFTER the authentication
+            /// otherwise the animation would pop up even before the authentication prompt appears to the user
+            await MainActor.run {
+                insulinEntryDeleted = true
+                waitForSuggestion = true
+            }
+
+            // Delete from remote service(s) (i.e. Nightscout, Apple Health, Tidepool)
+            await deleteInsulinFromServices(with: treatmentObjectID)
+
+            // Delete from Core Data
+            await CoreDataStack.shared.deleteObject(identifiedBy: treatmentObjectID)
+
+            // Perform a determine basal sync to update iob
+            try await apsManager.determineBasalSync()
+        } catch {
+            debugPrint(
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Error while Insulin Deletion Task: \(error)"
+            )
+            await MainActor.run {
+                insulinEntryDeleted = false
+                waitForSuggestion = false
+            }
+        }
+    }
+
+    func deleteInsulinFromServices(with treatmentObjectID: NSManagedObjectID) async {
+        let taskContext = CoreDataStack.shared.newTaskContext()
+        taskContext.name = "deleteContext"
+        taskContext.transactionAuthor = "deleteInsulinFromServices"
+
+        await taskContext.perform {
+            do {
+                guard let treatmentToDelete = try taskContext.existingObject(with: treatmentObjectID) as? PumpEventStored
+                else {
+                    debug(.default, "Could not cast the object to PumpEventStored")
+                    return
+                }
+
+                if let id = treatmentToDelete.id, let timestamp = treatmentToDelete.timestamp,
+                   let bolus = treatmentToDelete.bolus, let bolusAmount = bolus.amount
+                {
+                    self.provider.deleteInsulinFromNightscout(withID: id)
+                    self.provider.deleteInsulinFromHealth(withSyncID: id)
+                    self.provider.deleteInsulinFromTidepool(withSyncId: id, amount: bolusAmount as Decimal, at: timestamp)
+                }
+
+                taskContext.delete(treatmentToDelete)
+                try taskContext.save()
+
+                debug(.default, "Successfully deleted the treatment object.")
+            } catch {
+                debug(.default, "Failed to delete the treatment object: \(error)")
+            }
+        }
+    }
+}

+ 0 - 533
Trio/Sources/Modules/History/HistoryStateModel.swift

@@ -42,57 +42,6 @@ extension History {
             glucoseStorage.isGlucoseDataFresh(glucoseDate)
         }
 
-        /// 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) {
-            Task {
-                await deleteGlucose(treatmentObjectID)
-            }
-        }
-
-        func deleteGlucose(_ treatmentObjectID: NSManagedObjectID) async {
-            // Delete from Apple Health/Tidepool
-            await deleteGlucoseFromServices(treatmentObjectID)
-
-            // Delete from Core Data
-            await glucoseStorage.deleteGlucose(treatmentObjectID)
-        }
-
-        func deleteGlucoseFromServices(_ treatmentObjectID: NSManagedObjectID) async {
-            let taskContext = CoreDataStack.shared.newTaskContext()
-            taskContext.name = "deleteContext"
-            taskContext.transactionAuthor = "deleteGlucoseFromServices"
-
-            await taskContext.perform {
-                do {
-                    let result = try taskContext.existingObject(with: treatmentObjectID) as? GlucoseStored
-
-                    guard let glucoseToDelete = result else {
-                        debugPrint("Data Table State: \(#function) \(DebuggingIdentifiers.failed) glucose not found in core data")
-                        return
-                    }
-
-                    // Delete from Nightscout
-                    if let id = glucoseToDelete.id?.uuidString, let date = glucoseToDelete.date {
-                        self.provider.deleteGlucoseFromNightscout(withID: id, withDate: date)
-                    }
-
-                    // Delete from Apple Health
-                    if let id = glucoseToDelete.id?.uuidString {
-                        self.provider.deleteGlucoseFromHealth(withSyncID: id)
-                    }
-
-                    debugPrint(
-                        "\(#file) \(#function) \(DebuggingIdentifiers.succeeded) deleted glucose from remote service(s) (Nightscout, Apple Health, Tidepool)"
-                    )
-                } catch {
-                    debugPrint(
-                        "\(#file) \(#function) \(DebuggingIdentifiers.failed) error while deleting glucose remote service(s) (Nightscout, Apple Health, Tidepool) with error: \(error)"
-                    )
-                }
-            }
-        }
-
         func addManualGlucose() {
             // Always save value in mg/dL
             let glucose = units == .mmolL ? manualGlucose.asMgdL : manualGlucose
@@ -100,488 +49,6 @@ extension History {
 
             glucoseStorage.addManualGlucose(glucose: glucoseAsInt)
         }
-
-        // Carb and FPU deletion from history
-        /// - **Parameter**: NSManagedObjectID to be able to transfer the object safely from one thread to another thread
-        func invokeCarbDeletionTask(_ treatmentObjectID: NSManagedObjectID, isFpuOrComplexMeal: Bool = false) {
-            Task {
-                do {
-                    /// Set the variables that control the CustomProgressView BEFORE the actual deletion
-                    /// otherwise the determineBasalSync gets executed first, sets waitForSuggestion to false and afterwards waitForSuggestion is set in this function to true, leading to an endless animation
-                    await MainActor.run {
-                        carbEntryDeleted = true
-                        waitForSuggestion = true
-                    }
-
-                    try await deleteCarbs(treatmentObjectID, isFpuOrComplexMeal: isFpuOrComplexMeal)
-
-                } catch {
-                    debug(.default, "\(DebuggingIdentifiers.failed) Failed to delete carbs: \(error)")
-                    await MainActor.run {
-                        carbEntryDeleted = false
-                        waitForSuggestion = false
-                    }
-                }
-            }
-        }
-
-        func deleteCarbs(_ treatmentObjectID: NSManagedObjectID, isFpuOrComplexMeal: Bool = false) async throws {
-            // Delete from Nightscout/Apple Health/Tidepool
-            await deleteFromServices(treatmentObjectID, isFPUDeletion: isFpuOrComplexMeal)
-
-            // Delete carbs from Core Data
-            await carbsStorage.deleteCarbsEntryStored(treatmentObjectID)
-
-            // Perform a determine basal sync to update cob
-            try await apsManager.determineBasalSync()
-        }
-
-        /// 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()
-            taskContext.name = "deleteContext"
-            taskContext.transactionAuthor = "deleteCarbsFromServices"
-
-            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 entries from all services
-            await taskContext.perform {
-                do {
-                    carbEntry = try taskContext.existingObject(with: objectIDToDelete) as? CarbEntryStored
-                    guard let carbEntry = carbEntry else {
-                        debugPrint("Carb entry for deletion not found. \(DebuggingIdentifiers.failed)")
-                        return
-                    }
-
-                    // Delete FPU related entries if they exist
-                    if let fpuID = carbEntry.fpuID {
-                        // Delete Fat and Protein entries from Nightscout
-                        self.provider.deleteCarbsFromNightscout(withID: fpuID.uuidString)
-
-                        // Delete Fat and Protein entries from Apple Health
-                        let healthObjectsToDelete: [HKSampleType?] = [
-                            AppleHealthConfig.healthFatObject,
-                            AppleHealthConfig.healthProteinObject
-                        ]
-
-                        for sampleType in healthObjectsToDelete {
-                            if let validSampleType = sampleType {
-                                self.provider.deleteMealDataFromHealth(byID: fpuID.uuidString, sampleType: validSampleType)
-                            }
-                        }
-                    }
-
-                    // Delete carb entries if they exist
-                    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)
-                        }
-
-                        self.provider.deleteCarbsFromTidepool(
-                            withSyncId: id,
-                            carbs: Decimal(carbEntry.carbs),
-                            at: entryDate,
-                            enteredBy: CarbsEntry.local
-                        )
-                    }
-                } catch {
-                    debugPrint("\(DebuggingIdentifiers.failed) Error deleting entries: \(error)")
-                }
-            }
-        }
-
-        // Insulin deletion from history
-        /// - **Parameter**: NSManagedObjectID to be able to transfer the object safely from one thread to another thread
-        func invokeInsulinDeletionTask(_ treatmentObjectID: NSManagedObjectID) {
-            Task {
-                do {
-                    try await invokeInsulinDeletion(treatmentObjectID)
-                } catch {
-                    debug(.default, "\(DebuggingIdentifiers.failed) Failed to delete insulin entry: \(error)")
-                }
-            }
-        }
-
-        func invokeInsulinDeletion(_ treatmentObjectID: NSManagedObjectID) async throws {
-            do {
-                let authenticated = try await unlockmanager.unlock()
-
-                guard authenticated else {
-                    debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Authentication Error")
-                    return
-                }
-
-                /// Set variables that control the CustomProgressView to true AFTER the authentication and BEFORE the actual determineBasalSync
-                /// We definitely need to set the variables BEFORE the actual sync
-                /// otherwise the determineBasalSync gets executed first, sets waitForSuggestion to false and afterwards waitForSuggestion is set in this function to true, leading to an endless animation
-                /// But we also want it AFTER the authentication
-                /// otherwise the animation would pop up even before the authentication prompt appears to the user
-                await MainActor.run {
-                    insulinEntryDeleted = true
-                    waitForSuggestion = true
-                }
-
-                // Delete from remote service(s) (i.e. Nightscout, Apple Health, Tidepool)
-                await deleteInsulinFromServices(with: treatmentObjectID)
-
-                // Delete from Core Data
-                await CoreDataStack.shared.deleteObject(identifiedBy: treatmentObjectID)
-
-                // Perform a determine basal sync to update iob
-                try await apsManager.determineBasalSync()
-            } catch {
-                debugPrint(
-                    "\(DebuggingIdentifiers.failed) \(#file) \(#function) Error while Insulin Deletion Task: \(error)"
-                )
-                await MainActor.run {
-                    insulinEntryDeleted = false
-                    waitForSuggestion = false
-                }
-            }
-        }
-
-        func deleteInsulinFromServices(with treatmentObjectID: NSManagedObjectID) async {
-            let taskContext = CoreDataStack.shared.newTaskContext()
-            taskContext.name = "deleteContext"
-            taskContext.transactionAuthor = "deleteInsulinFromServices"
-
-            await taskContext.perform {
-                do {
-                    guard let treatmentToDelete = try taskContext.existingObject(with: treatmentObjectID) as? PumpEventStored
-                    else {
-                        debug(.default, "Could not cast the object to PumpEventStored")
-                        return
-                    }
-
-                    if let id = treatmentToDelete.id, let timestamp = treatmentToDelete.timestamp,
-                       let bolus = treatmentToDelete.bolus, let bolusAmount = bolus.amount
-                    {
-                        self.provider.deleteInsulinFromNightscout(withID: id)
-                        self.provider.deleteInsulinFromHealth(withSyncID: id)
-                        self.provider.deleteInsulinFromTidepool(withSyncId: id, amount: bolusAmount as Decimal, at: timestamp)
-                    }
-
-                    taskContext.delete(treatmentToDelete)
-                    try taskContext.save()
-
-                    debug(.default, "Successfully deleted the treatment object.")
-                } catch {
-                    debug(.default, "Failed to delete the treatment object: \(error)")
-                }
-            }
-        }
-
-        // 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 {
-                do {
-                    // 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
-                    try await deleteOldEntries(
-                        treatmentObjectID,
-                        originalEntry: originalEntry,
-                        newCarbs: newCarbs,
-                        newFat: newFat,
-                        newProtein: newProtein,
-                        newNote: newNote
-                    )
-
-                    try await createNewEntries(
-                        originalDate: newDate,
-                        newCarbs: newCarbs,
-                        newFat: newFat,
-                        newProtein: newProtein,
-                        newNote: newNote
-                    )
-
-                    await syncWithServices()
-
-                    // Perform a determine basal sync to update cob
-                    try await apsManager.determineBasalSync()
-
-                } catch {
-                    debug(.default, "\(DebuggingIdentifiers.failed) failed to update entry: \(error)")
-                }
-            }
-        }
-
-        private func createNewEntries(
-            originalDate: Date,
-            newCarbs: Decimal,
-            newFat: Decimal,
-            newProtein: Decimal,
-            newNote: String
-        ) async throws {
-            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
-            try 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 throws {
-            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
-                try 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
-                try await deleteCarbs(treatmentObjectID, isFpuOrComplexMeal: true)
-
-            } else {
-                // Delete just the carb entry since there are no carb equivalents
-                // Use NSManagedObjectID
-                try 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)")
-                    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
-                }
-            }
-        }
     }
 }
 

+ 72 - 0
Trio/Sources/Modules/History/View/HistoryRootView+AddGlucose.swift

@@ -0,0 +1,72 @@
+import SwiftUI
+
+extension History.RootView {
+    var manualGlucoseFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        if state.units == .mgdL {
+            formatter.maximumIntegerDigits = 3
+            formatter.maximumFractionDigits = 0
+        } else {
+            formatter.maximumIntegerDigits = 2
+            formatter.minimumFractionDigits = 0
+            formatter.maximumFractionDigits = 1
+        }
+        formatter.roundingMode = .halfUp
+        return formatter
+    }
+
+    @ViewBuilder func addGlucoseView() -> some View {
+        let limitLow: Decimal = state.units == .mgdL ? Decimal(14) : 14.asMmolL
+        let limitHigh: Decimal = state.units == .mgdL ? Decimal(720) : 720.asMmolL
+
+        NavigationView {
+            VStack {
+                Form {
+                    Section {
+                        HStack {
+                            Text("New Glucose")
+                            TextFieldWithToolBar(
+                                text: $state.manualGlucose,
+                                placeholder: " ... ",
+                                keyboardType: state.units == .mgdL ? .numberPad : .decimalPad,
+                                numberFormatter: manualGlucoseFormatter,
+                                initialFocus: true,
+                                unitsText: state.units.rawValue
+                            )
+                        }
+                    }.listRowBackground(Color.chart)
+
+                    Section {
+                        HStack {
+                            Button {
+                                state.addManualGlucose()
+                                isAmountUnconfirmed = false
+                                showManualGlucose = false
+                                state.mode = .glucose
+                            }
+                            label: { Text("Save") }
+                                .frame(maxWidth: .infinity, alignment: .center)
+                                .disabled(state.manualGlucose < limitLow || state.manualGlucose > limitHigh)
+                        }
+                    }
+                    .listRowBackground(
+                        state.manualGlucose < limitLow || state
+                            .manualGlucose > limitHigh ? Color(.systemGray4) : Color(.systemBlue)
+                    )
+                    .tint(.white)
+                }.scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
+            }
+            .onAppear(perform: configureView)
+            .navigationTitle("Add Glucose")
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .topBarLeading) {
+                    Button("Close") {
+                        showManualGlucose = false
+                    }
+                }
+            }
+        }
+    }
+}

+ 133 - 0
Trio/Sources/Modules/History/View/HistoryRootView+Adjustments.swift

@@ -0,0 +1,133 @@
+import CoreData
+import SwiftUI
+
+extension History.RootView {
+    var adjustmentsList: some View {
+        List {
+            HStack {
+                Text("Adjustment").foregroundStyle(.secondary)
+                Spacer()
+            }
+            if !combinedAdjustments.isEmpty {
+                ForEach(combinedAdjustments) { item in
+                    adjustmentView(for: item)
+                }
+            } else {
+                ContentUnavailableView(
+                    String(localized: "No data."),
+                    systemImage: "clock.arrow.2.circlepath"
+                )
+            }
+        }
+        .listRowBackground(Color.chart)
+    }
+
+    fileprivate var combinedAdjustments: [AdjustmentItem] {
+        let overrides = overrideRunStored.map { override -> AdjustmentItem in
+            AdjustmentItem(
+                id: override.objectID,
+                name: override.name ?? String(localized: "Override"),
+                startDate: override.startDate ?? Date(),
+                endDate: override.endDate ?? Date(),
+                target: override.target?.decimalValue,
+                type: .override
+            )
+        }
+
+        let tempTargets = tempTargetRunStored.map { tempTarget -> AdjustmentItem in
+            AdjustmentItem(
+                id: tempTarget.objectID,
+                name: tempTarget.name ?? String(localized: "Temp Target"),
+                startDate: tempTarget.startDate ?? Date(),
+                endDate: tempTarget.endDate ?? Date(),
+                target: tempTarget.target?.decimalValue,
+                type: .tempTarget
+            )
+        }
+
+        let combined = overrides + tempTargets
+        return combined.sorted {
+            if $0.startDate == $1.startDate {
+                return $0.endDate > $1.endDate
+            }
+            return $0.startDate > $1.startDate
+        }
+    }
+
+    fileprivate struct AdjustmentItem: Identifiable {
+        let id: NSManagedObjectID
+        let name: String
+        let startDate: Date
+        let endDate: Date
+        let target: Decimal?
+        let type: AdjustmentType
+    }
+
+    fileprivate enum AdjustmentType {
+        case override
+        case tempTarget
+
+        var symbolName: String {
+            switch self {
+            case .override:
+                return "clock.arrow.2.circlepath"
+            case .tempTarget:
+                return "target"
+            }
+        }
+
+        var symbolColor: Color {
+            switch self {
+            case .override:
+                return .orange
+            case .tempTarget:
+                return .blue
+            }
+        }
+    }
+
+    @ViewBuilder fileprivate func adjustmentView(for item: AdjustmentItem) -> some View {
+        let formattedDates =
+            "\(Formatter.dateFormatter.string(from: item.startDate)) - \(Formatter.dateFormatter.string(from: item.endDate))"
+
+        let targetDescription: String = {
+            guard let target = item.target, target != 0 else {
+                return ""
+            }
+            return "\(state.units == .mgdL ? target : target.asMmolL) \(state.units.rawValue)"
+        }()
+
+        let labels: [String] = [
+            targetDescription,
+            formattedDates
+        ].filter { !$0.isEmpty }
+
+        ZStack(alignment: .trailing) {
+            HStack {
+                VStack(alignment: .leading) {
+                    HStack {
+                        Image(systemName: item.type.symbolName)
+                            .foregroundStyle(item.type == .override ? Color.purple : Color.green)
+                        Text(item.name)
+                            .font(.headline)
+                        Spacer()
+                    }
+                    HStack(spacing: 5) {
+                        ForEach(labels, id: \.self) { label in
+                            Text(label)
+                            if label != labels.last {
+                                Divider()
+                            }
+                        }
+                        Spacer()
+                    }
+                    .padding(.top, 2)
+                    .foregroundColor(.secondary)
+                    .font(.caption)
+                }
+                .contentShape(Rectangle())
+            }
+        }
+        .padding(.vertical, 8)
+    }
+}

+ 44 - 0
Trio/Sources/Modules/History/View/HistoryRootView+Confirmations.swift

@@ -0,0 +1,44 @@
+import SwiftUI
+
+extension History.RootView {
+    func requestDelete(_ target: History.DeletionTarget) {
+        deletionTarget = target
+    }
+
+    @ViewBuilder func historyConfirmations(_ content: some View) -> some View {
+        content
+            .confirmationDialog(
+                deletionTarget?.title(units: state.units) ?? "",
+                isPresented: Binding(
+                    get: { deletionTarget != nil },
+                    set: { if !$0 { deletionTarget = nil } }
+                ),
+                titleVisibility: .visible,
+                presenting: deletionTarget
+            ) { target in
+                Button("Delete", role: .destructive) {
+                    switch target {
+                    case let .glucose(glucose):
+                        state.invokeGlucoseDeletionTask(glucose.objectID)
+                    case let .insulin(pumpEvent):
+                        state.invokeInsulinDeletionTask(pumpEvent.objectID)
+                    case let .carbs(carbEntry):
+                        state.invokeCarbDeletionTask(
+                            carbEntry.objectID,
+                            isFpuOrComplexMeal: carbEntry.isFPU || carbEntry.fat > 0 || carbEntry.protein > 0
+                        )
+                    }
+                }
+                Button("Cancel", role: .cancel) {}
+            } message: { target in
+                if let message = target.message(units: state.units) {
+                    Text(message)
+                }
+            }
+            .alert("Error", isPresented: $showErrorAlert) {
+                Button("OK", role: .cancel) {}
+            } message: {
+                Text(errorMessage)
+            }
+    }
+}

+ 103 - 0
Trio/Sources/Modules/History/View/HistoryRootView+Filters.swift

@@ -0,0 +1,103 @@
+import SwiftUI
+
+extension History.RootView {
+    var filterTreatmentsButton: some View {
+        Button(action: {
+            showTreatmentTypeFilter.toggle()
+        }) {
+            HStack {
+                Text("Filter")
+                Image(
+                    systemName: selectedTreatmentTypes.count == History.TreatmentType.allCases.count
+                        ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill"
+                )
+                if selectedTreatmentTypes.count < History.TreatmentType.allCases.count {
+                    Text(verbatim: "(\(selectedTreatmentTypes.count)/\(History.TreatmentType.allCases.count))")
+                }
+            }.foregroundColor(Color.accentColor)
+        }
+        .popover(isPresented: $showTreatmentTypeFilter, arrowEdge: .top) {
+            VStack(alignment: .leading, spacing: 20) {
+                Button(action: {
+                    if selectedTreatmentTypes.count == History.TreatmentType.allCases.count {
+                        // Deselect all - keep at least one selected
+                        selectedTreatmentTypes = []
+                    } else {
+                        // Select all
+                        selectedTreatmentTypes = Set(History.TreatmentType.allCases)
+                    }
+                }) {
+                    HStack(spacing: 20) {
+                        Image(
+                            systemName: selectedTreatmentTypes.count == History.TreatmentType.allCases.count
+                                ? "checkmark.square.fill" : "square"
+                        )
+                        .frame(width: 20)
+                        .foregroundColor(Color.accentColor)
+                        Text(
+                            selectedTreatmentTypes.count == History.TreatmentType.allCases
+                                .count ? String(localized: "Deselect All") : String(localized: "Select All")
+                        )
+                        .foregroundColor(Color.primary)
+                    }.padding(4)
+                }
+                .buttonStyle(.borderless)
+
+                Divider()
+
+                ForEach(History.TreatmentType.allCases, id: \.rawValue) { treatmentType in
+                    Button(action: {
+                        toggleTreatmentType(treatmentType)
+                    }) {
+                        HStack(spacing: 20) {
+                            Image(
+                                systemName: selectedTreatmentTypes
+                                    .contains(treatmentType) ? "checkmark.square.fill" : "square"
+                            )
+                            .frame(width: 20)
+                            .foregroundColor(Color.accentColor)
+                            Text(treatmentType.displayName)
+                                .foregroundColor(Color.primary)
+                        }.padding(4)
+                    }
+                    .buttonStyle(.borderless)
+                }
+
+                Divider()
+
+                Button("Done") {
+                    showTreatmentTypeFilter = false
+                }
+                .frame(maxWidth: .infinity)
+                .buttonStyle(.borderless)
+            }
+            .padding()
+            .presentationCompactAdaptation(.popover)
+            .background(Color.chart)
+        }
+    }
+
+    var filterFutureEntriesButton: some View {
+        Button(
+            action: {
+                showFutureEntries.toggle()
+            },
+            label: {
+                HStack {
+                    Text(showFutureEntries ? String(localized: "Hide Future") : String(localized: "Show Future"))
+                        .foregroundColor(Color.accentColor)
+                    Image(systemName: showFutureEntries ? "eye.slash" : "eye")
+                        .foregroundColor(Color.accentColor)
+                }
+            }
+        ).buttonStyle(.borderless)
+    }
+
+    func toggleTreatmentType(_ type: History.TreatmentType) {
+        if selectedTreatmentTypes.contains(type) {
+            selectedTreatmentTypes.remove(type)
+        } else {
+            selectedTreatmentTypes.insert(type)
+        }
+    }
+}

+ 79 - 0
Trio/Sources/Modules/History/View/HistoryRootView+Glucose.swift

@@ -0,0 +1,79 @@
+import CoreData
+import SwiftUI
+
+extension History.RootView {
+    var glucoseList: some View {
+        List {
+            HStack {
+                Text("Values")
+                Spacer()
+                Text("Time")
+            }.foregroundStyle(.secondary)
+
+            if !glucoseStored.isEmpty {
+                ForEach(glucoseStored) { glucose in
+                    HStack {
+                        Text(formatGlucose(Decimal(glucose.glucose), isManual: glucose.isManual))
+
+                        /// check for manual glucose
+                        if glucose.isManual {
+                            Image(systemName: "drop.fill").symbolRenderingMode(.monochrome).foregroundStyle(.red)
+                        } else {
+                            Text("\(glucose.directionEnum?.symbol ?? "--")")
+                        }
+
+                        if state.settingsManager.settings.smoothGlucose, !glucose.isManual,
+                           let smoothedGlucose = glucose.smoothedGlucose, smoothedGlucose != 0
+                        {
+                            let smoothedGlucoseForDisplay = state.units == .mgdL ? smoothedGlucose
+                                .description : smoothedGlucose.decimalValue
+                                .formattedAsMmolL
+
+                            (
+                                Text("(") +
+                                    Text(Image(systemName: "sparkles")) +
+                                    Text(" ") +
+                                    Text("\(smoothedGlucoseForDisplay)") +
+                                    Text(")")
+                            ).foregroundStyle(.secondary)
+                                .padding(.leading, 10)
+                        }
+
+                        Spacer()
+
+                        Text(Formatter.dateFormatter.string(from: glucose.date ?? Date()))
+                    }
+                    .contextMenu {
+                        Button(
+                            "Delete",
+                            systemImage: "trash.fill",
+                            role: .destructive,
+                            action: { requestDelete(.glucose(glucose)) }
+                        ).tint(.red)
+                    }
+                    .swipeActions {
+                        Button(
+                            "Delete",
+                            systemImage: "trash.fill",
+                            role: .none,
+                            action: { requestDelete(.glucose(glucose)) }
+                        ).tint(.red)
+                    }
+                }
+            } else {
+                ContentUnavailableView(
+                    String(localized: "No data."),
+                    systemImage: "drop.fill"
+                )
+            }
+        }.listRowBackground(Color.chart)
+    }
+
+    func formatGlucose(_ value: Decimal, isManual: Bool) -> String {
+        let formatter = isManual ? manualGlucoseFormatter : Formatter.glucoseFormatter(for: state.units)
+        let glucoseValue = state.units == .mmolL ? value.asMmolL : value
+        let formattedValue = formatter.string(from: glucoseValue as NSNumber) ?? "--"
+
+        return formattedValue
+    }
+}

+ 98 - 0
Trio/Sources/Modules/History/View/HistoryRootView+Meals.swift

@@ -0,0 +1,98 @@
+import CoreData
+import SwiftUI
+
+extension History.RootView {
+    var mealsList: some View {
+        List {
+            HStack {
+                Text("Type").foregroundStyle(.secondary)
+                Spacer()
+                filterFutureEntriesButton
+            }
+            if !carbEntryStored.isEmpty {
+                ForEach(carbEntryStored.filter({ !showFutureEntries ? $0.date ?? Date() <= Date() : true })) { item in
+                    mealView(item)
+                }
+            } else {
+                ContentUnavailableView(
+                    String(localized: "No data."),
+                    systemImage: "fork.knife"
+                )
+            }
+        }.listRowBackground(Color.chart)
+    }
+
+    @ViewBuilder func mealView(_ meal: CarbEntryStored) -> some View {
+        VStack {
+            HStack {
+                if meal.isFPU {
+                    Image(systemName: "circle.fill").foregroundColor(Color.orange.opacity(0.5))
+                    Text("Fat / Protein")
+                    Text(
+                        (Formatter.decimalFormatterWithTwoFractionDigits.string(for: meal.carbs) ?? "0") +
+                            String(localized: " g", comment: "gram of carbs")
+                    )
+                } else {
+                    Image(systemName: "circle.fill").foregroundColor(Color.loopYellow)
+                    Text("Carbs")
+                    Text(
+                        (Formatter.decimalFormatterWithTwoFractionDigits.string(for: meal.carbs) ?? "0") +
+                            String(localized: " g", comment: "gram of carb equilvalents")
+                    )
+                }
+
+                Spacer()
+
+                Text(Formatter.dateFormatter.string(from: meal.date ?? Date()))
+                    .moveDisabled(true)
+            }
+            if let note = meal.note, note != "" {
+                HStack {
+                    Image(systemName: "square.and.pencil")
+                    Text(note)
+                    Spacer()
+                }.padding(.top, 5).foregroundColor(.secondary)
+            }
+        }
+        .contextMenu {
+            Button(
+                "Delete",
+                systemImage: "trash.fill",
+                role: .destructive,
+                action: { requestDelete(.carbs(meal)) }
+            ).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)
+        }
+        .swipeActions {
+            Button(
+                "Delete",
+                systemImage: "trash.fill",
+                role: .none,
+                action: { requestDelete(.carbs(meal)) }
+            ).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)
+        }
+    }
+}

+ 103 - 0
Trio/Sources/Modules/History/View/HistoryRootView+Treatments.swift

@@ -0,0 +1,103 @@
+import CoreData
+import SwiftUI
+
+extension History.RootView {
+    var treatmentsList: some View {
+        List {
+            HStack {
+                filterTreatmentsButton
+                Spacer()
+                Text("Time").foregroundStyle(.secondary)
+            }
+            if !filteredPumpEvents.isEmpty {
+                ForEach(filteredPumpEvents) { item in
+                    treatmentView(item)
+                }
+            } else {
+                ContentUnavailableView(
+                    String(localized: "No data."),
+                    systemImage: "syringe"
+                )
+            }
+        }.listRowBackground(Color.chart)
+    }
+
+    var filteredPumpEvents: [PumpEventStored] {
+        pumpEventStored.filter { item in
+            // First filter by date
+            let passesDateFilter = !showFutureEntries ? item.timestamp ?? Date() <= Date() : true
+
+            guard passesDateFilter else { return false }
+
+            // Then filter by treatment type
+            if let bolus = item.bolus {
+                if bolus.isSMB {
+                    return selectedTreatmentTypes.contains(.smb)
+                } else if bolus.isExternal {
+                    return selectedTreatmentTypes.contains(.externalBolus)
+                } else {
+                    return selectedTreatmentTypes.contains(.bolus)
+                }
+            } else if item.tempBasal != nil {
+                return selectedTreatmentTypes.contains(.tempBasal)
+            } else if item.type == "PumpSuspend" {
+                return selectedTreatmentTypes.contains(.suspend)
+            } else {
+                return selectedTreatmentTypes.contains(.other)
+            }
+        }
+    }
+
+    @ViewBuilder func treatmentView(_ item: PumpEventStored) -> some View {
+        HStack {
+            if let bolus = item.bolus, let amount = bolus.amount {
+                Image(systemName: "circle.fill").foregroundColor(Color.insulin)
+                Text(bolus.isSMB ? "SMB" : item.type ?? "Bolus")
+                Text(
+                    (Formatter.decimalFormatterWithThreeFractionDigits.string(from: amount) ?? "0") +
+                        String(localized: " U", comment: "Insulin unit")
+                )
+                .foregroundColor(.secondary)
+                if bolus.isExternal {
+                    Text(String(localized: "External", comment: "External Insulin")).foregroundColor(.secondary)
+                }
+            } else if let tempBasal = item.tempBasal, let rate = tempBasal.rate {
+                Image(systemName: "circle.fill").foregroundColor(Color.insulin.opacity(0.4))
+                Text("Temp Basal")
+                Text(
+                    (Formatter.decimalFormatterWithThreeFractionDigits.string(from: rate) ?? "0") +
+                        String(localized: " U/hr", comment: "Unit insulin per hour")
+                )
+                .foregroundColor(.secondary)
+                if tempBasal.duration > 0 {
+                    Text("\(tempBasal.duration.string) min").foregroundColor(.secondary)
+                }
+            } else {
+                Image(systemName: "circle.fill").foregroundColor(Color.loopGray)
+                Text(item.type ?? "Pump Event")
+            }
+            Spacer()
+            Text(Formatter.dateFormatter.string(from: item.timestamp ?? Date())).moveDisabled(true)
+        }
+        .contextMenu {
+            if item.bolus != nil {
+                Button(
+                    "Delete",
+                    systemImage: "trash.fill",
+                    role: .destructive,
+                    action: { requestDelete(.insulin(item)) }
+                ).tint(.red)
+            }
+        }
+        .swipeActions {
+            if item.bolus != nil {
+                Button(
+                    "Delete",
+                    systemImage: "trash.fill",
+                    role: .none,
+                    action: { requestDelete(.insulin(item)) }
+                ).tint(.red)
+            }
+        }
+    }
+}

+ 61 - 838
Trio/Sources/Modules/History/View/HistoryRootView.swift

@@ -8,19 +8,14 @@ extension History {
 
         @State var state = StateModel()
 
-        @State private var isRemoveHistoryItemAlertPresented: Bool = false
-        @State private var alertTitle: String = ""
-        @State private var alertMessage: String = ""
-        @State private var alertTreatmentToDelete: PumpEventStored?
-        @State private var alertCarbEntryToDelete: CarbEntryStored?
-        @State private var alertGlucoseToDelete: GlucoseStored?
-        @State private var showAlert = false
-        @State private var showFutureEntries: Bool = false // default to hide future entries
-        @State private var showManualGlucose: Bool = false
-        @State private var isAmountUnconfirmed: Bool = true
-        @State private var showTreatmentTypeFilter = false
-        @State private var selectedTreatmentTypes: Set<TreatmentType> = Set(TreatmentType.allCases)
-        @State private var filterPopoverAnchor: CGRect = .zero
+        @State var deletionTarget: History.DeletionTarget?
+        @State var showErrorAlert: Bool = false
+        @State var errorMessage: String = ""
+        @State var showFutureEntries: Bool = false // default to hide future entries
+        @State var showManualGlucose: Bool = false
+        @State var isAmountUnconfirmed: Bool = true
+        @State var showTreatmentTypeFilter = false
+        @State var selectedTreatmentTypes: Set<TreatmentType> = Set(TreatmentType.allCases)
 
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.managedObjectContext) var context
@@ -61,76 +56,63 @@ extension History {
             animation: .bouncy
         ) var tempTargetRunStored: FetchedResults<TempTargetRunStored>
 
-        private var manualGlucoseFormatter: NumberFormatter {
-            let formatter = NumberFormatter()
-            formatter.numberStyle = .decimal
-            if state.units == .mgdL {
-                formatter.maximumIntegerDigits = 3
-                formatter.maximumFractionDigits = 0
-            } else {
-                formatter.maximumIntegerDigits = 2
-                formatter.minimumFractionDigits = 0
-                formatter.maximumFractionDigits = 1
-            }
-            formatter.roundingMode = .halfUp
-            return formatter
-        }
-
         var body: some View {
-            ZStack(alignment: .center, content: {
-                VStack {
-                    Picker("Mode", selection: $state.mode) {
-                        ForEach(
-                            Mode.allCases.indexed(),
-                            id: \.1
-                        ) { index, item in
-                            Text(item.name).tag(index)
-                        }
-                    }
-                    .pickerStyle(SegmentedPickerStyle())
-                    .padding(.horizontal)
-
-                    Form {
-                        switch state.mode {
-                        case .treatments: treatmentsList
-                        case .glucose: glucoseList
-                        case .meals: mealsList
-                        case .adjustments: adjustmentsList
+            historyConfirmations(
+                ZStack(alignment: .center, content: {
+                    VStack {
+                        Picker("Mode", selection: $state.mode) {
+                            ForEach(
+                                Mode.allCases.indexed(),
+                                id: \.1
+                            ) { index, item in
+                                Text(item.name).tag(index)
+                            }
                         }
-                    }.scrollContentBackground(.hidden)
-                        .background(appState.trioBackgroundColor(for: colorScheme))
-                }.blur(radius: state.waitForSuggestion ? 8 : 0)
+                        .pickerStyle(SegmentedPickerStyle())
+                        .padding(.horizontal)
 
-                // Show custom progress view
-                /// don't show it if glucose is stale as it will block the UI
-                if state.waitForSuggestion && state.isGlucoseDataFresh(glucoseStored.first?.date) {
-                    CustomProgressView(text: progressText.displayName)
-                }
-            })
-                .background(appState.trioBackgroundColor(for: colorScheme))
-                .onAppear(perform: configureView)
-                .onDisappear {
-                    state.carbEntryDeleted = false
-                    state.insulinEntryDeleted = false
-                }
-                .navigationTitle("History")
-                .navigationBarTitleDisplayMode(.large)
-                .toolbar {
-                    ToolbarItem(placement: .topBarTrailing, content: {
-                        addButton({
-                            showManualGlucose = true
-                            state.manualGlucose = 0
+                        Form {
+                            switch state.mode {
+                            case .treatments: treatmentsList
+                            case .glucose: glucoseList
+                            case .meals: mealsList
+                            case .adjustments: adjustmentsList
+                            }
+                        }.scrollContentBackground(.hidden)
+                            .background(appState.trioBackgroundColor(for: colorScheme))
+                    }.blur(radius: state.waitForSuggestion ? 8 : 0)
+
+                    // Show custom progress view
+                    /// don't show it if glucose is stale as it will block the UI
+                    if state.waitForSuggestion && state.isGlucoseDataFresh(glucoseStored.first?.date) {
+                        CustomProgressView(text: progressText.displayName)
+                    }
+                })
+                    .background(appState.trioBackgroundColor(for: colorScheme))
+                    .onAppear(perform: configureView)
+                    .onDisappear {
+                        state.carbEntryDeleted = false
+                        state.insulinEntryDeleted = false
+                    }
+                    .navigationTitle("History")
+                    .navigationBarTitleDisplayMode(.large)
+                    .toolbar {
+                        ToolbarItem(placement: .topBarTrailing, content: {
+                            addButton({
+                                showManualGlucose = true
+                                state.manualGlucose = 0
+                            })
                         })
-                    })
-                }
-                .sheet(isPresented: $showManualGlucose) {
-                    addGlucoseView()
-                }
-                .sheet(isPresented: $state.showCarbEntryEditor) {
-                    if let carbEntry = state.carbEntryToEdit {
-                        CarbEntryEditorView(state: state, carbEntry: carbEntry)
                     }
-                }
+                    .sheet(isPresented: $showManualGlucose) {
+                        addGlucoseView()
+                    }
+                    .sheet(isPresented: $state.showCarbEntryEditor) {
+                        if let carbEntry = state.carbEntryToEdit {
+                            CarbEntryEditorView(state: state, carbEntry: carbEntry)
+                        }
+                    }
+            )
         }
 
         @ViewBuilder func addButton(_ action: @escaping () -> Void) -> some View {
@@ -146,7 +128,7 @@ extension History {
             )
         }
 
-        private var progressText: ProgressText {
+        var progressText: ProgressText {
             switch (state.carbEntryDeleted, state.insulinEntryDeleted) {
             case (true, false):
                 return .updatingCOB
@@ -156,764 +138,5 @@ extension History {
                 return .updatingHistory
             }
         }
-
-        private var logGlucoseButton: some View {
-            Button(
-                action: {
-                    showManualGlucose = true
-                    state.manualGlucose = 0
-                },
-                label: {
-                    Text("Log Glucose")
-                        .foregroundColor(Color.accentColor)
-                    Image(systemName: "plus")
-                        .foregroundColor(Color.accentColor)
-                }
-            ).buttonStyle(.borderless)
-        }
-
-        private var filterTreatmentsButton: some View {
-            Button(action: {
-                showTreatmentTypeFilter.toggle()
-            }) {
-                HStack {
-                    Text("Filter")
-                    Image(
-                        systemName: selectedTreatmentTypes.count == TreatmentType.allCases.count
-                            ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill"
-                    )
-                    if selectedTreatmentTypes.count < TreatmentType.allCases.count {
-                        Text(verbatim: "(\(selectedTreatmentTypes.count)/\(TreatmentType.allCases.count))")
-                    }
-                }.foregroundColor(Color.accentColor)
-            }
-            .popover(isPresented: $showTreatmentTypeFilter, arrowEdge: .top) {
-                VStack(alignment: .leading, spacing: 20) {
-                    Button(action: {
-                        if selectedTreatmentTypes.count == TreatmentType.allCases.count {
-                            // Deselect all - keep at least one selected
-                            selectedTreatmentTypes = []
-                        } else {
-                            // Select all
-                            selectedTreatmentTypes = Set(TreatmentType.allCases)
-                        }
-                    }) {
-                        HStack(spacing: 20) {
-                            Image(
-                                systemName: selectedTreatmentTypes.count == TreatmentType.allCases.count
-                                    ? "checkmark.square.fill" : "square"
-                            )
-                            .frame(width: 20)
-                            .foregroundColor(Color.accentColor)
-                            Text(
-                                selectedTreatmentTypes.count == TreatmentType.allCases
-                                    .count ? String(localized: "Deselect All") : String(localized: "Select All")
-                            )
-                            .foregroundColor(Color.primary)
-                        }.padding(4)
-                    }
-                    .buttonStyle(.borderless)
-
-                    Divider()
-
-                    ForEach(TreatmentType.allCases, id: \.rawValue) { treatmentType in
-                        Button(action: {
-                            toggleTreatmentType(treatmentType)
-                        }) {
-                            HStack(spacing: 20) {
-                                Image(
-                                    systemName: selectedTreatmentTypes
-                                        .contains(treatmentType) ? "checkmark.square.fill" : "square"
-                                )
-                                .frame(width: 20)
-                                .foregroundColor(Color.accentColor)
-                                Text(treatmentType.displayName)
-                                    .foregroundColor(Color.primary)
-                            }.padding(4)
-                        }
-                        .buttonStyle(.borderless)
-                    }
-
-                    Divider()
-
-                    Button("Done") {
-                        showTreatmentTypeFilter = false
-                    }
-                    .frame(maxWidth: .infinity)
-                    .buttonStyle(.borderless)
-                }
-                .padding()
-                .presentationCompactAdaptation(.popover)
-                .background(Color.chart)
-            }
-        }
-
-        private var filterFutureEntriesButton: some View {
-            Button(
-                action: {
-                    showFutureEntries.toggle()
-                },
-                label: {
-                    HStack {
-                        Text(showFutureEntries ? String(localized: "Hide Future") : String(localized: "Show Future"))
-                            .foregroundColor(Color.accentColor)
-                        Image(systemName: showFutureEntries ? "eye.slash" : "eye")
-                            .foregroundColor(Color.accentColor)
-                    }
-                }
-            ).buttonStyle(.borderless)
-        }
-
-        private func toggleTreatmentType(_ type: TreatmentType) {
-            if selectedTreatmentTypes.contains(type) {
-                selectedTreatmentTypes.remove(type)
-            } else {
-                selectedTreatmentTypes.insert(type)
-            }
-        }
-
-        private var filteredPumpEvents: [PumpEventStored] {
-            pumpEventStored.filter { item in
-                // First filter by date
-                let passesDateFilter = !showFutureEntries ? item.timestamp ?? Date() <= Date() : true
-
-                guard passesDateFilter else { return false }
-
-                // Then filter by treatment type
-                if let bolus = item.bolus {
-                    if bolus.isSMB {
-                        return selectedTreatmentTypes.contains(.smb)
-                    } else if bolus.isExternal {
-                        return selectedTreatmentTypes.contains(.externalBolus)
-                    } else {
-                        return selectedTreatmentTypes.contains(.bolus)
-                    }
-                } else if item.tempBasal != nil {
-                    return selectedTreatmentTypes.contains(.tempBasal)
-                } else if item.type == "PumpSuspend" {
-                    return selectedTreatmentTypes.contains(.suspend)
-                } else {
-                    return selectedTreatmentTypes.contains(.other)
-                }
-            }
-        }
-
-        private var treatmentsList: some View {
-            List {
-                HStack {
-                    filterTreatmentsButton
-                    Spacer()
-                    Text("Time").foregroundStyle(.secondary)
-                }
-                if !filteredPumpEvents.isEmpty {
-                    ForEach(filteredPumpEvents) { item in
-                        treatmentView(item)
-                    }
-                } else {
-                    ContentUnavailableView(
-                        String(localized: "No data."),
-                        systemImage: "syringe"
-                    )
-                }
-            }.listRowBackground(Color.chart)
-        }
-
-        private var mealsList: some View {
-            List {
-                HStack {
-                    Text("Type").foregroundStyle(.secondary)
-                    Spacer()
-                    filterFutureEntriesButton
-                }
-                if !carbEntryStored.isEmpty {
-                    ForEach(carbEntryStored.filter({ !showFutureEntries ? $0.date ?? Date() <= Date() : true })) { item in
-                        mealView(item)
-                    }
-                } else {
-                    ContentUnavailableView(
-                        String(localized: "No data."),
-                        systemImage: "fork.knife"
-                    )
-                }
-            }.listRowBackground(Color.chart)
-        }
-
-        private var adjustmentsList: some View {
-            List {
-                HStack {
-                    Text("Adjustment").foregroundStyle(.secondary)
-                    Spacer()
-                }
-                if !combinedAdjustments.isEmpty {
-                    ForEach(combinedAdjustments) { item in
-                        adjustmentView(for: item)
-                    }
-                } else {
-                    ContentUnavailableView(
-                        String(localized: "No data."),
-                        systemImage: "clock.arrow.2.circlepath"
-                    )
-                }
-            }
-            .listRowBackground(Color.chart)
-        }
-
-        private var combinedAdjustments: [AdjustmentItem] {
-            let overrides = overrideRunStored.map { override -> AdjustmentItem in
-                AdjustmentItem(
-                    id: override.objectID,
-                    name: override.name ?? String(localized: "Override"),
-                    startDate: override.startDate ?? Date(),
-                    endDate: override.endDate ?? Date(),
-                    target: override.target?.decimalValue,
-                    type: .override
-                )
-            }
-
-            let tempTargets = tempTargetRunStored.map { tempTarget -> AdjustmentItem in
-                AdjustmentItem(
-                    id: tempTarget.objectID,
-                    name: tempTarget.name ?? String(localized: "Temp Target"),
-                    startDate: tempTarget.startDate ?? Date(),
-                    endDate: tempTarget.endDate ?? Date(),
-                    target: tempTarget.target?.decimalValue,
-                    type: .tempTarget
-                )
-            }
-
-            let combined = overrides + tempTargets
-            return combined.sorted {
-                if $0.startDate == $1.startDate {
-                    return $0.endDate > $1.endDate
-                }
-                return $0.startDate > $1.startDate
-            } }
-
-        private struct AdjustmentItem: Identifiable {
-            let id: NSManagedObjectID
-            let name: String
-            let startDate: Date
-            let endDate: Date
-            let target: Decimal?
-            let type: AdjustmentType
-        }
-
-        private enum AdjustmentType {
-            case override
-            case tempTarget
-
-            var symbolName: String {
-                switch self {
-                case .override:
-                    return "clock.arrow.2.circlepath"
-                case .tempTarget:
-                    return "target"
-                }
-            }
-
-            var symbolColor: Color {
-                switch self {
-                case .override:
-                    return .orange
-                case .tempTarget:
-                    return .blue
-                }
-            }
-        }
-
-        @ViewBuilder private func adjustmentView(for item: AdjustmentItem) -> some View {
-            let formattedDates =
-                "\(Formatter.dateFormatter.string(from: item.startDate)) - \(Formatter.dateFormatter.string(from: item.endDate))"
-
-            let targetDescription: String = {
-                guard let target = item.target, target != 0 else {
-                    return ""
-                }
-                return "\(state.units == .mgdL ? target : target.asMmolL) \(state.units.rawValue)"
-            }()
-
-            let labels: [String] = [
-                targetDescription,
-                formattedDates
-            ].filter { !$0.isEmpty }
-
-            ZStack(alignment: .trailing) {
-                HStack {
-                    VStack(alignment: .leading) {
-                        HStack {
-                            Image(systemName: item.type.symbolName)
-                                .foregroundStyle(item.type == .override ? Color.purple : Color.green)
-                            Text(item.name)
-                                .font(.headline)
-                            Spacer()
-                        }
-                        HStack(spacing: 5) {
-                            ForEach(labels, id: \.self) { label in
-                                Text(label)
-                                if label != labels.last {
-                                    Divider()
-                                }
-                            }
-                            Spacer()
-                        }
-                        .padding(.top, 2)
-                        .foregroundColor(.secondary)
-                        .font(.caption)
-                    }
-                    .contentShape(Rectangle())
-                }
-            }
-            .padding(.vertical, 8)
-        }
-
-        private var glucoseList: some View {
-            List {
-                HStack {
-                    Text("Values")
-                    Spacer()
-                    Text("Time")
-                }.foregroundStyle(.secondary)
-
-                if !glucoseStored.isEmpty {
-                    ForEach(glucoseStored) { glucose in
-                        HStack {
-                            Text(formatGlucose(Decimal(glucose.glucose), isManual: glucose.isManual))
-
-                            /// check for manual glucose
-                            if glucose.isManual {
-                                Image(systemName: "drop.fill").symbolRenderingMode(.monochrome).foregroundStyle(.red)
-                            } else {
-                                Text("\(glucose.directionEnum?.symbol ?? "--")")
-                            }
-
-                            if state.settingsManager.settings.smoothGlucose, !glucose.isManual,
-                               let smoothedGlucose = glucose.smoothedGlucose, smoothedGlucose != 0
-                            {
-                                let smoothedGlucoseForDisplay = state.units == .mgdL ? smoothedGlucose
-                                    .description : smoothedGlucose.decimalValue
-                                    .formattedAsMmolL
-
-                                (
-                                    Text("(") +
-                                        Text(Image(systemName: "sparkles")) +
-                                        Text(" ") +
-                                        Text("\(smoothedGlucoseForDisplay)") +
-                                        Text(")")
-                                ).foregroundStyle(.secondary)
-                                    .padding(.leading, 10)
-                            }
-
-                            Spacer()
-
-                            Text(Formatter.dateFormatter.string(from: glucose.date ?? Date()))
-                        }
-                        .contextMenu {
-                            Button(
-                                "Delete",
-                                systemImage: "trash.fill",
-                                role: .destructive,
-                                action: {
-                                    alertGlucoseToDelete = glucose
-
-                                    let glucoseToDisplay = state.units == .mgdL ? glucose.glucose
-                                        .description : Int(glucose.glucose).formattedAsMmolL
-                                    alertTitle = String(localized: "Delete Glucose?", comment: "Alert title for deleting glucose")
-                                    alertMessage = Formatter.dateFormatter
-                                        .string(from: glucose.date ?? Date()) + ", " + glucoseToDisplay + " " + state.units
-                                        .rawValue
-
-                                    isRemoveHistoryItemAlertPresented = true
-                                }
-                            ).tint(.red)
-                        }
-                        .swipeActions {
-                            Button(
-                                "Delete",
-                                systemImage: "trash.fill",
-                                role: .none,
-                                action: {
-                                    alertGlucoseToDelete = glucose
-
-                                    let glucoseToDisplay = state.units == .mgdL ? glucose.glucose
-                                        .description : Int(glucose.glucose).formattedAsMmolL
-                                    alertTitle = String(localized: "Delete Glucose?", comment: "Alert title for deleting glucose")
-                                    alertMessage = Formatter.dateFormatter
-                                        .string(from: glucose.date ?? Date()) + ", " + glucoseToDisplay + " " + state.units
-                                        .rawValue
-
-                                    isRemoveHistoryItemAlertPresented = true
-                                }
-                            ).tint(.red)
-                        }
-                        .alert(
-                            Text(alertTitle),
-                            isPresented: $isRemoveHistoryItemAlertPresented
-                        ) {
-                            Button("Cancel", role: .cancel) {}
-                            Button("Delete", role: .destructive) {
-                                guard let glucoseToDelete = alertGlucoseToDelete else {
-                                    debug(.default, "Cannot gracefully unwrap alertCarbEntryToDelete!")
-                                    return
-                                }
-                                let glucoseToDeleteObjectID = glucoseToDelete.objectID
-                                state.invokeGlucoseDeletionTask(glucoseToDeleteObjectID)
-                            }
-                        } message: {
-                            Text("\n" + alertMessage)
-                        }
-                    }
-                } else {
-                    ContentUnavailableView(
-                        String(localized: "No data."),
-                        systemImage: "drop.fill"
-                    )
-                }
-            }.listRowBackground(Color.chart)
-                .alert(isPresented: $showAlert) {
-                    Alert(title: Text("Error"), message: Text(alertMessage), dismissButton: .default(Text("OK")))
-                }
-        }
-
-        private func deleteGlucose(at offsets: IndexSet) {
-            for index in offsets {
-                let glucoseToDelete = glucoseStored[index]
-                context.delete(glucoseToDelete)
-            }
-
-            do {
-                try context.save()
-                debugPrint("Data Table Root View: \(#function) \(DebuggingIdentifiers.succeeded) deleted glucose from core data")
-            } catch {
-                debugPrint(
-                    "Data Table Root View: \(#function) \(DebuggingIdentifiers.failed) error while deleting glucose from core data"
-                )
-                alertMessage = "Failed to delete glucose data: \(error.localizedDescription)"
-                showAlert = true
-            }
-        }
-
-        @ViewBuilder private func addGlucoseView() -> some View {
-            let limitLow: Decimal = state.units == .mgdL ? Decimal(14) : 14.asMmolL
-            let limitHigh: Decimal = state.units == .mgdL ? Decimal(720) : 720.asMmolL
-
-            NavigationView {
-                VStack {
-                    Form {
-                        Section {
-                            HStack {
-                                Text("New Glucose")
-                                TextFieldWithToolBar(
-                                    text: $state.manualGlucose,
-                                    placeholder: " ... ",
-                                    keyboardType: state.units == .mgdL ? .numberPad : .decimalPad,
-                                    numberFormatter: manualGlucoseFormatter,
-                                    initialFocus: true,
-                                    unitsText: state.units.rawValue
-                                )
-                            }
-                        }.listRowBackground(Color.chart)
-
-                        Section {
-                            HStack {
-                                Button {
-                                    state.addManualGlucose()
-                                    isAmountUnconfirmed = false
-                                    showManualGlucose = false
-                                    state.mode = .glucose
-                                }
-                                label: { Text("Save") }
-                                    .frame(maxWidth: .infinity, alignment: .center)
-                                    .disabled(state.manualGlucose < limitLow || state.manualGlucose > limitHigh)
-                            }
-                        }
-                        .listRowBackground(
-                            state.manualGlucose < limitLow || state
-                                .manualGlucose > limitHigh ? Color(.systemGray4) : Color(.systemBlue)
-                        )
-                        .tint(.white)
-                    }.scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
-                }
-                .onAppear(perform: configureView)
-                .navigationTitle("Add Glucose")
-                .navigationBarTitleDisplayMode(.inline)
-                .toolbar {
-                    ToolbarItem(placement: .topBarLeading) {
-                        Button("Close") {
-                            showManualGlucose = false
-                        }
-                    }
-                }
-            }
-        }
-
-        private var filterEntriesButton: some View {
-            Button(action: { showFutureEntries.toggle() }, label: {
-                HStack {
-                    Text(showFutureEntries ? String(localized: "Hide Future") : String(localized: "Show Future"))
-                        .foregroundColor(Color.secondary)
-                    Image(systemName: showFutureEntries ? "calendar.badge.minus" : "calendar.badge.plus")
-                }.frame(maxWidth: .infinity, alignment: .trailing)
-            }).buttonStyle(.borderless)
-        }
-
-        @ViewBuilder private func treatmentView(_ item: PumpEventStored) -> some View {
-            HStack {
-                if let bolus = item.bolus, let amount = bolus.amount {
-                    Image(systemName: "circle.fill").foregroundColor(Color.insulin)
-                    Text(bolus.isSMB ? "SMB" : item.type ?? "Bolus")
-                    Text(
-                        (Formatter.decimalFormatterWithThreeFractionDigits.string(from: amount) ?? "0") +
-                            String(localized: " U", comment: "Insulin unit")
-                    )
-                    .foregroundColor(.secondary)
-                    if bolus.isExternal {
-                        Text(String(localized: "External", comment: "External Insulin")).foregroundColor(.secondary)
-                    }
-                } else if let tempBasal = item.tempBasal, let rate = tempBasal.rate {
-                    Image(systemName: "circle.fill").foregroundColor(Color.insulin.opacity(0.4))
-                    Text("Temp Basal")
-                    Text(
-                        (Formatter.decimalFormatterWithThreeFractionDigits.string(from: rate) ?? "0") +
-                            String(localized: " U/hr", comment: "Unit insulin per hour")
-                    )
-                    .foregroundColor(.secondary)
-                    if tempBasal.duration > 0 {
-                        Text("\(tempBasal.duration.string) min").foregroundColor(.secondary)
-                    }
-                } else {
-                    Image(systemName: "circle.fill").foregroundColor(Color.loopGray)
-                    Text(item.type ?? "Pump Event")
-                }
-                Spacer()
-                Text(Formatter.dateFormatter.string(from: item.timestamp ?? Date())).moveDisabled(true)
-            }
-            .contextMenu {
-                if item.bolus != nil {
-                    Button(
-                        "Delete",
-                        systemImage: "trash.fill",
-                        role: .destructive,
-                        action: {
-                            alertTreatmentToDelete = item
-                            alertTitle = String(localized: "Delete Insulin?", comment: "Alert title for deleting insulin")
-                            alertMessage = Formatter.dateFormatter
-                                .string(from: item.timestamp ?? Date()) + ", " +
-                                (Formatter.decimalFormatterWithThreeFractionDigits.string(from: item.bolus?.amount ?? 0) ?? "0") +
-                                String(localized: " U", comment: "Insulin unit")
-
-                            if let bolus = item.bolus {
-                                // Add text snippet, so that alert message is more descriptive for SMBs
-                                alertMessage += bolus.isSMB ? String(
-                                    localized: " SMB",
-                                    comment: "Super Micro Bolus indicator in delete alert"
-                                )
-                                    : ""
-                            }
-
-                            isRemoveHistoryItemAlertPresented = true
-                        }
-                    ).tint(.red)
-                }
-            }
-            .swipeActions {
-                if item.bolus != nil {
-                    Button(
-                        "Delete",
-                        systemImage: "trash.fill",
-                        role: .none,
-                        action: {
-                            alertTreatmentToDelete = item
-                            alertTitle = String(localized: "Delete Insulin?", comment: "Alert title for deleting insulin")
-                            alertMessage = Formatter.dateFormatter
-                                .string(from: item.timestamp ?? Date()) + ", " +
-                                (Formatter.decimalFormatterWithThreeFractionDigits.string(from: item.bolus?.amount ?? 0) ?? "0") +
-                                String(localized: " U", comment: "Insulin unit")
-
-                            if let bolus = item.bolus {
-                                // Add text snippet, so that alert message is more descriptive for SMBs
-                                alertMessage += bolus.isSMB ? String(
-                                    localized: " SMB",
-                                    comment: "Super Micro Bolus indicator in delete alert"
-                                )
-                                    : ""
-                            }
-
-                            isRemoveHistoryItemAlertPresented = true
-                        }
-                    ).tint(.red)
-                }
-            }
-            .alert(
-                Text(alertTitle),
-                isPresented: $isRemoveHistoryItemAlertPresented
-            ) {
-                Button("Cancel", role: .cancel) {}
-                Button("Delete", role: .destructive) {
-                    guard let treatmentToDelete = alertTreatmentToDelete else {
-                        debug(.default, "Cannot gracefully unwrap alertTreatmentToDelete!")
-                        return
-                    }
-                    let treatmentObjectID = treatmentToDelete.objectID
-
-                    state.invokeInsulinDeletionTask(treatmentObjectID)
-                }
-            } message: {
-                Text("\n" + alertMessage)
-            }
-        }
-
-        @ViewBuilder private func mealView(_ meal: CarbEntryStored) -> some View {
-            VStack {
-                HStack {
-                    if meal.isFPU {
-                        Image(systemName: "circle.fill").foregroundColor(Color.orange.opacity(0.5))
-                        Text("Fat / Protein")
-                        Text(
-                            (Formatter.decimalFormatterWithTwoFractionDigits.string(for: meal.carbs) ?? "0") +
-                                String(localized: " g", comment: "gram of carbs")
-                        )
-                    } else {
-                        Image(systemName: "circle.fill").foregroundColor(Color.loopYellow)
-                        Text("Carbs")
-                        Text(
-                            (Formatter.decimalFormatterWithTwoFractionDigits.string(for: meal.carbs) ?? "0") +
-                                String(localized: " g", comment: "gram of carb equilvalents")
-                        )
-                    }
-
-                    Spacer()
-
-                    Text(Formatter.dateFormatter.string(from: meal.date ?? Date()))
-                        .moveDisabled(true)
-                }
-                if let note = meal.note, note != "" {
-                    HStack {
-                        Image(systemName: "square.and.pencil")
-                        Text(note)
-                        Spacer()
-                    }.padding(.top, 5).foregroundColor(.secondary)
-                }
-            }
-            .contextMenu {
-                Button(
-                    "Delete",
-                    systemImage: "trash.fill",
-                    role: .destructive,
-                    action: {
-                        alertCarbEntryToDelete = meal
-
-                        // meal is carb-only
-                        if meal.fpuID == nil {
-                            alertTitle = String(localized: "Delete Carbs?", comment: "Alert title for deleting carbs")
-                            alertMessage = Formatter.dateFormatter
-                                .string(from: meal.date ?? Date()) + ", " +
-                                (Formatter.decimalFormatterWithTwoFractionDigits.string(for: meal.carbs) ?? "0") +
-                                String(localized: " g", comment: "gram of carbs")
-                        }
-                        // meal is complex-meal or fpu-only
-                        else {
-                            alertTitle = meal.isFPU ? String(
-                                localized: "Delete Carbs Equivalents?",
-                                comment: "Alert title for deleting carb equivalents"
-                            )
-                                : String(localized: "Delete Carbs?", comment: "Alert title for deleting carbs")
-                            alertMessage = String(
-                                localized: "All FPUs and the carbs of the meal will be deleted.",
-                                comment: "Alert message for meal deletion"
-                            )
-                        }
-
-                        isRemoveHistoryItemAlertPresented = true
-                    }
-                ).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)
-            }
-            .swipeActions {
-                Button(
-                    "Delete",
-                    systemImage: "trash.fill",
-                    role: .none,
-                    action: {
-                        alertCarbEntryToDelete = meal
-
-                        // meal is carb-only
-                        if meal.fpuID == nil {
-                            alertTitle = String(localized: "Delete Carbs?", comment: "Alert title for deleting carbs")
-                            alertMessage = Formatter.dateFormatter
-                                .string(from: meal.date ?? Date()) + ", " +
-                                (Formatter.decimalFormatterWithTwoFractionDigits.string(for: meal.carbs) ?? "0") +
-                                String(localized: " g", comment: "gram of carbs")
-                        }
-                        // meal is complex-meal or fpu-only
-                        else {
-                            alertTitle = meal.isFPU ? String(
-                                localized: "Delete Carbs Equivalents?",
-                                comment: "Alert title for deleting carb equivalents"
-                            )
-                                : String(localized: "Delete Carbs?", comment: "Alert title for deleting carbs")
-                            alertMessage = String(
-                                localized: "All FPUs and the carbs of the meal will be deleted.",
-                                comment: "Alert message for meal deletion"
-                            )
-                        }
-
-                        isRemoveHistoryItemAlertPresented = true
-                    }
-                ).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(
-                Text(alertTitle),
-                isPresented: $isRemoveHistoryItemAlertPresented
-            ) {
-                Button("Cancel", role: .cancel) {}
-                Button("Delete", role: .destructive) {
-                    guard let carbEntryToDelete = alertCarbEntryToDelete else {
-                        debug(.default, "Cannot gracefully unwrap alertCarbEntryToDelete!")
-                        return
-                    }
-                    let treatmentObjectID = carbEntryToDelete.objectID
-
-                    state.invokeCarbDeletionTask(
-                        treatmentObjectID,
-                        isFpuOrComplexMeal: carbEntryToDelete.isFPU || carbEntryToDelete.fat > 0 || carbEntryToDelete.protein > 0
-                    )
-                }
-            } message: {
-                Text("\n" + alertMessage)
-            }
-        }
-
-        // MARK: - Format glucose
-
-        private func formatGlucose(_ value: Decimal, isManual: Bool) -> String {
-            let formatter = isManual ? manualGlucoseFormatter : Formatter.glucoseFormatter(for: state.units)
-            let glucoseValue = state.units == .mmolL ? value.asMmolL : value
-            let formattedValue = formatter.string(from: glucoseValue as NSNumber) ?? "--"
-
-            return formattedValue
-        }
     }
 }