فهرست منبع

Delete Carbs/Fat/Protein

polscm32 1 سال پیش
والد
کامیت
781bdf688a

+ 2 - 0
FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift

@@ -1,5 +1,6 @@
 import CoreData
 import Foundation
+import HealthKit
 import SwiftUI
 
 enum DataTable {
@@ -222,4 +223,5 @@ protocol DataTableProvider: Provider {
     func deleteInsulinFromNightscout(withID id: String)
     func deleteManualGlucoseFromNightscout(withID id: String)
     func deleteGlucoseFromHealth(withSyncID id: String)
+    func deleteMealDataFromHealth(byID id: String, sampleType: HKSampleType)
 }

+ 8 - 0
FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift

@@ -1,5 +1,6 @@
 import CoreData
 import Foundation
+import HealthKit
 
 extension DataTable {
     final class Provider: BaseProvider, DataTableProvider {
@@ -40,5 +41,12 @@ extension DataTable {
                 await self.healthkitManager.deleteGlucose(syncID: id)
             }
         }
+
+        func deleteMealDataFromHealth(byID id: String, sampleType: HKSampleType) {
+            Task.detached { [weak self] in
+                guard let self = self else { return }
+                await self.healthkitManager.deleteMealData(byID: id, sampleType: sampleType)
+            }
+        }
     }
 }

+ 19 - 1
FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -1,4 +1,5 @@
 import CoreData
+import HealthKit
 import SwiftUI
 
 extension DataTable {
@@ -112,6 +113,18 @@ extension DataTable {
                         // Delete FPUs 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)
+                            }
+                        }
+
                         // fetch request for all carb entries with the same id
                         let fetchRequest: NSFetchRequest<NSFetchRequestResult> = CarbEntryStored.fetchRequest()
                         fetchRequest.predicate = NSPredicate(format: "fpuID == %@", fpuID as CVarArg)
@@ -129,9 +142,14 @@ extension DataTable {
                         // Delete carbs from Nightscout
                         if let id = carbEntry.id?.uuidString {
                             self.provider.deleteCarbsFromNightscout(withID: id)
+
+                            // Delete carbs from Apple Health
+                            if let sampleType = AppleHealthConfig.healthCarbObject {
+                                self.provider.deleteMealDataFromHealth(byID: id, sampleType: sampleType)
+                            }
                         }
 
-                        // Now delete carbs also from the Database
+                        // Now also delete carbs from the Database
                         taskContext.delete(carbEntry)
 
                         guard taskContext.hasChanges else { return }

+ 62 - 44
FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift

@@ -27,31 +27,31 @@ protocol HealthKitManager: GlucoseSource {
     /// Delete glucose with syncID
     func deleteGlucose(syncID: String) async
     /// delete carbs with syncID
-    func deleteCarbs(syncID: String, fpuID: String)
+    func deleteMealData(byID id: String, sampleType: HKSampleType) async
     /// delete insulin with syncID
     func deleteInsulin(syncID: String)
 }
 
-final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDelegate, PumpHistoryDelegate {
-    private enum Config {
-        // unwraped HKObjects
-        static var readPermissions: Set<HKSampleType> {
-            Set([healthBGObject].compactMap { $0 }) }
-
-        static var writePermissions: Set<HKSampleType> {
-            Set([healthBGObject, healthCarbObject, healthFatObject, healthProteinObject, healthInsulinObject].compactMap { $0 }) }
-
-        // link to object in HealthKit
-        static let healthBGObject = HKObjectType.quantityType(forIdentifier: .bloodGlucose)
-        static let healthCarbObject = HKObjectType.quantityType(forIdentifier: .dietaryCarbohydrates)
-        static let healthFatObject = HKObjectType.quantityType(forIdentifier: .dietaryFatTotal)
-        static let healthProteinObject = HKObjectType.quantityType(forIdentifier: .dietaryProtein)
-        static let healthInsulinObject = HKObjectType.quantityType(forIdentifier: .insulinDelivery)
-
-        // Meta-data key of FreeASPX data in HealthStore
-        static let freeAPSMetaKey = "From Trio"
-    }
+public enum AppleHealthConfig {
+    // unwraped HKObjects
+    static var readPermissions: Set<HKSampleType> {
+        Set([healthBGObject].compactMap { $0 }) }
+
+    static var writePermissions: Set<HKSampleType> {
+        Set([healthBGObject, healthCarbObject, healthFatObject, healthProteinObject, healthInsulinObject].compactMap { $0 }) }
 
+    // link to object in HealthKit
+    static let healthBGObject = HKObjectType.quantityType(forIdentifier: .bloodGlucose)
+    static let healthCarbObject = HKObjectType.quantityType(forIdentifier: .dietaryCarbohydrates)
+    static let healthFatObject = HKObjectType.quantityType(forIdentifier: .dietaryFatTotal)
+    static let healthProteinObject = HKObjectType.quantityType(forIdentifier: .dietaryProtein)
+    static let healthInsulinObject = HKObjectType.quantityType(forIdentifier: .insulinDelivery)
+
+    // Meta-data key of FreeASPX data in HealthStore
+    static let freeAPSMetaKey = "From Trio"
+}
+
+final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDelegate, PumpHistoryDelegate {
     @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var healthKitStore: HKHealthStore!
     @Injected() private var settingsManager: SettingsManager!
@@ -99,10 +99,10 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
     }
 
     var areAllowAllPermissions: Bool {
-        Set(Config.readPermissions.map { healthKitStore.authorizationStatus(for: $0) })
+        Set(AppleHealthConfig.readPermissions.map { healthKitStore.authorizationStatus(for: $0) })
             .intersection([.notDetermined])
             .isEmpty &&
-            Set(Config.writePermissions.map { healthKitStore.authorizationStatus(for: $0) })
+            Set(AppleHealthConfig.writePermissions.map { healthKitStore.authorizationStatus(for: $0) })
             .intersection([.sharingDenied, .notDetermined])
             .isEmpty
     }
@@ -119,7 +119,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
         // loading only not FreeAPS bg
         // this predicate dont influence on Deleted Objects, only on added
         let predicateByMeta = HKQuery.predicateForObjects(
-            withMetadataKey: Config.freeAPSMetaKey,
+            withMetadataKey: AppleHealthConfig.freeAPSMetaKey,
             operatorType: .notEqualTo,
             value: 1
         )
@@ -130,7 +130,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
     init(resolver: Resolver) {
         injectServices(resolver)
         guard isAvailableOnCurrentDevice,
-              Config.healthBGObject != nil else { return }
+              AppleHealthConfig.healthBGObject != nil else { return }
 
         carbsStorage.delegate = self
         pumpHistoryStorage.delegate = self
@@ -143,19 +143,22 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
     }
 
     func checkAvailabilitySaveBG() -> Bool {
-        Config.healthBGObject.map { checkAvailabilitySave(objectTypeToHealthStore: $0) } ?? false
+        AppleHealthConfig.healthBGObject.map { checkAvailabilitySave(objectTypeToHealthStore: $0) } ?? false
     }
 
     func requestPermission() async throws -> Bool {
         guard isAvailableOnCurrentDevice else {
             throw HKError.notAvailableOnCurrentDevice
         }
-        guard Config.readPermissions.isNotEmpty, Config.writePermissions.isNotEmpty else {
+        guard AppleHealthConfig.readPermissions.isNotEmpty, AppleHealthConfig.writePermissions.isNotEmpty else {
             throw HKError.dataNotAvailable
         }
 
         return try await withCheckedThrowingContinuation { continuation in
-            healthKitStore.requestAuthorization(toShare: Config.writePermissions, read: Config.readPermissions) { status, error in
+            healthKitStore.requestAuthorization(
+                toShare: AppleHealthConfig.writePermissions,
+                read: AppleHealthConfig.readPermissions
+            ) { status, error in
                 if let error = error {
                     continuation.resume(throwing: error)
                 } else {
@@ -174,7 +177,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
 
     func uploadGlucose(_ glucose: [BloodGlucose]) async {
         guard settingsManager.settings.useAppleHealth,
-              let sampleType = Config.healthBGObject,
+              let sampleType = AppleHealthConfig.healthBGObject,
               checkAvailabilitySave(objectTypeToHealthStore: sampleType),
               glucose.isNotEmpty
         else { return }
@@ -193,7 +196,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
                         HKMetadataKeyExternalUUID: glucoseSample.id,
                         HKMetadataKeySyncIdentifier: glucoseSample.id,
                         HKMetadataKeySyncVersion: 1,
-                        Config.freeAPSMetaKey: true
+                        AppleHealthConfig.freeAPSMetaKey: true
                     ]
                 )
             }
@@ -245,9 +248,9 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
 
     func uploadCarbs(_ carbs: [CarbsEntry]) async {
         guard settingsManager.settings.useAppleHealth,
-              let carbSampleType = Config.healthCarbObject,
-              let fatSampleType = Config.healthFatObject,
-              let proteinSampleType = Config.healthProteinObject,
+              let carbSampleType = AppleHealthConfig.healthCarbObject,
+              let fatSampleType = AppleHealthConfig.healthFatObject,
+              let proteinSampleType = AppleHealthConfig.healthProteinObject,
               checkAvailabilitySave(objectTypeToHealthStore: carbSampleType),
               carbs.isNotEmpty
         else { return }
@@ -272,7 +275,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
                         HKMetadataKeyExternalUUID: id,
                         HKMetadataKeySyncIdentifier: id,
                         HKMetadataKeySyncVersion: 1,
-                        Config.freeAPSMetaKey: true
+                        AppleHealthConfig.freeAPSMetaKey: true
                     ]
                 )
                 samples.append(carbSample)
@@ -288,7 +291,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
                             HKMetadataKeyExternalUUID: id,
                             HKMetadataKeySyncIdentifier: id,
                             HKMetadataKeySyncVersion: 1,
-                            Config.freeAPSMetaKey: true
+                            AppleHealthConfig.freeAPSMetaKey: true
                         ]
                     )
                     samples.append(fatSample)
@@ -305,7 +308,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
                             HKMetadataKeyExternalUUID: id,
                             HKMetadataKeySyncIdentifier: id,
                             HKMetadataKeySyncVersion: 1,
-                            Config.freeAPSMetaKey: true
+                            AppleHealthConfig.freeAPSMetaKey: true
                         ]
                     )
                     samples.append(proteinSample)
@@ -359,7 +362,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
 
     func uploadInsulin(_ insulin: [PumpHistoryEvent]) async {
         guard settingsManager.settings.useAppleHealth,
-              let sampleType = Config.healthInsulinObject,
+              let sampleType = AppleHealthConfig.healthInsulinObject,
               checkAvailabilitySave(objectTypeToHealthStore: sampleType),
               insulin.isNotEmpty
         else { return }
@@ -391,7 +394,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
                         HKMetadataKeySyncIdentifier: insulinSample.id,
                         HKMetadataKeySyncVersion: 1,
                         HKMetadataKeyInsulinDeliveryReason: deliveryReason.rawValue,
-                        Config.freeAPSMetaKey: true
+                        AppleHealthConfig.freeAPSMetaKey: true
                     ]
                 )
             }
@@ -439,7 +442,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
 
     func deleteGlucose(syncID: String) async {
         guard settingsManager.settings.useAppleHealth,
-              let sampleType = Config.healthBGObject,
+              let sampleType = AppleHealthConfig.healthBGObject,
               checkAvailabilitySave(objectTypeToHealthStore: sampleType)
         else { return }
 
@@ -457,6 +460,21 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
         }
     }
 
+    func deleteMealData(byID id: String, sampleType: HKSampleType) async {
+        let predicate = HKQuery.predicateForObjects(
+            withMetadataKey: HKMetadataKeySyncIdentifier,
+            operatorType: .equalTo,
+            value: id
+        )
+
+        do {
+            try await deleteObjects(of: sampleType, predicate: predicate)
+            debug(.service, "Successfully deleted \(sampleType) with syncID: \(id)")
+        } catch {
+            warning(.service, "Failed to delete carbs sample with syncID: \(id)", error: error)
+        }
+    }
+
     private func deleteObjects(of sampleType: HKSampleType, predicate: NSPredicate) async throws {
         try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
             healthKitStore.deleteObjects(of: sampleType, predicate: predicate) { success, _, error in
@@ -474,7 +492,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
     func createBGObserver() {
         guard settingsManager.settings.useAppleHealth else { return }
 
-        guard let bgType = Config.healthBGObject else {
+        guard let bgType = AppleHealthConfig.healthBGObject else {
             warning(.service, "Can not create HealthKit Observer, because unable to get the Blood Glucose type")
             return
         }
@@ -501,7 +519,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
             healthKitStore.disableAllBackgroundDelivery { _, _ in }
             return }
 
-        guard let bgType = Config.healthBGObject else {
+        guard let bgType = AppleHealthConfig.healthBGObject else {
             warning(
                 .service,
                 "Can not create background delivery, because unable to get the Blood Glucose type"
@@ -519,7 +537,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
     }
 
     private func getBloodGlucoseHKQuery(predicate: NSPredicate) -> HKQuery? {
-        guard let sampleType = Config.healthBGObject else { return nil }
+        guard let sampleType = AppleHealthConfig.healthBGObject else { return nil }
 
         let query = HKAnchoredObjectQuery(
             type: sampleType,
@@ -549,7 +567,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
 
         newGlucose += samples
             .compactMap { sample -> HealthKitSample? in
-                let fromTrio = sample.metadata?[Config.freeAPSMetaKey] as? Bool ?? false
+                let fromTrio = sample.metadata?[AppleHealthConfig.freeAPSMetaKey] as? Bool ?? false
                 guard !fromTrio else { return nil }
                 return HealthKitSample(
                     healthKitId: sample.uuid.uuidString,
@@ -622,7 +640,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
 
     func deleteCarbs(syncID: String, fpuID: String) {
         guard settingsManager.settings.useAppleHealth,
-              let sampleType = Config.healthCarbObject,
+              let sampleType = AppleHealthConfig.healthCarbObject,
               checkAvailabilitySave(objectTypeToHealthStore: sampleType)
         else { return }
 
@@ -662,7 +680,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsStoredDeleg
 
     func deleteInsulin(syncID: String) {
         guard settingsManager.settings.useAppleHealth,
-              let sampleType = Config.healthInsulinObject,
+              let sampleType = AppleHealthConfig.healthInsulinObject,
               checkAvailabilitySave(objectTypeToHealthStore: sampleType)
         else { return }