DataTableStateModel.swift 12 KB

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