DeliveryLimitSettingsTableViewController.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. //
  2. // DeliveryLimitSettingsTableViewController.swift
  3. // Loop
  4. //
  5. // Copyright © 2018 LoopKit Authors. All rights reserved.
  6. //
  7. import UIKit
  8. public protocol DeliveryLimitSettingsTableViewControllerDelegate: AnyObject {
  9. func deliveryLimitSettingsTableViewControllerDidUpdateMaximumBasalRatePerHour(_ vc: DeliveryLimitSettingsTableViewController)
  10. func deliveryLimitSettingsTableViewControllerDidUpdateMaximumBolus(_ vc: DeliveryLimitSettingsTableViewController)
  11. }
  12. public enum DeliveryLimitSettingsResult {
  13. case success(maximumBasalRatePerHour: Double, maximumBolus: Double)
  14. case failure(Error)
  15. }
  16. public protocol DeliveryLimitSettingsTableViewControllerSyncSource: AnyObject {
  17. func syncDeliveryLimitSettings(for viewController: DeliveryLimitSettingsTableViewController, completion: @escaping (_ result: DeliveryLimitSettingsResult) -> Void)
  18. func syncButtonTitle(for viewController: DeliveryLimitSettingsTableViewController) -> String
  19. func syncButtonDetailText(for viewController: DeliveryLimitSettingsTableViewController) -> String?
  20. func deliveryLimitSettingsTableViewControllerIsReadOnly(_ viewController: DeliveryLimitSettingsTableViewController) -> Bool
  21. }
  22. public class DeliveryLimitSettingsTableViewController: UITableViewController {
  23. public weak var delegate: DeliveryLimitSettingsTableViewControllerDelegate?
  24. public weak var syncSource: DeliveryLimitSettingsTableViewControllerSyncSource? {
  25. didSet {
  26. isReadOnly = syncSource?.deliveryLimitSettingsTableViewControllerIsReadOnly(self) ?? false
  27. if isViewLoaded {
  28. tableView.reloadData()
  29. }
  30. }
  31. }
  32. public var maximumBasalRatePerHour: Double? {
  33. didSet {
  34. if isViewLoaded, let cell = tableView.cellForRow(at: IndexPath(row: 0, section: Section.basalRate.rawValue)) as? TextFieldTableViewCell {
  35. if let maximumBasalRatePerHour = maximumBasalRatePerHour {
  36. cell.textField.text = valueNumberFormatter.string(from: maximumBasalRatePerHour)
  37. } else {
  38. cell.textField.text = nil
  39. }
  40. }
  41. }
  42. }
  43. public var maximumBolus: Double? {
  44. didSet {
  45. if isViewLoaded, let cell = tableView.cellForRow(at: IndexPath(row: 0, section: Section.bolus.rawValue)) as? TextFieldTableViewCell {
  46. if let maximumBolus = maximumBolus {
  47. cell.textField.text = valueNumberFormatter.string(from: maximumBolus)
  48. } else {
  49. cell.textField.text = nil
  50. }
  51. }
  52. }
  53. }
  54. public var isReadOnly = false
  55. private var isSyncInProgress = false {
  56. didSet {
  57. for cell in tableView.visibleCells {
  58. switch cell {
  59. case let cell as TextButtonTableViewCell:
  60. cell.isEnabled = !isSyncInProgress
  61. cell.isLoading = isSyncInProgress
  62. case let cell as TextFieldTableViewCell:
  63. cell.textField.isEnabled = !isReadOnly && !isSyncInProgress
  64. default:
  65. break
  66. }
  67. }
  68. for item in navigationItem.rightBarButtonItems ?? [] {
  69. item.isEnabled = !isSyncInProgress
  70. }
  71. navigationItem.hidesBackButton = isSyncInProgress
  72. }
  73. }
  74. private lazy var valueNumberFormatter: NumberFormatter = {
  75. let formatter = NumberFormatter()
  76. formatter.numberStyle = .decimal
  77. formatter.minimumFractionDigits = 1
  78. return formatter
  79. }()
  80. // MARK: -
  81. public override func viewDidLoad() {
  82. super.viewDidLoad()
  83. tableView.register(TextFieldTableViewCell.nib(), forCellReuseIdentifier: TextFieldTableViewCell.className)
  84. tableView.register(TextButtonTableViewCell.self, forCellReuseIdentifier: TextButtonTableViewCell.className)
  85. }
  86. // MARK: - Table view data source
  87. private enum Section: Int {
  88. case basalRate
  89. case bolus
  90. case sync
  91. }
  92. public override func numberOfSections(in tableView: UITableView) -> Int {
  93. return syncSource == nil ? 2 : 3
  94. }
  95. public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  96. return 1
  97. }
  98. public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  99. switch Section(rawValue: indexPath.section)! {
  100. case .basalRate:
  101. let cell = tableView.dequeueReusableCell(withIdentifier: TextFieldTableViewCell.className, for: indexPath) as! TextFieldTableViewCell
  102. if let maximumBasalRatePerHour = maximumBasalRatePerHour {
  103. cell.textField.text = valueNumberFormatter.string(from: maximumBasalRatePerHour)
  104. } else {
  105. cell.textField.text = nil
  106. }
  107. cell.textField.keyboardType = .decimalPad
  108. cell.textField.placeholder = isReadOnly ? LocalizedString("Enter a rate in units per hour", comment: "The placeholder text instructing users how to enter a maximum basal rate") : nil
  109. cell.textField.isEnabled = !isReadOnly && !isSyncInProgress
  110. cell.unitLabel?.text = LocalizedString("U/hour", comment: "The unit string for units per hour")
  111. cell.delegate = self
  112. return cell
  113. case .bolus:
  114. let cell = tableView.dequeueReusableCell(withIdentifier: TextFieldTableViewCell.className, for: indexPath) as! TextFieldTableViewCell
  115. if let maximumBolus = maximumBolus {
  116. cell.textField.text = valueNumberFormatter.string(from: maximumBolus)
  117. } else {
  118. cell.textField.text = nil
  119. }
  120. cell.textField.keyboardType = .decimalPad
  121. cell.textField.placeholder = isReadOnly ? LocalizedString("Enter a number of units", comment: "The placeholder text instructing users how to enter a maximum bolus") : nil
  122. cell.textField.isEnabled = !isReadOnly && !isSyncInProgress
  123. cell.unitLabel?.text = LocalizedString("Units", comment: "The unit string for units")
  124. cell.delegate = self
  125. return cell
  126. case .sync:
  127. let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell
  128. cell.textLabel?.text = syncSource?.syncButtonTitle(for: self)
  129. cell.isEnabled = !isSyncInProgress
  130. cell.isLoading = isSyncInProgress
  131. return cell
  132. }
  133. }
  134. public override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  135. switch Section(rawValue: section)! {
  136. case .basalRate:
  137. return LocalizedString("Maximum Basal Rate", comment: "The title text for the maximum basal rate value")
  138. case .bolus:
  139. return LocalizedString("Maximum Bolus", comment: "The title text for the maximum bolus value")
  140. case .sync:
  141. return nil
  142. }
  143. }
  144. public override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
  145. switch Section(rawValue: section)! {
  146. case .basalRate:
  147. return nil
  148. case .bolus:
  149. return nil
  150. case .sync:
  151. return syncSource?.syncButtonDetailText(for: self)
  152. }
  153. }
  154. public override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
  155. return true
  156. }
  157. public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  158. switch Section(rawValue: indexPath.section)! {
  159. case .basalRate, .bolus:
  160. if let cell = tableView.cellForRow(at: indexPath) as? TextFieldTableViewCell {
  161. if cell.textField.isFirstResponder {
  162. cell.textField.resignFirstResponder()
  163. } else {
  164. cell.textField.becomeFirstResponder()
  165. }
  166. }
  167. case .sync:
  168. tableView.endEditing(true)
  169. guard let syncSource = syncSource, !isSyncInProgress else {
  170. break
  171. }
  172. isSyncInProgress = true
  173. syncSource.syncDeliveryLimitSettings(for: self) { (result) in
  174. DispatchQueue.main.async {
  175. switch result {
  176. case .success(maximumBasalRatePerHour: let maxBasal, maximumBolus: let maxBolus):
  177. self.maximumBasalRatePerHour = maxBasal
  178. self.maximumBolus = maxBolus
  179. self.delegate?.deliveryLimitSettingsTableViewControllerDidUpdateMaximumBasalRatePerHour(self)
  180. self.delegate?.deliveryLimitSettingsTableViewControllerDidUpdateMaximumBolus(self)
  181. self.isSyncInProgress = false
  182. case .failure(let error):
  183. let alert = UIAlertController(with: error)
  184. self.present(alert, animated: true) {
  185. self.isSyncInProgress = false
  186. }
  187. }
  188. }
  189. }
  190. }
  191. tableView.deselectRow(at: indexPath, animated: true)
  192. }
  193. }
  194. extension DeliveryLimitSettingsTableViewController: TextFieldTableViewCellDelegate {
  195. public func textFieldTableViewCellDidBeginEditing(_ cell: TextFieldTableViewCell) {
  196. }
  197. public func textFieldTableViewCellDidEndEditing(_ cell: TextFieldTableViewCell) {
  198. guard let indexPath = tableView.indexPath(for: cell) else {
  199. return
  200. }
  201. let value = valueNumberFormatter.number(from: cell.textField.text ?? "")?.doubleValue
  202. switch Section(rawValue: indexPath.section)! {
  203. case .basalRate:
  204. maximumBasalRatePerHour = value
  205. if syncSource == nil {
  206. delegate?.deliveryLimitSettingsTableViewControllerDidUpdateMaximumBasalRatePerHour(self)
  207. }
  208. case .bolus:
  209. maximumBolus = value
  210. if syncSource == nil {
  211. delegate?.deliveryLimitSettingsTableViewControllerDidUpdateMaximumBolus(self)
  212. }
  213. case .sync:
  214. break
  215. }
  216. }
  217. }