PairPodViewModel.swift 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. //
  2. // PairPodViewModel.swift
  3. // OmniKit
  4. //
  5. // Created by Pete Schwamb on 3/2/20.
  6. // Copyright © 2021 LoopKit Authors. All rights reserved.
  7. //
  8. import SwiftUI
  9. import LoopKit
  10. import LoopKitUI
  11. import OmniKit
  12. class PairPodViewModel: ObservableObject, Identifiable {
  13. enum NavBarButtonAction {
  14. case cancel
  15. case discard
  16. var text: String {
  17. switch self {
  18. case .cancel:
  19. return LocalizedString("Cancel", comment: "Pairing interface navigation bar button text for cancel action")
  20. case .discard:
  21. return LocalizedString("Discard Pod", comment: "Pairing interface navigation bar button text for discard pod action")
  22. }
  23. }
  24. func color(using guidanceColors: GuidanceColors) -> Color? {
  25. switch self {
  26. case .discard:
  27. return guidanceColors.critical
  28. case .cancel:
  29. return nil
  30. }
  31. }
  32. }
  33. enum PairPodViewModelState {
  34. case ready
  35. case pairing
  36. case priming(finishTime: CFTimeInterval?)
  37. case error(OmnipodPairingError)
  38. case finished
  39. var instructionsDisabled: Bool {
  40. switch self {
  41. case .ready:
  42. return false
  43. case .error(let error):
  44. return !error.recoverable
  45. default:
  46. return true
  47. }
  48. }
  49. var actionButtonAccessibilityLabel: String {
  50. switch self {
  51. case .ready:
  52. return LocalizedString("Pair pod.", comment: "Pairing action button accessibility label while ready to pair")
  53. case .pairing:
  54. return LocalizedString("Pairing.", comment: "Pairing action button accessibility label while pairing")
  55. case .priming:
  56. return LocalizedString("Priming. Please wait.", comment: "Pairing action button accessibility label while priming")
  57. case .error(let error):
  58. return String(format: "%@ %@", error.errorDescription ?? "", error.recoverySuggestion ?? "")
  59. case .finished:
  60. return LocalizedString("Pod paired successfully. Continue.", comment: "Pairing action button accessibility label when pairing succeeded")
  61. }
  62. }
  63. var nextActionButtonDescription: String {
  64. switch self {
  65. case .ready:
  66. return LocalizedString("Pair Pod", comment: "Pod pairing action button text while ready to pair")
  67. case .error:
  68. return LocalizedString("Retry", comment: "Pod pairing action button text while showing error")
  69. case .pairing:
  70. return LocalizedString("Pairing...", comment: "Pod pairing action button text while pairing")
  71. case .priming:
  72. return LocalizedString("Priming...", comment: "Pod pairing action button text while priming")
  73. case .finished:
  74. return LocalizedString("Continue", comment: "Pod pairing action button text when paired")
  75. }
  76. }
  77. var navBarButtonAction: NavBarButtonAction {
  78. return .cancel
  79. }
  80. var navBarVisible: Bool {
  81. if case .error(let error) = self {
  82. return error.recoverable
  83. }
  84. return true
  85. }
  86. var showProgressDetail: Bool {
  87. switch self {
  88. case .ready:
  89. return false
  90. default:
  91. return true
  92. }
  93. }
  94. var progressState: ProgressIndicatorState {
  95. switch self {
  96. case .ready, .error:
  97. return .hidden
  98. case .pairing:
  99. return .indeterminantProgress
  100. case .priming(let finishTime):
  101. if let finishTime {
  102. return .timedProgress(finishTime: finishTime)
  103. } else {
  104. return .indeterminantProgress
  105. }
  106. case .finished:
  107. return .completed
  108. }
  109. }
  110. var isProcessing: Bool {
  111. switch self {
  112. case .pairing, .priming:
  113. return true
  114. default:
  115. return false
  116. }
  117. }
  118. var isFinished: Bool {
  119. if case .finished = self {
  120. return true
  121. }
  122. return false
  123. }
  124. }
  125. var error: OmnipodPairingError? {
  126. if case .error(let error) = state {
  127. return error
  128. }
  129. return nil
  130. }
  131. @Published var state: PairPodViewModelState = .ready
  132. var podIsActivated: Bool {
  133. return podPairer.podCommState != .noPod
  134. }
  135. var backButtonHidden: Bool {
  136. if case .pairing = state {
  137. return true
  138. }
  139. if podIsActivated {
  140. return true
  141. }
  142. return false
  143. }
  144. var didFinish: (() -> Void)?
  145. var didRequestDeactivation: (() -> Void)?
  146. var didCancelSetup: (() -> Void)?
  147. var podPairer: PodPairer
  148. init(podPairer: PodPairer) {
  149. self.podPairer = podPairer
  150. // If resuming, don't wait for the button action
  151. if podPairer.podCommState == .activating {
  152. pairAndPrime()
  153. }
  154. }
  155. private func pairAndPrime() {
  156. if podPairer.podCommState == .noPod {
  157. state = .pairing
  158. } else {
  159. // Already paired, so resume with the prime
  160. state = .priming(finishTime: nil)
  161. }
  162. podPairer.pairAndPrimePod { (status) in
  163. DispatchQueue.main.async {
  164. switch status {
  165. case .failure(let error):
  166. let pairingError = OmnipodPairingError.pumpManagerError(error)
  167. self.state = .error(pairingError)
  168. case .success(let duration):
  169. if duration > 0 {
  170. self.state = .priming(finishTime: CACurrentMediaTime() + duration)
  171. DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
  172. self.state = .finished
  173. }
  174. } else {
  175. self.state = .finished
  176. }
  177. }
  178. }
  179. }
  180. }
  181. public func continueButtonTapped() {
  182. switch state {
  183. case .error(let error):
  184. if !error.recoverable {
  185. self.didRequestDeactivation?()
  186. } else {
  187. // Retry
  188. pairAndPrime()
  189. }
  190. case .finished:
  191. didFinish?()
  192. default:
  193. pairAndPrime()
  194. }
  195. }
  196. }
  197. // Pairing recovery suggestions
  198. enum OmnipodPairingError : LocalizedError {
  199. case pumpManagerError(PumpManagerError)
  200. var recoverySuggestion: String? {
  201. switch self {
  202. case .pumpManagerError(let error):
  203. return error.recoverySuggestion
  204. }
  205. }
  206. var errorDescription: String? {
  207. switch self {
  208. case .pumpManagerError(let error):
  209. return error.errorDescription
  210. }
  211. }
  212. var recoverable: Bool {
  213. // switch self {
  214. // case .pumpManagerError(let error):
  215. // TODO: check which errors are recoverable
  216. return true
  217. // }
  218. }
  219. }
  220. public protocol PodPairer {
  221. func pairAndPrimePod(completion: @escaping (PumpManagerResult<TimeInterval>) -> Void)
  222. func discardPod(completion: @escaping (Bool) -> ())
  223. var podCommState: PodCommState { get }
  224. }
  225. extension OmnipodPumpManager: PodPairer {
  226. public func discardPod(completion: @escaping (Bool) -> ()) {
  227. }
  228. public func pairAndPrimePod(completion: @escaping (PumpManagerResult<TimeInterval>) -> Void) {
  229. pairAndPrime(completion: completion)
  230. }
  231. }