DataTableStateModel.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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) async {
  148. do {
  149. let authenticated = try await unlockmanager.unlock()
  150. if authenticated {
  151. provider.deleteInsulin(treatment)
  152. } else {
  153. print("authentication failed")
  154. }
  155. } catch {
  156. print("authentication error: \(error.localizedDescription)")
  157. }
  158. }
  159. func deleteGlucose(_ glucose: Glucose) {
  160. let id = glucose.id
  161. provider.deleteGlucose(id: id)
  162. let fetchRequest: NSFetchRequest<NSFetchRequestResult>
  163. fetchRequest = NSFetchRequest(entityName: "Readings")
  164. fetchRequest.predicate = NSPredicate(format: "id == %@", id)
  165. let deleteRequest = NSBatchDeleteRequest(
  166. fetchRequest: fetchRequest
  167. )
  168. deleteRequest.resultType = .resultTypeObjectIDs
  169. do {
  170. let deleteResult = try coredataContext.execute(deleteRequest) as? NSBatchDeleteResult
  171. if let objectIDs = deleteResult?.result as? [NSManagedObjectID] {
  172. NSManagedObjectContext.mergeChanges(
  173. fromRemoteContextSave: [NSDeletedObjectsKey: objectIDs],
  174. into: [coredataContext]
  175. )
  176. }
  177. } catch { /* To do: handle any thrown errors. */ }
  178. // Deletes Manual Glucose
  179. if (glucose.glucose.type ?? "") == GlucoseType.manual.rawValue {
  180. provider.deleteManualGlucose(date: glucose.glucose.dateString)
  181. }
  182. }
  183. func addManualGlucose() {
  184. let glucose = units == .mmolL ? manualGlucose.asMgdL : manualGlucose
  185. let now = Date()
  186. let id = UUID().uuidString
  187. let saveToJSON = BloodGlucose(
  188. _id: id,
  189. direction: nil,
  190. date: Decimal(now.timeIntervalSince1970) * 1000,
  191. dateString: now,
  192. unfiltered: nil,
  193. filtered: nil,
  194. noise: nil,
  195. glucose: Int(glucose),
  196. type: GlucoseType.manual.rawValue
  197. )
  198. provider.glucoseStorage.storeGlucose([saveToJSON])
  199. debug(.default, "Manual Glucose saved to glucose.json")
  200. // Save to Health
  201. var saveToHealth = [BloodGlucose]()
  202. saveToHealth.append(saveToJSON)
  203. }
  204. func addExternalInsulin() async {
  205. guard externalInsulinAmount > 0 else {
  206. showModal(for: nil)
  207. return
  208. }
  209. externalInsulinAmount = min(externalInsulinAmount, maxBolus * 3)
  210. do {
  211. let authenticated = try await unlockmanager.unlock()
  212. if authenticated {
  213. storeExternalInsulinEvent()
  214. } else {
  215. print("authentication failed")
  216. }
  217. } catch {
  218. print("authentication error: \(error.localizedDescription)")
  219. }
  220. }
  221. private func storeExternalInsulinEvent() {
  222. pumpHistoryStorage.storeEvents(
  223. [
  224. PumpHistoryEvent(
  225. id: UUID().uuidString,
  226. type: .bolus,
  227. timestamp: externalInsulinDate,
  228. amount: externalInsulinAmount,
  229. duration: nil,
  230. durationMin: nil,
  231. rate: nil,
  232. temp: nil,
  233. carbInput: nil,
  234. isExternal: true
  235. )
  236. ]
  237. )
  238. debug(.default, "External insulin saved to pumphistory.json")
  239. // Reset amount to 0 for next entry.
  240. externalInsulinAmount = 0
  241. }
  242. }
  243. }
  244. extension DataTable.StateModel:
  245. SettingsObserver,
  246. PumpHistoryObserver,
  247. TempTargetsObserver,
  248. CarbsObserver,
  249. GlucoseObserver
  250. {
  251. func settingsDidChange(_: FreeAPSSettings) {
  252. setupTreatments()
  253. }
  254. func pumpHistoryDidUpdate(_: [PumpHistoryEvent]) {
  255. setupTreatments()
  256. }
  257. func tempTargetsDidUpdate(_: [TempTarget]) {
  258. setupTreatments()
  259. }
  260. func carbsDidUpdate(_: [CarbsEntry]) {
  261. setupTreatments()
  262. }
  263. func glucoseDidUpdate(_: [BloodGlucose]) {
  264. setupGlucose()
  265. }
  266. }