MasterViewController.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. //
  2. // MasterViewController.swift
  3. // LoopKit Example
  4. //
  5. // Created by Nathan Racklyeft on 2/24/16.
  6. // Copyright © 2016 Nathan Racklyeft. All rights reserved.
  7. //
  8. import UIKit
  9. import LoopKit
  10. import LoopKitUI
  11. import HealthKit
  12. class MasterViewController: UITableViewController {
  13. private var dataManager: DeviceDataManager? = DeviceDataManager()
  14. override func viewDidAppear(_ animated: Bool) {
  15. super.viewDidAppear(animated)
  16. guard let dataManager = dataManager else {
  17. return
  18. }
  19. let sampleTypes = Set([
  20. dataManager.glucoseStore.sampleType,
  21. dataManager.carbStore.sampleType,
  22. dataManager.doseStore.sampleType,
  23. ].compactMap { $0 })
  24. if dataManager.glucoseStore.authorizationRequired ||
  25. dataManager.carbStore.authorizationRequired ||
  26. dataManager.doseStore.authorizationRequired
  27. {
  28. dataManager.carbStore.healthStore.requestAuthorization(toShare: sampleTypes, read: sampleTypes) { (success, error) in
  29. if success {
  30. // Call the individual authorization methods to trigger query creation
  31. dataManager.carbStore.authorize({ _ in })
  32. dataManager.doseStore.insulinDeliveryStore.authorize(toShare: true, { _ in })
  33. dataManager.glucoseStore.authorize({ _ in })
  34. }
  35. }
  36. }
  37. }
  38. // MARK: - Data Source
  39. private enum Section: Int, CaseIterable {
  40. case data
  41. case configuration
  42. }
  43. private enum DataRow: Int, CaseIterable {
  44. case carbs = 0
  45. case reservoir
  46. case diagnostic
  47. case generate
  48. case reset
  49. }
  50. private enum ConfigurationRow: Int, CaseIterable {
  51. case basalRate
  52. case carbRatio
  53. case correctionRange
  54. case insulinSensitivity
  55. case pumpID
  56. }
  57. // MARK: UITableViewDataSource
  58. override func numberOfSections(in tableView: UITableView) -> Int {
  59. return Section.allCases.count
  60. }
  61. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  62. switch Section(rawValue: section)! {
  63. case .configuration:
  64. return ConfigurationRow.allCases.count
  65. case .data:
  66. return DataRow.allCases.count
  67. }
  68. }
  69. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  70. let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
  71. switch Section(rawValue: indexPath.section)! {
  72. case .configuration:
  73. switch ConfigurationRow(rawValue: indexPath.row)! {
  74. case .basalRate:
  75. cell.textLabel?.text = LocalizedString("Basal Rates", comment: "The title text for the basal rate schedule")
  76. case .carbRatio:
  77. cell.textLabel?.text = LocalizedString("Carb Ratios", comment: "The title of the carb ratios schedule screen")
  78. case .correctionRange:
  79. cell.textLabel?.text = LocalizedString("Correction Range", comment: "The title text for the glucose correction range schedule")
  80. case .insulinSensitivity:
  81. cell.textLabel?.text = LocalizedString("Insulin Sensitivity", comment: "The title text for the insulin sensitivity schedule")
  82. case .pumpID:
  83. cell.textLabel?.text = LocalizedString("Pump ID", comment: "The title text for the pump ID")
  84. }
  85. case .data:
  86. switch DataRow(rawValue: indexPath.row)! {
  87. case .carbs:
  88. cell.textLabel?.text = LocalizedString("Carbs", comment: "The title for the cell navigating to the carbs screen")
  89. case .reservoir:
  90. cell.textLabel?.text = LocalizedString("Reservoir", comment: "The title for the cell navigating to the reservoir screen")
  91. case .diagnostic:
  92. cell.textLabel?.text = LocalizedString("Diagnostic", comment: "The title for the cell displaying diagnostic data")
  93. case .generate:
  94. cell.textLabel?.text = LocalizedString("Generate Data", comment: "The title for the cell displaying data generation")
  95. case .reset:
  96. cell.textLabel?.text = LocalizedString("Reset", comment: "Title for the cell resetting the data manager")
  97. }
  98. }
  99. return cell
  100. }
  101. // MARK: - UITableViewDelegate
  102. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  103. let sender = tableView.cellForRow(at: indexPath)
  104. switch Section(rawValue: indexPath.section)! {
  105. case .configuration:
  106. let row = ConfigurationRow(rawValue: indexPath.row)!
  107. switch row {
  108. // case .basalRate:
  109. //
  110. // // x22 with max basal rate of 5U/hr
  111. // let pulsesPerUnit = 20
  112. // let basalRates = (1...100).map { Double($0) / Double(pulsesPerUnit) }
  113. //
  114. // // full x23 rates
  115. // let rateGroup1 = ((1...39).map { Double($0) / Double(40) })
  116. // let rateGroup2 = ((20...199).map { Double($0) / Double(20) })
  117. // let rateGroup3 = ((100...350).map { Double($0) / Double(10) })
  118. // let basalRates = rateGroup1 + rateGroup2 + rateGroup3
  119. // let scheduleVC = BasalScheduleTableViewController(allowedBasalRates: basalRates, maximumScheduleItemCount: 5, minimumTimeInterval: .minutes(30))
  120. //
  121. // if let profile = dataManager?.basalRateSchedule {
  122. // scheduleVC.timeZone = profile.timeZone
  123. //
  124. //
  125. // scheduleVC.scheduleItems = profile.items
  126. // }
  127. // scheduleVC.delegate = self
  128. // scheduleVC.title = sender?.textLabel?.text
  129. // scheduleVC.syncSource = self
  130. //
  131. // show(scheduleVC, sender: sender)
  132. case .carbRatio:
  133. let scheduleVC = DailyQuantityScheduleTableViewController()
  134. scheduleVC.delegate = self
  135. scheduleVC.title = NSLocalizedString("Carb Ratios", comment: "The title of the carb ratios schedule screen")
  136. scheduleVC.unit = .gram()
  137. if let schedule = dataManager?.carbRatioSchedule {
  138. scheduleVC.timeZone = schedule.timeZone
  139. scheduleVC.scheduleItems = schedule.items
  140. scheduleVC.unit = schedule.unit
  141. }
  142. show(scheduleVC, sender: sender)
  143. case .correctionRange:
  144. var therapySettings = TherapySettings()
  145. therapySettings.glucoseTargetRangeSchedule = self.dataManager?.glucoseTargetRangeSchedule
  146. let therapySettingsViewModel = TherapySettingsViewModel(therapySettings: therapySettings)
  147. let view = CorrectionRangeScheduleEditor(mode: .settings, therapySettingsViewModel: therapySettingsViewModel, didSave: {
  148. self.dataManager?.glucoseTargetRangeSchedule = therapySettingsViewModel.therapySettings.glucoseTargetRangeSchedule
  149. self.navigationController?.popToViewController(self, animated: true)
  150. })
  151. .environmentObject(DisplayGlucoseUnitObservable(displayGlucoseUnit: .milligramsPerDeciliter))
  152. let scheduleVC = DismissibleHostingController(rootView: view, dismissalMode: .pop(to: type(of: self)), isModalInPresentation: false)
  153. show(scheduleVC, sender: sender)
  154. case .insulinSensitivity:
  155. let unit = dataManager?.insulinSensitivitySchedule?.unit ?? dataManager?.glucoseStore.preferredUnit ?? HKUnit.milligramsPerDeciliter
  156. let scheduleVC = InsulinSensitivityScheduleViewController(allowedValues: unit.allowedSensitivityValues, unit: unit)
  157. scheduleVC.unit = unit
  158. scheduleVC.delegate = self
  159. scheduleVC.insulinSensitivityScheduleStorageDelegate = self
  160. scheduleVC.schedule = dataManager?.insulinSensitivitySchedule
  161. scheduleVC.title = NSLocalizedString("Insulin Sensitivity", comment: "The title of the insulin sensitivity schedule screen")
  162. show(scheduleVC, sender: sender)
  163. case .pumpID:
  164. let textFieldVC = TextFieldTableViewController()
  165. // textFieldVC.delegate = self
  166. textFieldVC.title = sender?.textLabel?.text
  167. textFieldVC.placeholder = LocalizedString("Enter the 6-digit pump ID", comment: "The placeholder text instructing users how to enter a pump ID")
  168. textFieldVC.value = dataManager?.pumpID
  169. textFieldVC.keyboardType = .numberPad
  170. textFieldVC.contextHelp = LocalizedString("The pump ID can be found printed on the back, or near the bottom of the STATUS/Esc screen. It is the strictly numerical portion of the serial number (shown as SN or S/N).", comment: "Instructions on where to find the pump ID on a Minimed pump")
  171. show(textFieldVC, sender: sender)
  172. default:
  173. break
  174. }
  175. case .data:
  176. switch DataRow(rawValue: indexPath.row)! {
  177. case .carbs:
  178. performSegue(withIdentifier: CarbEntryTableViewController.className, sender: sender)
  179. case .reservoir:
  180. performSegue(withIdentifier: LegacyInsulinDeliveryTableViewController.className, sender: sender)
  181. case .diagnostic:
  182. let vc = CommandResponseViewController(command: { [weak self] (completionHandler) -> String in
  183. let group = DispatchGroup()
  184. guard let dataManager = self?.dataManager else {
  185. completionHandler("")
  186. return "nil"
  187. }
  188. var doseStoreResponse = ""
  189. group.enter()
  190. dataManager.doseStore.generateDiagnosticReport { (report) in
  191. doseStoreResponse = report
  192. group.leave()
  193. }
  194. var carbStoreResponse = ""
  195. if let carbStore = dataManager.carbStore {
  196. group.enter()
  197. carbStore.generateDiagnosticReport { (report) in
  198. carbStoreResponse = report
  199. group.leave()
  200. }
  201. }
  202. var glucoseStoreResponse = ""
  203. group.enter()
  204. dataManager.glucoseStore.generateDiagnosticReport { (report) in
  205. glucoseStoreResponse = report
  206. group.leave()
  207. }
  208. group.notify(queue: DispatchQueue.main) {
  209. completionHandler([
  210. doseStoreResponse,
  211. carbStoreResponse,
  212. glucoseStoreResponse
  213. ].joined(separator: "\n\n"))
  214. }
  215. return "…"
  216. })
  217. vc.title = "Diagnostic"
  218. show(vc, sender: sender)
  219. case .generate:
  220. let vc = CommandResponseViewController(command: { [weak self] (completionHandler) -> String in
  221. guard let dataManager = self?.dataManager else {
  222. completionHandler("")
  223. return "dataManager is nil"
  224. }
  225. let group = DispatchGroup()
  226. var unitVolume = 150.0
  227. reservoir: for index in sequence(first: TimeInterval(hours: -6), next: { $0 + .minutes(5) }) {
  228. guard index < 0 else {
  229. break reservoir
  230. }
  231. unitVolume -= (drand48() * 2.0)
  232. group.enter()
  233. dataManager.doseStore.addReservoirValue(unitVolume, at: Date(timeIntervalSinceNow: index)) { (_, _, _, error) in
  234. group.leave()
  235. }
  236. }
  237. group.enter()
  238. dataManager.glucoseStore.addGlucoseSamples([NewGlucoseSample(date: Date(), quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 101), condition: nil, trend: nil, trendRate: nil, isDisplayOnly: false, wasUserEntered: false, syncIdentifier: UUID().uuidString)], completion: { (result) in
  239. group.leave()
  240. })
  241. group.notify(queue: .main) {
  242. completionHandler("Completed")
  243. }
  244. return "Generating…"
  245. })
  246. vc.title = sender?.textLabel?.text
  247. show(vc, sender: sender)
  248. case .reset:
  249. dataManager = nil
  250. tableView.reloadData()
  251. }
  252. }
  253. }
  254. // MARK: - Segues
  255. override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  256. super.prepare(for: segue, sender: sender)
  257. var targetViewController = segue.destination
  258. if let navVC = targetViewController as? UINavigationController, let topViewController = navVC.topViewController {
  259. targetViewController = topViewController
  260. }
  261. switch targetViewController {
  262. case let vc as CarbEntryTableViewController:
  263. vc.carbStore = dataManager?.carbStore
  264. case let vc as CarbEntryEditViewController:
  265. if let carbStore = dataManager?.carbStore {
  266. vc.defaultAbsorptionTimes = carbStore.defaultAbsorptionTimes
  267. vc.preferredUnit = carbStore.preferredUnit
  268. }
  269. case let vc as LegacyInsulinDeliveryTableViewController:
  270. vc.doseStore = dataManager?.doseStore
  271. default:
  272. break
  273. }
  274. }
  275. }
  276. extension MasterViewController: DailyValueScheduleTableViewControllerDelegate {
  277. func dailyValueScheduleTableViewControllerWillFinishUpdating(_ controller: DailyValueScheduleTableViewController) {
  278. if let indexPath = tableView.indexPathForSelectedRow {
  279. switch Section(rawValue: indexPath.section)! {
  280. case .configuration:
  281. switch ConfigurationRow(rawValue: indexPath.row)! {
  282. // case .basalRate:
  283. // if let controller = controller as? BasalScheduleTableViewController {
  284. // dataManager?.basalRateSchedule = BasalRateSchedule(dailyItems: controller.scheduleItems, timeZone: controller.timeZone)
  285. // }
  286. default:
  287. break
  288. }
  289. tableView.reloadRows(at: [indexPath], with: .none)
  290. default:
  291. break
  292. }
  293. }
  294. }
  295. }
  296. extension MasterViewController: InsulinSensitivityScheduleStorageDelegate {
  297. func saveSchedule(_ schedule: InsulinSensitivitySchedule, for viewController: InsulinSensitivityScheduleViewController, completion: @escaping (SaveInsulinSensitivityScheduleResult) -> Void) {
  298. self.dataManager?.insulinSensitivitySchedule = schedule
  299. completion(.success)
  300. }
  301. }
  302. private extension HKUnit {
  303. var allowedSensitivityValues: [Double] {
  304. if self == HKUnit.milligramsPerDeciliter {
  305. return (10...500).map { Double($0) }
  306. }
  307. if self == HKUnit.millimolesPerLiter {
  308. return (6...270).map { Double($0) / 10.0 }
  309. }
  310. return []
  311. }
  312. var allowedCorrectionRangeValues: [Double] {
  313. if self == HKUnit.milligramsPerDeciliter {
  314. return (60...180).map { Double($0) }
  315. }
  316. if self == HKUnit.millimolesPerLiter {
  317. return (33...100).map { Double($0) / 10.0 }
  318. }
  319. return []
  320. }
  321. }