InsertCannulaSetupViewController.swift 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. //
  2. // InsertCannulaSetupViewController.swift
  3. // OmniKitUI
  4. //
  5. // Created by Pete Schwamb on 9/18/18.
  6. // Copyright © 2018 Pete Schwamb. All rights reserved.
  7. //
  8. import UIKit
  9. import LoopKit
  10. import LoopKitUI
  11. import RileyLinkKit
  12. import OmniKit
  13. class InsertCannulaSetupViewController: SetupTableViewController {
  14. var pumpManager: OmnipodPumpManager!
  15. // MARK: -
  16. @IBOutlet weak var activityIndicator: SetupIndicatorView!
  17. @IBOutlet weak var loadingLabel: UILabel!
  18. private var loadingText: String? {
  19. didSet {
  20. tableView.beginUpdates()
  21. loadingLabel.text = loadingText
  22. let isHidden = (loadingText == nil)
  23. loadingLabel.isHidden = isHidden
  24. tableView.endUpdates()
  25. }
  26. }
  27. override func viewDidLoad() {
  28. super.viewDidLoad()
  29. continueState = .initial
  30. }
  31. override func setEditing(_ editing: Bool, animated: Bool) {
  32. super.setEditing(editing, animated: animated)
  33. }
  34. // MARK: - UITableViewDelegate
  35. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  36. if case .startingInsertion = continueState {
  37. return
  38. }
  39. tableView.deselectRow(at: indexPath, animated: true)
  40. }
  41. // MARK: - Navigation
  42. private enum State {
  43. case initial
  44. case startingInsertion
  45. case inserting(finishTime: CFTimeInterval)
  46. case needsCheckInsertion
  47. case fault
  48. case ready
  49. }
  50. private var continueState: State = .initial {
  51. didSet {
  52. switch continueState {
  53. case .initial:
  54. activityIndicator.state = .hidden
  55. footerView.primaryButton.isEnabled = true
  56. footerView.primaryButton.setInsertCannulaTitle()
  57. case .startingInsertion:
  58. activityIndicator.state = .indeterminantProgress
  59. footerView.primaryButton.isEnabled = false
  60. lastError = nil
  61. case .inserting(let finishTime):
  62. activityIndicator.state = .timedProgress(finishTime: CACurrentMediaTime() + finishTime)
  63. footerView.primaryButton.isEnabled = false
  64. lastError = nil
  65. case .needsCheckInsertion:
  66. activityIndicator.state = .hidden
  67. footerView.primaryButton.isEnabled = true
  68. footerView.primaryButton.setRecheckInsertionTitle()
  69. case .fault:
  70. activityIndicator.state = .hidden
  71. footerView.primaryButton.isEnabled = true
  72. footerView.primaryButton.setDeactivateTitle()
  73. case .ready:
  74. activityIndicator.state = .completed
  75. footerView.primaryButton.isEnabled = true
  76. footerView.primaryButton.resetTitle()
  77. lastError = nil
  78. }
  79. }
  80. }
  81. private var lastError: Error? {
  82. didSet {
  83. guard oldValue != nil || lastError != nil else {
  84. return
  85. }
  86. var errorText = lastError?.localizedDescription
  87. if let error = lastError as? LocalizedError {
  88. let localizedText = [error.errorDescription, error.failureReason, error.recoverySuggestion].compactMap({ $0 }).joined(separator: ". ")
  89. if !localizedText.isEmpty {
  90. errorText = localizedText + "."
  91. }
  92. }
  93. // If we have an error but no error text, generate a string to describe the error
  94. if let error = lastError, (errorText == nil || errorText!.isEmpty) {
  95. errorText = String(describing: error)
  96. }
  97. loadingText = errorText
  98. var podCommsError: PodCommsError? = nil
  99. if let pumpManagerError = lastError as? PumpManagerError {
  100. switch pumpManagerError {
  101. case .communication(let error):
  102. podCommsError = error as? PodCommsError
  103. default:
  104. break
  105. }
  106. }
  107. // If we have an error, update the continue state depending on whether it's fatal or if the cannula insertion was started or not
  108. if let podCommsError = podCommsError {
  109. if podCommsError.isFaulted {
  110. continueState = .fault
  111. } else {
  112. continueState = initialOrNeedsCannulaInsertionCheck
  113. }
  114. } else if lastError != nil {
  115. continueState = initialOrNeedsCannulaInsertionCheck
  116. }
  117. }
  118. }
  119. // .needsCheckInsertion (if cannula insertion has been started but its completion hasn't been verified) or else .initial
  120. private var initialOrNeedsCannulaInsertionCheck: State {
  121. if pumpManager.state.podState?.setupProgress == .cannulaInserting {
  122. return .needsCheckInsertion
  123. }
  124. return .initial
  125. }
  126. // .ready (if pod setup has been verifed to be complete) or else .needsCheckInsertion
  127. private var readyOrNeedsCannulaInsertionCheck: State {
  128. if pumpManager.state.podState?.setupProgress == .completed {
  129. return .ready
  130. }
  131. return .needsCheckInsertion
  132. }
  133. private func navigateToReplacePod() {
  134. performSegue(withIdentifier: "ReplacePod", sender: nil)
  135. }
  136. override func continueButtonPressed(_ sender: Any) {
  137. switch continueState {
  138. case .initial:
  139. continueState = .startingInsertion
  140. insertCannula()
  141. case .needsCheckInsertion:
  142. checkCannulaInsertionFinished()
  143. if pumpManager.state.podState?.setupProgress == .completed {
  144. super.continueButtonPressed(sender)
  145. }
  146. case .ready:
  147. super.continueButtonPressed(sender)
  148. case .fault:
  149. navigateToReplacePod()
  150. case .startingInsertion, .inserting:
  151. break
  152. }
  153. }
  154. override func cancelButtonPressed(_ sender: Any) {
  155. let confirmVC = UIAlertController(pumpDeletionHandler: {
  156. self.navigateToReplacePod()
  157. })
  158. present(confirmVC, animated: true) {}
  159. }
  160. private func insertCannula() {
  161. guard let podState = pumpManager.state.podState, podState.setupProgress.needsCannulaInsertion else {
  162. self.continueState = readyOrNeedsCannulaInsertionCheck
  163. return
  164. }
  165. pumpManager.insertCannula() { (result) in
  166. DispatchQueue.main.async {
  167. switch(result) {
  168. case .success(let finishTime):
  169. self.continueState = .inserting(finishTime: finishTime)
  170. let delay = finishTime
  171. if delay > 0 {
  172. DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
  173. self.checkCannulaInsertionFinished() // now check if actually ready
  174. }
  175. } else {
  176. self.continueState = self.readyOrNeedsCannulaInsertionCheck
  177. }
  178. case .failure(let error):
  179. self.lastError = error
  180. }
  181. }
  182. }
  183. }
  184. private func checkCannulaInsertionFinished() {
  185. activityIndicator.state = .indeterminantProgress
  186. self.pumpManager.checkCannulaInsertionFinished() { (error) in
  187. DispatchQueue.main.async {
  188. if let error = error {
  189. self.lastError = error
  190. }
  191. self.continueState = self.readyOrNeedsCannulaInsertionCheck
  192. }
  193. }
  194. }
  195. }
  196. private extension SetupButton {
  197. func setInsertCannulaTitle() {
  198. setTitle(LocalizedString("Insert Cannula", comment: "Button title to insert cannula during setup"), for: .normal)
  199. }
  200. func setRecheckInsertionTitle() {
  201. setTitle(LocalizedString("Recheck Cannula Insertion", comment: "Button title to recheck cannula insertion during setup"), for: .normal)
  202. }
  203. func setDeactivateTitle() {
  204. setTitle(LocalizedString("Deactivate", comment: "Button title to deactivate pod because of fault during setup"), for: .normal)
  205. }
  206. }
  207. private extension UIAlertController {
  208. convenience init(pumpDeletionHandler handler: @escaping () -> Void) {
  209. self.init(
  210. title: nil,
  211. message: LocalizedString("Are you sure you want to shutdown this pod?", comment: "Confirmation message for shutting down a pod"),
  212. preferredStyle: .actionSheet
  213. )
  214. addAction(UIAlertAction(
  215. title: LocalizedString("Deactivate Pod", comment: "Button title to deactivate pod"),
  216. style: .destructive,
  217. handler: { (_) in
  218. handler()
  219. }
  220. ))
  221. let exit = LocalizedString("Continue", comment: "The title of the continue action in an action sheet")
  222. addAction(UIAlertAction(title: exit, style: .default, handler: nil))
  223. }
  224. }