Просмотр исходного кода

fix calendar + address PR feedback

polscm32 aka Marvout 1 год назад
Родитель
Сommit
e2be6a7375

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

@@ -79,7 +79,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
                         glucoseEntry.id = UUID()
                         glucoseEntry.glucose = Int16(entry.glucose ?? 0)
                         glucoseEntry.date = entry.dateString
-                        glucoseEntry.direction = entry.direction?.symbol
+                        glucoseEntry.direction = entry.direction?.rawValue
                         glucoseEntry.isUploadedToNS = false /// the value is not uploaded to NS (yet)
                         debugPrint("\(DebuggingIdentifiers.failed)")
                         debugPrint("\(String(describing: glucoseEntry.direction))")

+ 1 - 1
FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -248,7 +248,7 @@ extension DataTable {
                             if glucose.isManual {
                                 Image(systemName: "drop.fill").symbolRenderingMode(.monochrome).foregroundStyle(.red)
                             } else {
-                                Text("\(glucose.direction ?? "--")")
+                                Text("\(glucose.directionEnum?.symbol ?? "--")")
                             }
 
                             Spacer()

+ 169 - 127
FreeAPS/Sources/Services/Calendar/CalendarManager.swift

@@ -7,7 +7,7 @@ protocol CalendarManager {
     func requestAccessIfNeeded() -> AnyPublisher<Bool, Never>
     func calendarIDs() -> [String]
     var currentCalendarID: String? { get set }
-    func createEvent(for glucose: GlucoseStored, delta: Int)
+    func createEvent() async
 }
 
 final class BaseCalendarManager: CalendarManager, Injectable {
@@ -19,13 +19,87 @@ final class BaseCalendarManager: CalendarManager, Injectable {
     @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var storage: FileStorage!
 
+    private var coreDataObserver: CoreDataObserver?
+
+    private var glucoseFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        if settingsManager.settings.units == .mmolL {
+            formatter.minimumFractionDigits = 1
+            formatter.maximumFractionDigits = 1
+        }
+        formatter.roundingMode = .halfUp
+        return formatter
+    }
+
+    private var deltaFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 1
+        formatter.positivePrefix = "+"
+        return formatter
+    }
+
+    private var iobFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 1
+        return formatter
+    }
+
+    private var cobFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        return formatter
+    }
+
     init(resolver: Resolver) {
         injectServices(resolver)
-        broadcaster.register(GlucoseObserver.self, observer: self)
-        setupGlucose()
+        setupCurrentCalendar()
+        Task {
+            await createEvent()
+        }
+        coreDataObserver = CoreDataObserver()
+        registerHandlers()
+        setupGlucoseNotification()
+    }
+
+    let backgroundContext = CoreDataStack.shared.newTaskContext()
+    let viewContext = CoreDataStack.shared.persistentContainer.viewContext
+
+    private func setupCurrentCalendar() {
+        let calendars = eventStore.calendars(for: .event)
+        if let defaultCalendar = calendars.first {
+            currentCalendarID = defaultCalendar.title
+        }
+    }
+
+    private func registerHandlers() {
+        coreDataObserver?.registerHandler(for: "GlucoseStored") { [weak self] in
+            guard let self = self else { return }
+            Task {
+                await self.createEvent()
+            }
+        }
     }
 
-    let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
+    private func setupGlucoseNotification() {
+        /// custom notification that is sent when a batch insert of glucose objects is done
+        Foundation.NotificationCenter.default.addObserver(
+            self,
+            selector: #selector(handleBatchInsert),
+            name: .didPerformBatchInsert,
+            object: nil
+        )
+    }
+
+    @objc private func handleBatchInsert() {
+        Task {
+            await createEvent()
+        }
+    }
 
     func requestAccessIfNeeded() -> AnyPublisher<Bool, Never> {
         Future { promise in
@@ -88,92 +162,123 @@ final class BaseCalendarManager: CalendarManager, Injectable {
         EKEventStore().calendars(for: .event).map(\.title)
     }
 
-    private func getLastDetermination() -> [OrefDetermination] {
-        CoreDataStack.shared.fetchEntities(
+    private func getLastDetermination() async -> NSManagedObjectID? {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OrefDetermination.self,
-            onContext: coredataContext,
+            onContext: backgroundContext,
             predicate: NSPredicate.predicateFor30MinAgoForDetermination,
             key: "timestamp",
             ascending: false,
             fetchLimit: 1,
             propertiesToFetch: ["timestamp", "cob", "iob"]
         )
+        return await backgroundContext.perform {
+            results.first.map(\.objectID)
+        }
     }
 
-    func createEvent(for glucose: GlucoseStored, delta: Int) {
-        guard settingsManager.settings.useCalendar else { return }
-
-        guard let calendar = currentCalendar else { return }
-
-        deleteAllEvents(in: calendar)
-
-        let glucoseValue = glucose.glucose
+    private func fetchGlucose() async -> [NSManagedObjectID] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: GlucoseStored.self,
+            onContext: backgroundContext,
+            predicate: NSPredicate.predicateFor30MinAgo,
+            key: "date",
+            ascending: false
+        )
+        return await backgroundContext.perform {
+            return results.map(\.objectID)
+        }
+    }
 
-        // create an event now
-        let event = EKEvent(eventStore: eventStore)
+    @MainActor func createEvent() async {
+        guard settingsManager.settings.useCalendar, let calendar = currentCalendar,
+              let determinationId = await getLastDetermination() else { return }
 
-        // Calendar settings
-        let displeyCOBandIOB = settingsManager.settings.displayCalendarIOBandCOB
-        let displayEmojis = settingsManager.settings.displayCalendarEmojis
+        let glucoseIds = await fetchGlucose()
 
-        // Latest Loop data
-        var freshLoop: Double = 20
-        var lastLoop: Date?
-        if displeyCOBandIOB || displayEmojis {
-            lastLoop = getLastDetermination().first?.timestamp
-            freshLoop = -1 * (lastLoop?.timeIntervalSinceNow.minutes ?? 0)
-        }
+        deleteAllEvents(in: calendar)
 
-        var glucoseIcon = "🟢"
-        if displayEmojis {
-            glucoseIcon = Double(glucoseValue) <= Double(settingsManager.settings.low) ? "🔴" : glucoseIcon
-            glucoseIcon = Double(glucoseValue) >= Double(settingsManager.settings.high) ? "🟠" : glucoseIcon
-            glucoseIcon = freshLoop > 15 ? "🚫" : glucoseIcon
-        }
+        do {
+            guard let determinationObject = try viewContext.existingObject(with: determinationId) as? OrefDetermination
+            else { return }
 
-        let glucoseText = glucoseFormatter
-            .string(from: Double(
-                settingsManager.settings.units == .mmolL ? Int(glucoseValue)
-                    .asMmolL : Decimal(glucoseValue)
-            ) as NSNumber)!
+            let glucoseObjects = try glucoseIds.compactMap { id in
+                try viewContext.existingObject(with: id) as? GlucoseStored
+            }
 
-        let directionText = glucose.direction ?? "↔︎"
+            guard let lastGlucoseObject = glucoseObjects.first, let lastGlucoseValue = glucoseObjects.first?.glucose,
+                  let secondLastReading = glucoseObjects.dropFirst().first?.glucose else { return }
 
-        let deltaValue = settingsManager.settings.units == .mmolL ? Int(delta.asMmolL) : delta
-        let deltaText = deltaFormatter.string(from: NSNumber(value: deltaValue)) ?? "--"
+            let delta = Decimal(lastGlucoseValue) - Decimal(secondLastReading)
 
-        let iobText = iobFormatter.string(from: (getLastDetermination().first?.iob ?? 0) as NSNumber) ?? ""
-        let cobText = cobFormatter.string(from: (getLastDetermination().first?.cob ?? 0) as NSNumber) ?? ""
+            // create an event now
+            let event = EKEvent(eventStore: eventStore)
 
-        var glucoseDisplayText = displayEmojis ? glucoseIcon + " " : ""
-        glucoseDisplayText += glucoseText + " " + directionText + " " + deltaText
+            // Calendar settings
+            let displayCOBandIOB = settingsManager.settings.displayCalendarIOBandCOB
+            let displayEmojis = settingsManager.settings.displayCalendarEmojis
 
-        var iobDisplayText = ""
-        var cobDisplayText = ""
+            // Latest Loop data
+            var freshLoop: Double = 20
+            var lastLoop: Date?
+            if displayCOBandIOB || displayEmojis {
+                lastLoop = determinationObject.timestamp
+                freshLoop = -1 * (lastLoop?.timeIntervalSinceNow.minutes ?? 0)
+            }
 
-        if displeyCOBandIOB {
+            var glucoseIcon = "🟢"
             if displayEmojis {
-                iobDisplayText += "💉"
-                cobDisplayText += "🥨"
-            } else {
-                iobDisplayText += "IOB:"
-                cobDisplayText += "COB:"
+                glucoseIcon = Double(lastGlucoseValue) <= Double(settingsManager.settings.low) ? "🔴" : glucoseIcon
+                glucoseIcon = Double(lastGlucoseValue) >= Double(settingsManager.settings.high) ? "🟠" : glucoseIcon
+                glucoseIcon = freshLoop > 15 ? "🚫" : glucoseIcon
             }
-            iobDisplayText += " " + iobText
-            cobDisplayText += " " + cobText
-            event.location = iobDisplayText + " " + cobDisplayText
-        }
 
-        event.title = glucoseDisplayText
-        event.notes = "Trio"
-        event.startDate = Date()
-        event.endDate = Date(timeIntervalSinceNow: 60 * 10)
-        event.calendar = calendar
+            let glucoseText = glucoseFormatter
+                .string(from: Double(
+                    settingsManager.settings.units == .mmolL ? Int(lastGlucoseValue)
+                        .asMmolL : Decimal(lastGlucoseValue)
+                ) as NSNumber)!
+            debugPrint("\(DebuggingIdentifiers.failed) glucose text: \(glucoseText)")
+
+            let directionText = lastGlucoseObject.directionEnum?.symbol ?? "↔︎"
+
+            let deltaValue = settingsManager.settings.units == .mmolL ? Int(delta.asMmolL) : Int(delta)
+            let deltaText = deltaFormatter.string(from: NSNumber(value: deltaValue)) ?? "--"
+
+            let iobText = iobFormatter.string(from: (determinationObject.iob ?? 0) as NSNumber) ?? ""
+            let cobText = cobFormatter.string(from: determinationObject.cob as NSNumber) ?? ""
+
+            var glucoseDisplayText = displayEmojis ? glucoseIcon + " " : ""
+            glucoseDisplayText += glucoseText + " " + directionText + " " + deltaText
+
+            var iobDisplayText = ""
+            var cobDisplayText = ""
+
+            if displayCOBandIOB {
+                if displayEmojis {
+                    iobDisplayText += "💉"
+                    cobDisplayText += "🥨"
+                } else {
+                    iobDisplayText += "IOB:"
+                    cobDisplayText += "COB:"
+                }
+                iobDisplayText += " " + iobText
+                cobDisplayText += " " + cobText
+                event.location = iobDisplayText + " " + cobDisplayText
+            }
+
+            event.title = glucoseDisplayText
+            event.notes = "Trio"
+            event.startDate = Date()
+            event.endDate = Date(timeIntervalSinceNow: 60 * 10)
+            event.calendar = calendar
 
-        do {
             try eventStore.save(event, span: .thisEvent)
+
         } catch {
-            warning(.service, "Cannot create calendar event", error: error)
+            debugPrint(
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to create calendar event: \(error.localizedDescription)"
+            )
         }
     }
 
@@ -200,69 +305,6 @@ final class BaseCalendarManager: CalendarManager, Injectable {
             }
         }
     }
-
-    private var glucoseFormatter: NumberFormatter {
-        let formatter = NumberFormatter()
-        formatter.numberStyle = .decimal
-        formatter.maximumFractionDigits = 0
-        if settingsManager.settings.units == .mmolL {
-            formatter.minimumFractionDigits = 1
-            formatter.maximumFractionDigits = 1
-        }
-        formatter.roundingMode = .halfUp
-        return formatter
-    }
-
-    private var deltaFormatter: NumberFormatter {
-        let formatter = NumberFormatter()
-        formatter.numberStyle = .decimal
-        formatter.maximumFractionDigits = 1
-        formatter.positivePrefix = "+"
-        return formatter
-    }
-
-    private var iobFormatter: NumberFormatter {
-        let formatter = NumberFormatter()
-        formatter.numberStyle = .decimal
-        formatter.maximumFractionDigits = 1
-        return formatter
-    }
-
-    private var cobFormatter: NumberFormatter {
-        let formatter = NumberFormatter()
-        formatter.numberStyle = .decimal
-        formatter.maximumFractionDigits = 0
-        return formatter
-    }
-
-    private func setupGlucose() {
-        coredataContext.performAndWait {
-            let results = CoreDataStack.shared.fetchEntities(
-                ofType: GlucoseStored.self,
-                onContext: coredataContext,
-                predicate: NSPredicate.predicateFor30MinAgo,
-                key: "date",
-                ascending: false
-            )
-
-            guard results.count >= 2 else { return }
-
-            if let lastGlucose = results.first,
-               let secondLastReading = results.dropFirst().first?.glucose
-            {
-                let glucoseDelta = lastGlucose.glucose - secondLastReading
-                self.createEvent(for: lastGlucose, delta: Int(glucoseDelta))
-            } else {
-                debugPrint("Failed to unwrap necessary glucose readings")
-            }
-        }
-    }
-}
-
-extension BaseCalendarManager: GlucoseObserver {
-    func glucoseDidUpdate(_: [BloodGlucose]) {
-        setupGlucose()
-    }
 }
 
 extension BloodGlucose.Direction {