StatStateModel.swift 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import CoreData
  2. import Foundation
  3. import Observation
  4. import SwiftUI
  5. import Swinject
  6. extension Stat {
  7. @Observable final class StateModel: BaseStateModel<Provider> {
  8. @ObservationIgnored @Injected() var settings: SettingsManager!
  9. var highLimit: Decimal = 180
  10. var lowLimit: Decimal = 70
  11. var hbA1cDisplayUnit: HbA1cDisplayUnit = .percent
  12. var timeInRangeChartStyle: TimeInRangeChartStyle = .vertical
  13. var units: GlucoseUnits = .mgdL
  14. var glucoseFromPersistence: [GlucoseStored] = []
  15. var loopStatRecords: [LoopStatRecord] = []
  16. var groupedLoopStats: [LoopStatsByPeriod] = []
  17. var mealStats: [MealStats] = []
  18. var tddStats: [TDD] = []
  19. var bolusStats: [BolusStats] = []
  20. var selectedDurationForGlucoseStats: Duration = .Today {
  21. didSet {
  22. setupGlucoseArray(for: selectedDurationForGlucoseStats)
  23. }
  24. }
  25. var selectedDurationForInsulinStats: StatsTimeInterval = .Day
  26. var selectedDurationForMealStats: StatsTimeInterval = .Day
  27. var selectedDurationForLoopStats: Duration = .Today {
  28. didSet {
  29. setupLoopStatRecords()
  30. }
  31. }
  32. let context = CoreDataStack.shared.newTaskContext()
  33. let viewContext = CoreDataStack.shared.persistentContainer.viewContext
  34. let determinationFetchContext = CoreDataStack.shared.newTaskContext()
  35. let loopTaskContext = CoreDataStack.shared.newTaskContext()
  36. let mealTaskContext = CoreDataStack.shared.newTaskContext()
  37. let bolusTaskContext = CoreDataStack.shared.newTaskContext()
  38. enum Duration: String, CaseIterable, Identifiable {
  39. case Today
  40. case Day = "D"
  41. case Week = "W"
  42. case Month = "M"
  43. case Total = "3 M"
  44. var id: Self { self }
  45. }
  46. enum StatsTimeInterval: String, CaseIterable, Identifiable {
  47. case Day = "D"
  48. case Week = "W"
  49. case Month = "M"
  50. case Total = "3 M"
  51. var id: Self { self }
  52. }
  53. var hourlyStats: [HourlyStats] = []
  54. var glucoseRangeStats: [GlucoseRangeStats] = []
  55. override func subscribe() {
  56. setupGlucoseArray(for: .Today)
  57. setupTDDs()
  58. setupBolusStats()
  59. setupLoopStatRecords()
  60. setupMealStats()
  61. highLimit = settingsManager.settings.high
  62. lowLimit = settingsManager.settings.low
  63. units = settingsManager.settings.units
  64. hbA1cDisplayUnit = settingsManager.settings.hbA1cDisplayUnit
  65. timeInRangeChartStyle = settingsManager.settings.timeInRangeChartStyle
  66. }
  67. func setupGlucoseArray(for duration: Duration) {
  68. Task {
  69. let ids = await fetchGlucose(for: duration)
  70. await updateGlucoseArray(with: ids)
  71. // Calculate hourly stats and glucose range stats asynchronously with fetched glucose IDs
  72. async let hourlyStats: () = calculateHourlyStatsForGlucoseAreaChart(from: ids)
  73. async let glucoseRangeStats: () = calculateGlucoseRangeStatsForStackedChart(from: ids)
  74. _ = await (hourlyStats, glucoseRangeStats)
  75. }
  76. }
  77. private func fetchGlucose(for duration: Duration) async -> [NSManagedObjectID] {
  78. let predicate: NSPredicate
  79. switch duration {
  80. case .Day:
  81. predicate = NSPredicate.glucoseForStatsDay
  82. case .Week:
  83. predicate = NSPredicate.glucoseForStatsWeek
  84. case .Today:
  85. predicate = NSPredicate.glucoseForStatsToday
  86. case .Month:
  87. predicate = NSPredicate.glucoseForStatsMonth
  88. case .Total:
  89. predicate = NSPredicate.glucoseForStatsTotal
  90. }
  91. let results = await CoreDataStack.shared.fetchEntitiesAsync(
  92. ofType: GlucoseStored.self,
  93. onContext: context,
  94. predicate: predicate,
  95. key: "date",
  96. ascending: false,
  97. batchSize: 100,
  98. propertiesToFetch: ["glucose", "objectID"]
  99. )
  100. return await context.perform {
  101. guard let fetchedResults = results as? [[String: Any]] else { return [] }
  102. return fetchedResults.compactMap { $0["objectID"] as? NSManagedObjectID }
  103. }
  104. }
  105. @MainActor private func updateGlucoseArray(with IDs: [NSManagedObjectID]) {
  106. do {
  107. let glucoseObjects = try IDs.compactMap { id in
  108. try viewContext.existingObject(with: id) as? GlucoseStored
  109. }
  110. glucoseFromPersistence = glucoseObjects
  111. } catch {
  112. debugPrint(
  113. "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the glucose array: \(error.localizedDescription)"
  114. )
  115. }
  116. }
  117. }
  118. @Observable final class UpdateTimer {
  119. private var workItem: DispatchWorkItem?
  120. func scheduleUpdate(action: @escaping () -> Void) {
  121. workItem?.cancel()
  122. let newWorkItem = DispatchWorkItem {
  123. Task { @MainActor in
  124. action()
  125. }
  126. }
  127. workItem = newWorkItem
  128. DispatchQueue.main.asyncAfter(deadline: .now() + 0.05, execute: newWorkItem)
  129. }
  130. }
  131. }