InsertCannulaSetupViewController.swift 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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. let podCommsError: PodCommsError?
  99. if let pumpManagerError = lastError as? PumpManagerError {
  100. switch pumpManagerError {
  101. // Check for a wrapped PodCommsError in the possible PumpManagerError types
  102. case .communication(let error), .configuration(let error), .connection(let error), .deviceState(let error):
  103. podCommsError = error as? PodCommsError
  104. default:
  105. podCommsError = nil
  106. break
  107. }
  108. } else {
  109. // Check for a non PumpManagerError PodCommsError
  110. podCommsError = lastError as? PodCommsError
  111. }
  112. // If we have an error, update the continue state depending on whether it's fatal or if the cannula insertion was started or not
  113. if let podCommsError = podCommsError {
  114. if podCommsError.isFaulted {
  115. continueState = .fault
  116. } else {
  117. continueState = initialOrNeedsCannulaInsertionCheck
  118. }
  119. } else if lastError != nil {
  120. continueState = initialOrNeedsCannulaInsertionCheck
  121. }
  122. }
  123. }
  124. // .needsCheckInsertion (if cannula insertion has been started but its completion hasn't been verified) or else .initial
  125. private var initialOrNeedsCannulaInsertionCheck: State {
  126. if pumpManager.state.podState?.setupProgress == .cannulaInserting {
  127. return .needsCheckInsertion
  128. }
  129. return .initial
  130. }
  131. // .ready (if pod setup has been verifed to be complete) or else .needsCheckInsertion
  132. private var readyOrNeedsCannulaInsertionCheck: State {
  133. if pumpManager.state.podState?.setupProgress == .completed {
  134. return .ready
  135. }
  136. return .needsCheckInsertion
  137. }
  138. private func navigateToReplacePod() {
  139. performSegue(withIdentifier: "ReplacePod", sender: nil)
  140. }
  141. override func continueButtonPressed(_ sender: Any) {
  142. switch continueState {
  143. case .initial:
  144. continueState = .startingInsertion
  145. insertCannula()
  146. case .needsCheckInsertion:
  147. checkCannulaInsertionFinished()
  148. if pumpManager.state.podState?.setupProgress == .completed {
  149. super.continueButtonPressed(sender)
  150. }
  151. case .ready:
  152. super.continueButtonPressed(sender)
  153. case .fault:
  154. navigateToReplacePod()
  155. case .startingInsertion, .inserting:
  156. break
  157. }
  158. }
  159. override func cancelButtonPressed(_ sender: Any) {
  160. let confirmVC = UIAlertController(pumpDeletionHandler: {
  161. self.navigateToReplacePod()
  162. })
  163. present(confirmVC, animated: true) {}
  164. }
  165. private func insertCannula() {
  166. guard let podState = pumpManager.state.podState, podState.setupProgress.needsCannulaInsertion else {
  167. self.continueState = readyOrNeedsCannulaInsertionCheck
  168. return
  169. }
  170. pumpManager.insertCannula() { (result) in
  171. DispatchQueue.main.async {
  172. switch(result) {
  173. case .success(let finishTime):
  174. self.continueState = .inserting(finishTime: finishTime)
  175. let delay = finishTime
  176. if delay > 0 {
  177. DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
  178. self.checkCannulaInsertionFinished() // now check if actually ready
  179. }
  180. } else {
  181. self.continueState = self.readyOrNeedsCannulaInsertionCheck
  182. }
  183. case .failure(let error):
  184. self.lastError = error
  185. }
  186. }
  187. }
  188. }
  189. private func checkCannulaInsertionFinished() {
  190. activityIndicator.state = .indeterminantProgress
  191. self.pumpManager.checkCannulaInsertionFinished() { (error) in
  192. DispatchQueue.main.async {
  193. if let error = error {
  194. self.lastError = error
  195. }
  196. self.continueState = self.readyOrNeedsCannulaInsertionCheck
  197. }
  198. }
  199. }
  200. }
  201. private extension SetupButton {
  202. func setInsertCannulaTitle() {
  203. setTitle(LocalizedString("Insert Cannula", comment: "Button title to insert cannula during setup"), for: .normal)
  204. }
  205. func setRecheckInsertionTitle() {
  206. setTitle(LocalizedString("Recheck Cannula Insertion", comment: "Button title to recheck cannula insertion during setup"), for: .normal)
  207. }
  208. func setDeactivateTitle() {
  209. setTitle(LocalizedString("Deactivate", comment: "Button title to deactivate pod because of fault during setup"), for: .normal)
  210. }
  211. }
  212. private extension UIAlertController {
  213. convenience init(pumpDeletionHandler handler: @escaping () -> Void) {
  214. self.init(
  215. title: nil,
  216. message: LocalizedString("Are you sure you want to shutdown this pod?", comment: "Confirmation message for shutting down a pod"),
  217. preferredStyle: .actionSheet
  218. )
  219. addAction(UIAlertAction(
  220. title: LocalizedString("Deactivate Pod", comment: "Button title to deactivate pod"),
  221. style: .destructive,
  222. handler: { (_) in
  223. handler()
  224. }
  225. ))
  226. let exit = LocalizedString("Continue", comment: "The title of the continue action in an action sheet")
  227. addAction(UIAlertAction(title: exit, style: .default, handler: nil))
  228. }
  229. }