ReplacePodViewController.swift 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. //
  2. // ReplacePodViewController.swift
  3. // OmniKitUI
  4. //
  5. // Created by Pete Schwamb on 11/28/18.
  6. // Copyright © 2018 Pete Schwamb. All rights reserved.
  7. //
  8. import UIKit
  9. import LoopKitUI
  10. import OmniKit
  11. class ReplacePodViewController: SetupTableViewController {
  12. enum PodReplacementReason {
  13. case normal
  14. case activationTimeout
  15. case podIncompatible
  16. case fault(_ podFault: DetailedStatus)
  17. case canceledPairingBeforeApplication
  18. case canceledPairing
  19. }
  20. var replacementReason: PodReplacementReason = .normal {
  21. didSet {
  22. updateButtonTint()
  23. switch replacementReason {
  24. case .normal:
  25. break // Text set in interface builder
  26. case .activationTimeout:
  27. instructionsLabel.text = LocalizedString("Activation time exceeded. The pod must be deactivated before pairing with a new one. Please deactivate and discard pod.", comment: "Instructions when deactivating pod that didn't complete activation in time.")
  28. case .podIncompatible:
  29. instructionsLabel.text = LocalizedString("Unable to use incompatible pod. The pod must be deactivated before pairing with a new one. Please deactivate and discard pod.", comment: "Instructions when deactivating an incompatible pod")
  30. case .fault(let podFault):
  31. var faultDescription = podFault.faultEventCode.localizedDescription
  32. if let refString = podFault.pdmRef {
  33. faultDescription += String(format: " (%@)", refString)
  34. }
  35. instructionsLabel.text = String(format: LocalizedString("%1$@. Insulin delivery has stopped. Please deactivate and remove pod.", comment: "Format string providing instructions for replacing pod due to a fault. (1: The fault description)"), faultDescription)
  36. case .canceledPairingBeforeApplication:
  37. instructionsLabel.text = LocalizedString("Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod.", comment: "Instructions when deactivating pod that has been paired, but not attached.")
  38. case .canceledPairing:
  39. instructionsLabel.text = LocalizedString("Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod.", comment: "Instructions when deactivating pod that has been paired and possibly attached.")
  40. }
  41. tableView.reloadData()
  42. }
  43. }
  44. var pumpManager: OmnipodPumpManager! {
  45. didSet {
  46. let podState = pumpManager.state.podState
  47. if let podFault = podState?.fault {
  48. if podFault.podProgressStatus == .activationTimeExceeded {
  49. self.replacementReason = .activationTimeout
  50. } else {
  51. self.replacementReason = .fault(podFault)
  52. }
  53. } else if podState?.setupProgress == .activationTimeout {
  54. self.replacementReason = .activationTimeout
  55. } else if podState?.setupProgress == .podIncompatible {
  56. self.replacementReason = .podIncompatible
  57. } else if podState?.setupProgress.primingNeeded == true {
  58. self.replacementReason = .canceledPairingBeforeApplication
  59. } else if podState?.setupProgress.needsCannulaInsertion == true {
  60. self.replacementReason = .canceledPairing
  61. } else {
  62. self.replacementReason = .normal
  63. }
  64. }
  65. }
  66. // MARK: -
  67. @IBOutlet weak var activityIndicator: SetupIndicatorView!
  68. @IBOutlet weak var loadingLabel: UILabel!
  69. @IBOutlet weak var instructionsLabel: UILabel!
  70. private var tryCount: Int = 0
  71. override func viewDidLoad() {
  72. super.viewDidLoad()
  73. continueState = .initial
  74. }
  75. override func setEditing(_ editing: Bool, animated: Bool) {
  76. super.setEditing(editing, animated: animated)
  77. }
  78. // MARK: - UITableViewDelegate
  79. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  80. guard continueState != .deactivating else {
  81. return
  82. }
  83. tableView.deselectRow(at: indexPath, animated: true)
  84. }
  85. // MARK: - Navigation
  86. private enum State {
  87. case initial
  88. case deactivating
  89. case deactivationFailed
  90. case continueAfterFailure
  91. case ready
  92. }
  93. private var continueState: State = .initial {
  94. didSet {
  95. updateButtonTint()
  96. switch continueState {
  97. case .initial:
  98. activityIndicator.state = .hidden
  99. footerView.primaryButton.isEnabled = true
  100. footerView.primaryButton.setDeactivateTitle()
  101. case .deactivating:
  102. activityIndicator.state = .indeterminantProgress
  103. footerView.primaryButton.isEnabled = false
  104. lastError = nil
  105. case .deactivationFailed:
  106. activityIndicator.state = .hidden
  107. footerView.primaryButton.isEnabled = true
  108. footerView.primaryButton.setRetryTitle()
  109. case .continueAfterFailure:
  110. activityIndicator.state = .hidden
  111. footerView.primaryButton.isEnabled = true
  112. footerView.primaryButton.resetTitle()
  113. tableView.beginUpdates()
  114. loadingLabel.text = LocalizedString("Unable to deactivate pod. Please continue and pair a new one.", comment: "Instructions when pod cannot be deactivated")
  115. loadingLabel.isHidden = false
  116. tableView.endUpdates()
  117. case .ready:
  118. navigationItem.rightBarButtonItem = nil
  119. activityIndicator.state = .completed
  120. footerView.primaryButton.isEnabled = true
  121. footerView.primaryButton.resetTitle()
  122. footerView.primaryButton.tintColor = nil
  123. lastError = nil
  124. }
  125. }
  126. }
  127. private var lastError: Error? {
  128. didSet {
  129. guard oldValue != nil || lastError != nil else {
  130. return
  131. }
  132. var errorText = lastError?.localizedDescription
  133. if let error = lastError as? LocalizedError {
  134. let localizedText = [error.errorDescription, error.failureReason, error.recoverySuggestion].compactMap({ $0 }).joined(separator: ". ")
  135. if !localizedText.isEmpty {
  136. errorText = localizedText + "."
  137. }
  138. }
  139. // If we have an error but no error text, generate a string to describe the error
  140. if let error = lastError, (errorText == nil || errorText!.isEmpty) {
  141. errorText = String(describing: error)
  142. }
  143. tableView.beginUpdates()
  144. loadingLabel.text = errorText
  145. let isHidden = (errorText == nil)
  146. loadingLabel.isHidden = isHidden
  147. tableView.endUpdates()
  148. }
  149. }
  150. override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
  151. return continueState == .ready || continueState == .continueAfterFailure
  152. }
  153. override func continueButtonPressed(_ sender: Any) {
  154. switch continueState {
  155. case .ready, .continueAfterFailure:
  156. super.continueButtonPressed(sender)
  157. case .initial, .deactivationFailed:
  158. continueState = .deactivating
  159. deactivate()
  160. case .deactivating:
  161. break
  162. }
  163. }
  164. func deactivate() {
  165. tryCount += 1
  166. let continueAfterFailure = tryCount > 1
  167. pumpManager.deactivatePod(forgetPodOnFail: continueAfterFailure) { (error) in
  168. DispatchQueue.main.async {
  169. if let error = error {
  170. if continueAfterFailure {
  171. self.continueState = .continueAfterFailure
  172. } else {
  173. self.lastError = error
  174. self.continueState = .deactivationFailed
  175. }
  176. } else {
  177. self.continueState = .ready
  178. }
  179. }
  180. }
  181. }
  182. override func cancelButtonPressed(_ sender: Any) {
  183. self.dismiss(animated: true, completion: nil)
  184. }
  185. private func updateButtonTint() {
  186. let buttonTint: UIColor?
  187. if case .normal = replacementReason, case .initial = continueState {
  188. buttonTint = .deleteColor
  189. } else {
  190. buttonTint = nil
  191. }
  192. footerView.primaryButton.tintColor = buttonTint
  193. }
  194. }
  195. private extension SetupButton {
  196. func setDeactivateTitle() {
  197. setTitle(LocalizedString("Deactivate Pod", comment: "Button title for pod deactivation"), for: .normal)
  198. }
  199. func setRetryTitle() {
  200. setTitle(LocalizedString("Retry Pod Deactivation", comment: "Button title for retrying pod deactivation"), for: .normal)
  201. }
  202. }