DataTableStateModel.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import CoreData
  2. import SwiftUI
  3. extension DataTable {
  4. final class StateModel: BaseStateModel<Provider> {
  5. @Injected() var broadcaster: Broadcaster!
  6. @Injected() var unlockmanager: UnlockManager!
  7. @Injected() private var storage: FileStorage!
  8. @Injected() var pumpHistoryStorage: PumpHistoryStorage!
  9. @Injected() var healthKitManager: HealthKitManager!
  10. let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
  11. @Published var mode: Mode = .treatments
  12. @Published var treatments: [Treatment] = []
  13. @Published var glucose: [Glucose] = []
  14. @Published var meals: [Treatment] = []
  15. @Published var manualGlucose: Decimal = 0
  16. @Published var maxBolus: Decimal = 0
  17. @Published var externalInsulinAmount: Decimal = 0
  18. @Published var externalInsulinDate = Date()
  19. var units: GlucoseUnits = .mmolL
  20. var historyLayout: HistoryLayout = .twoTabs
  21. override func subscribe() {
  22. units = settingsManager.settings.units
  23. maxBolus = provider.pumpSettings().maxBolus
  24. historyLayout = settingsManager.settings.historyLayout
  25. setupTreatments()
  26. setupGlucose()
  27. broadcaster.register(SettingsObserver.self, observer: self)
  28. broadcaster.register(PumpHistoryObserver.self, observer: self)
  29. broadcaster.register(TempTargetsObserver.self, observer: self)
  30. broadcaster.register(CarbsObserver.self, observer: self)
  31. broadcaster.register(GlucoseObserver.self, observer: self)
  32. }
  33. private func setupTreatments() {
  34. DispatchQueue.global().async {
  35. let units = self.settingsManager.settings.units
  36. let carbs = self.provider.carbs()
  37. .filter { !($0.isFPU ?? false) }
  38. .map {
  39. if let id = $0.id {
  40. return Treatment(
  41. units: units,
  42. type: .carbs,
  43. date: $0.actualDate ?? $0.createdAt,
  44. amount: $0.carbs,
  45. id: id,
  46. fpuID: $0.fpuID,
  47. note: $0.note
  48. )
  49. } else {
  50. return Treatment(
  51. units: units,
  52. type: .carbs,
  53. date: $0.actualDate ?? $0.createdAt,
  54. amount: $0.carbs,
  55. note: $0.note
  56. )
  57. }
  58. }
  59. let fpus = self.provider.fpus()
  60. .filter { $0.isFPU ?? false }
  61. .map {
  62. Treatment(
  63. units: units,
  64. type: .fpus,
  65. date: $0.actualDate ?? $0.createdAt,
  66. amount: $0.carbs,
  67. id: $0.id,
  68. isFPU: $0.isFPU,
  69. fpuID: $0.fpuID,
  70. note: $0.note
  71. )
  72. }
  73. let boluses = self.provider.pumpHistory()
  74. .filter { $0.type == .bolus }
  75. .map {
  76. Treatment(
  77. units: units,
  78. type: .bolus,
  79. date: $0.timestamp,
  80. amount: $0.amount,
  81. idPumpEvent: $0.id,
  82. isSMB: $0.isSMB,
  83. isExternal: $0.isExternal
  84. )
  85. }
  86. let tempBasals = self.provider.pumpHistory()
  87. .filter { $0.type == .tempBasal || $0.type == .tempBasalDuration }
  88. .chunks(ofCount: 2)
  89. .compactMap { chunk -> Treatment? in
  90. let chunk = Array(chunk)
  91. guard chunk.count == 2, chunk[0].type == .tempBasal,
  92. chunk[1].type == .tempBasalDuration else { return nil }
  93. return Treatment(
  94. units: units,
  95. type: .tempBasal,
  96. date: chunk[0].timestamp,
  97. amount: chunk[0].rate ?? 0,
  98. secondAmount: nil,
  99. duration: Decimal(chunk[1].durationMin ?? 0)
  100. )
  101. }
  102. let tempTargets = self.provider.tempTargets()
  103. .map {
  104. Treatment(
  105. units: units,
  106. type: .tempTarget,
  107. date: $0.createdAt,
  108. amount: $0.targetBottom ?? 0,
  109. secondAmount: $0.targetTop,
  110. duration: $0.duration
  111. )
  112. }
  113. let suspend = self.provider.pumpHistory()
  114. .filter { $0.type == .pumpSuspend }
  115. .map {
  116. Treatment(units: units, type: .suspend, date: $0.timestamp)
  117. }
  118. let resume = self.provider.pumpHistory()
  119. .filter { $0.type == .pumpResume }
  120. .map {
  121. Treatment(units: units, type: .resume, date: $0.timestamp)
  122. }
  123. DispatchQueue.main.async {
  124. if self.historyLayout == .threeTabs {
  125. self.treatments = [boluses, tempBasals, tempTargets, suspend, resume]
  126. .flatMap { $0 }
  127. .sorted { $0.date > $1.date }
  128. self.meals = [carbs, fpus]
  129. .flatMap { $0 }
  130. .sorted { $0.date > $1.date }
  131. } else {
  132. self.treatments = [carbs, fpus, boluses, tempBasals, tempTargets, suspend, resume]
  133. .flatMap { $0 }
  134. .sorted { $0.date > $1.date }
  135. }
  136. }
  137. }
  138. }
  139. func setupGlucose() {
  140. DispatchQueue.main.async {
  141. self.glucose = self.provider.glucose().map(Glucose.init)
  142. }
  143. }
  144. func deleteCarbs(_ treatment: Treatment) {
  145. provider.deleteCarbs(treatment)
  146. }
  147. func deleteInsulin(_ treatment: Treatment) {
  148. unlockmanager.unlock()
  149. .sink { _ in } receiveValue: { [weak self] _ in
  150. guard let self = self else { return }
  151. self.provider.deleteInsulin(treatment)
  152. }
  153. .store(in: &lifetime)
  154. }
  155. func deleteGlucose(_ glucose: Glucose) {
  156. let id = glucose.id
  157. provider.deleteGlucose(id: id)
  158. let fetchRequest: NSFetchRequest<NSFetchRequestResult>
  159. fetchRequest = NSFetchRequest(entityName: "Readings")
  160. fetchRequest.predicate = NSPredicate(format: "id == %@", id)
  161. let deleteRequest = NSBatchDeleteRequest(
  162. fetchRequest: fetchRequest
  163. )
  164. deleteRequest.resultType = .resultTypeObjectIDs
  165. do {
  166. let deleteResult = try coredataContext.execute(deleteRequest) as? NSBatchDeleteResult
  167. if let objectIDs = deleteResult?.result as? [NSManagedObjectID] {
  168. NSManagedObjectContext.mergeChanges(
  169. fromRemoteContextSave: [NSDeletedObjectsKey: objectIDs],
  170. into: [coredataContext]
  171. )
  172. }
  173. } catch { /* To do: handle any thrown errors. */ }
  174. // Deletes Manual Glucose
  175. if (glucose.glucose.type ?? "") == GlucoseType.manual.rawValue {
  176. provider.deleteManualGlucose(date: glucose.glucose.dateString)
  177. }
  178. }
  179. func addManualGlucose() {
  180. let glucose = units == .mmolL ? manualGlucose.asMgdL : manualGlucose
  181. let now = Date()
  182. let id = UUID().uuidString
  183. let saveToJSON = BloodGlucose(
  184. _id: id,
  185. direction: nil,
  186. date: Decimal(now.timeIntervalSince1970) * 1000,
  187. dateString: now,
  188. unfiltered: nil,
  189. filtered: nil,
  190. noise: nil,
  191. glucose: Int(glucose),
  192. type: GlucoseType.manual.rawValue
  193. )
  194. provider.glucoseStorage.storeGlucose([saveToJSON])
  195. debug(.default, "Manual Glucose saved to glucose.json")
  196. // Save to Health
  197. var saveToHealth = [BloodGlucose]()
  198. saveToHealth.append(saveToJSON)
  199. }
  200. func addExternalInsulin() {
  201. guard externalInsulinAmount > 0 else {
  202. showModal(for: nil)
  203. return
  204. }
  205. externalInsulinAmount = min(externalInsulinAmount, maxBolus * 3) // Allow for 3 * Max Bolus for external insulin
  206. unlockmanager.unlock()
  207. .sink { _ in } receiveValue: { [weak self] _ in
  208. guard let self = self else { return }
  209. pumpHistoryStorage.storeEvents(
  210. [
  211. PumpHistoryEvent(
  212. id: UUID().uuidString,
  213. type: .bolus,
  214. timestamp: externalInsulinDate,
  215. amount: externalInsulinAmount,
  216. duration: nil,
  217. durationMin: nil,
  218. rate: nil,
  219. temp: nil,
  220. carbInput: nil,
  221. isExternal: true
  222. )
  223. ]
  224. )
  225. debug(.default, "External insulin saved to pumphistory.json")
  226. // Reset amount to 0 for next entry.
  227. externalInsulinAmount = 0
  228. }
  229. .store(in: &lifetime)
  230. }
  231. }
  232. }
  233. extension DataTable.StateModel:
  234. SettingsObserver,
  235. PumpHistoryObserver,
  236. TempTargetsObserver,
  237. CarbsObserver,
  238. GlucoseObserver
  239. {
  240. func settingsDidChange(_: FreeAPSSettings) {
  241. setupTreatments()
  242. }
  243. func pumpHistoryDidUpdate(_: [PumpHistoryEvent]) {
  244. setupTreatments()
  245. }
  246. func tempTargetsDidUpdate(_: [TempTarget]) {
  247. setupTreatments()
  248. }
  249. func carbsDidUpdate(_: [CarbsEntry]) {
  250. setupTreatments()
  251. }
  252. func glucoseDidUpdate(_: [BloodGlucose]) {
  253. setupGlucose()
  254. }
  255. }