InsertCannulaSetupViewController.swift 8.7 KB

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