| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- //
- // MasterViewController.swift
- // LoopKit Example
- //
- // Created by Nathan Racklyeft on 2/24/16.
- // Copyright © 2016 Nathan Racklyeft. All rights reserved.
- //
- import UIKit
- import LoopKit
- import LoopKitUI
- import HealthKit
- class MasterViewController: UITableViewController {
- private var dataManager: DeviceDataManager? = DeviceDataManager()
- override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- guard let dataManager = dataManager else {
- return
- }
- let sampleTypes = Set([
- dataManager.glucoseStore.sampleType,
- dataManager.carbStore.sampleType,
- dataManager.doseStore.sampleType,
- ].compactMap { $0 })
- if dataManager.glucoseStore.authorizationRequired ||
- dataManager.carbStore.authorizationRequired ||
- dataManager.doseStore.authorizationRequired
- {
- dataManager.carbStore.healthStore.requestAuthorization(toShare: sampleTypes, read: sampleTypes) { (success, error) in
- if success {
- // Call the individual authorization methods to trigger query creation
- dataManager.carbStore.authorize({ _ in })
- dataManager.doseStore.insulinDeliveryStore.authorize(toShare: true, { _ in })
- dataManager.glucoseStore.authorize({ _ in })
- }
- }
- }
- }
- // MARK: - Data Source
- private enum Section: Int, CaseIterable {
- case data
- case configuration
- }
- private enum DataRow: Int, CaseIterable {
- case carbs = 0
- case reservoir
- case diagnostic
- case generate
- case reset
- }
- private enum ConfigurationRow: Int, CaseIterable {
- case basalRate
- case carbRatio
- case correctionRange
- case insulinSensitivity
- case pumpID
- }
- // MARK: UITableViewDataSource
- override func numberOfSections(in tableView: UITableView) -> Int {
- return Section.allCases.count
- }
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- switch Section(rawValue: section)! {
- case .configuration:
- return ConfigurationRow.allCases.count
- case .data:
- return DataRow.allCases.count
- }
- }
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
- switch Section(rawValue: indexPath.section)! {
- case .configuration:
- switch ConfigurationRow(rawValue: indexPath.row)! {
- case .basalRate:
- cell.textLabel?.text = LocalizedString("Basal Rates", comment: "The title text for the basal rate schedule")
- case .carbRatio:
- cell.textLabel?.text = LocalizedString("Carb Ratios", comment: "The title of the carb ratios schedule screen")
- case .correctionRange:
- cell.textLabel?.text = LocalizedString("Correction Range", comment: "The title text for the glucose correction range schedule")
- case .insulinSensitivity:
- cell.textLabel?.text = LocalizedString("Insulin Sensitivity", comment: "The title text for the insulin sensitivity schedule")
- case .pumpID:
- cell.textLabel?.text = LocalizedString("Pump ID", comment: "The title text for the pump ID")
- }
- case .data:
- switch DataRow(rawValue: indexPath.row)! {
- case .carbs:
- cell.textLabel?.text = LocalizedString("Carbs", comment: "The title for the cell navigating to the carbs screen")
- case .reservoir:
- cell.textLabel?.text = LocalizedString("Reservoir", comment: "The title for the cell navigating to the reservoir screen")
- case .diagnostic:
- cell.textLabel?.text = LocalizedString("Diagnostic", comment: "The title for the cell displaying diagnostic data")
- case .generate:
- cell.textLabel?.text = LocalizedString("Generate Data", comment: "The title for the cell displaying data generation")
- case .reset:
- cell.textLabel?.text = LocalizedString("Reset", comment: "Title for the cell resetting the data manager")
- }
- }
- return cell
- }
- // MARK: - UITableViewDelegate
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- let sender = tableView.cellForRow(at: indexPath)
- switch Section(rawValue: indexPath.section)! {
- case .configuration:
- let row = ConfigurationRow(rawValue: indexPath.row)!
- switch row {
- case .basalRate:
- // x22 with max basal rate of 5U/hr
- let pulsesPerUnit = 20
- let basalRates = (1...100).map { Double($0) / Double(pulsesPerUnit) }
- // full x23 rates
- // let rateGroup1 = ((1...39).map { Double($0) / Double(40) })
- // let rateGroup2 = ((20...199).map { Double($0) / Double(20) })
- // let rateGroup3 = ((100...350).map { Double($0) / Double(10) })
- // let basalRates = rateGroup1 + rateGroup2 + rateGroup3
- let scheduleVC = BasalScheduleTableViewController(allowedBasalRates: basalRates, maximumScheduleItemCount: 5, minimumTimeInterval: .minutes(30))
- if let profile = dataManager?.basalRateSchedule {
- scheduleVC.timeZone = profile.timeZone
- scheduleVC.scheduleItems = profile.items
- }
- scheduleVC.delegate = self
- scheduleVC.title = sender?.textLabel?.text
- scheduleVC.syncSource = self
- show(scheduleVC, sender: sender)
- case .carbRatio:
- let scheduleVC = DailyQuantityScheduleTableViewController()
- scheduleVC.delegate = self
- scheduleVC.title = NSLocalizedString("Carb Ratios", comment: "The title of the carb ratios schedule screen")
- scheduleVC.unit = .gram()
- if let schedule = dataManager?.carbRatioSchedule {
- scheduleVC.timeZone = schedule.timeZone
- scheduleVC.scheduleItems = schedule.items
- scheduleVC.unit = schedule.unit
- }
- show(scheduleVC, sender: sender)
- case .correctionRange:
- let unit = dataManager?.glucoseTargetRangeSchedule?.unit ?? dataManager?.glucoseStore.preferredUnit ?? HKUnit.milligramsPerDeciliter
- let scheduleVC = GlucoseRangeScheduleTableViewController(allowedValues: unit.allowedCorrectionRangeValues, unit: unit)
- scheduleVC.delegate = self
- scheduleVC.title = sender?.textLabel?.text
- if let schedule = dataManager?.glucoseTargetRangeSchedule {
- var overrides: [TemporaryScheduleOverride.Context: DoubleRange] = [:]
- overrides[.preMeal] = dataManager?.preMealTargetRange
- overrides[.legacyWorkout] = dataManager?.legacyWorkoutTargetRange
- scheduleVC.setSchedule(schedule, withOverrideRanges: overrides)
- }
- show(scheduleVC, sender: sender)
- case .insulinSensitivity:
- let unit = dataManager?.insulinSensitivitySchedule?.unit ?? dataManager?.glucoseStore.preferredUnit ?? HKUnit.milligramsPerDeciliter
- let scheduleVC = InsulinSensitivityScheduleViewController(allowedValues: unit.allowedSensitivityValues, unit: unit)
- scheduleVC.unit = unit
- scheduleVC.delegate = self
- scheduleVC.insulinSensitivityScheduleStorageDelegate = self
- scheduleVC.schedule = dataManager?.insulinSensitivitySchedule
- scheduleVC.title = NSLocalizedString("Insulin Sensitivity", comment: "The title of the insulin sensitivity schedule screen")
- show(scheduleVC, sender: sender)
- case .pumpID:
- let textFieldVC = TextFieldTableViewController()
- // textFieldVC.delegate = self
- textFieldVC.title = sender?.textLabel?.text
- textFieldVC.placeholder = LocalizedString("Enter the 6-digit pump ID", comment: "The placeholder text instructing users how to enter a pump ID")
- textFieldVC.value = dataManager?.pumpID
- textFieldVC.keyboardType = .numberPad
- 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")
- show(textFieldVC, sender: sender)
- }
- case .data:
- switch DataRow(rawValue: indexPath.row)! {
- case .carbs:
- performSegue(withIdentifier: CarbEntryTableViewController.className, sender: sender)
- case .reservoir:
- performSegue(withIdentifier: LegacyInsulinDeliveryTableViewController.className, sender: sender)
- case .diagnostic:
- let vc = CommandResponseViewController(command: { [weak self] (completionHandler) -> String in
- let group = DispatchGroup()
- guard let dataManager = self?.dataManager else {
- completionHandler("")
- return "nil"
- }
- var doseStoreResponse = ""
- group.enter()
- dataManager.doseStore.generateDiagnosticReport { (report) in
- doseStoreResponse = report
- group.leave()
- }
- var carbStoreResponse = ""
- if let carbStore = dataManager.carbStore {
- group.enter()
- carbStore.generateDiagnosticReport { (report) in
- carbStoreResponse = report
- group.leave()
- }
- }
- var glucoseStoreResponse = ""
- group.enter()
- dataManager.glucoseStore.generateDiagnosticReport { (report) in
- glucoseStoreResponse = report
- group.leave()
- }
- group.notify(queue: DispatchQueue.main) {
- completionHandler([
- doseStoreResponse,
- carbStoreResponse,
- glucoseStoreResponse
- ].joined(separator: "\n\n"))
- }
- return "…"
- })
- vc.title = "Diagnostic"
- show(vc, sender: sender)
- case .generate:
- let vc = CommandResponseViewController(command: { [weak self] (completionHandler) -> String in
- guard let dataManager = self?.dataManager else {
- completionHandler("")
- return "dataManager is nil"
- }
- let group = DispatchGroup()
- var unitVolume = 150.0
- reservoir: for index in sequence(first: TimeInterval(hours: -6), next: { $0 + .minutes(5) }) {
- guard index < 0 else {
- break reservoir
- }
- unitVolume -= (drand48() * 2.0)
- group.enter()
- dataManager.doseStore.addReservoirValue(unitVolume, at: Date(timeIntervalSinceNow: index)) { (_, _, _, error) in
- group.leave()
- }
- }
- group.enter()
- dataManager.glucoseStore.addGlucoseSamples([NewGlucoseSample(date: Date(), quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 101), isDisplayOnly: false, wasUserEntered: false, syncIdentifier: UUID().uuidString)], completion: { (result) in
- group.leave()
- })
- group.notify(queue: .main) {
- completionHandler("Completed")
- }
- return "Generating…"
- })
- vc.title = sender?.textLabel?.text
- show(vc, sender: sender)
- case .reset:
- dataManager = nil
- tableView.reloadData()
- }
- }
- }
- // MARK: - Segues
- override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
- super.prepare(for: segue, sender: sender)
- var targetViewController = segue.destination
- if let navVC = targetViewController as? UINavigationController, let topViewController = navVC.topViewController {
- targetViewController = topViewController
- }
- switch targetViewController {
- case let vc as CarbEntryTableViewController:
- vc.carbStore = dataManager?.carbStore
- case let vc as CarbEntryEditViewController:
- if let carbStore = dataManager?.carbStore {
- vc.defaultAbsorptionTimes = carbStore.defaultAbsorptionTimes
- vc.preferredUnit = carbStore.preferredUnit
- }
- case let vc as LegacyInsulinDeliveryTableViewController:
- vc.doseStore = dataManager?.doseStore
- default:
- break
- }
- }
- }
- extension MasterViewController: DailyValueScheduleTableViewControllerDelegate {
- func dailyValueScheduleTableViewControllerWillFinishUpdating(_ controller: DailyValueScheduleTableViewController) {
- if let indexPath = tableView.indexPathForSelectedRow {
- switch Section(rawValue: indexPath.section)! {
- case .configuration:
- switch ConfigurationRow(rawValue: indexPath.row)! {
- case .basalRate:
- if let controller = controller as? BasalScheduleTableViewController {
- dataManager?.basalRateSchedule = BasalRateSchedule(dailyItems: controller.scheduleItems, timeZone: controller.timeZone)
- }
- default:
- break
- }
- tableView.reloadRows(at: [indexPath], with: .none)
- default:
- break
- }
- }
- }
- }
- extension MasterViewController: BasalScheduleTableViewControllerSyncSource {
- func basalScheduleTableViewControllerIsReadOnly(_ viewController: BasalScheduleTableViewController) -> Bool {
- return false
- }
- func syncButtonDetailText(for viewController: BasalScheduleTableViewController) -> String? {
- return nil
- }
- func syncScheduleValues(for viewController: BasalScheduleTableViewController, completion: @escaping (SyncBasalScheduleResult<Double>) -> Void) {
- DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
- let scheduleItems = viewController.scheduleItems
- let timezone = self.dataManager?.basalRateSchedule?.timeZone ?? .currentFixed
- let schedule = BasalRateSchedule(dailyItems: scheduleItems, timeZone: timezone)
- self.dataManager?.basalRateSchedule = schedule
- completion(.success(scheduleItems: scheduleItems, timeZone: .currentFixed))
- }
- }
- func syncButtonTitle(for viewController: BasalScheduleTableViewController) -> String {
- return LocalizedString("Sync With Pump", comment: "Title of button to sync basal profile from pump")
- }
- }
- extension MasterViewController: InsulinSensitivityScheduleStorageDelegate {
- func saveSchedule(_ schedule: InsulinSensitivitySchedule, for viewController: InsulinSensitivityScheduleViewController, completion: @escaping (SaveInsulinSensitivityScheduleResult) -> Void) {
- self.dataManager?.insulinSensitivitySchedule = schedule
- completion(.success)
- }
- }
- extension MasterViewController: GlucoseRangeScheduleStorageDelegate {
- func saveSchedule(for viewController: GlucoseRangeScheduleTableViewController, completion: @escaping (SaveGlucoseRangeScheduleResult) -> Void) {
- self.dataManager?.glucoseTargetRangeSchedule = viewController.schedule
- for (context, range) in viewController.overrideRanges {
- switch context {
- case .preMeal:
- self.dataManager?.preMealTargetRange = range
- case .legacyWorkout:
- self.dataManager?.legacyWorkoutTargetRange = range
- default:
- break
- }
- }
- completion(.success)
- }
- }
- private extension HKUnit {
- var allowedSensitivityValues: [Double] {
- if self == HKUnit.milligramsPerDeciliter {
- return (10...500).map { Double($0) }
- }
- if self == HKUnit.millimolesPerLiter {
- return (6...270).map { Double($0) / 10.0 }
- }
- return []
- }
- var allowedCorrectionRangeValues: [Double] {
- if self == HKUnit.milligramsPerDeciliter {
- return (60...180).map { Double($0) }
- }
- if self == HKUnit.millimolesPerLiter {
- return (33...100).map { Double($0) / 10.0 }
- }
- return []
- }
- }
|