PairPodViewModel.swift 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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. // switch self {
  79. // case .error(_, let podCommState):
  80. // if podCommState == .activating {
  81. // return .discard
  82. // }
  83. // default:
  84. // break
  85. // }
  86. return .cancel
  87. }
  88. var navBarVisible: Bool {
  89. if case .error(let error) = self {
  90. return error.recoverable
  91. }
  92. return true
  93. }
  94. var showProgressDetail: Bool {
  95. switch self {
  96. case .ready:
  97. return false
  98. default:
  99. return true
  100. }
  101. }
  102. var progressState: ProgressIndicatorState {
  103. switch self {
  104. case .ready, .error:
  105. return .hidden
  106. case .pairing:
  107. return .indeterminantProgress
  108. case .priming(let finishTime):
  109. return .timedProgress(finishTime: finishTime)
  110. case .finished:
  111. return .completed
  112. }
  113. }
  114. var isProcessing: Bool {
  115. switch self {
  116. case .pairing, .priming:
  117. return true
  118. default:
  119. return false
  120. }
  121. }
  122. var isFinished: Bool {
  123. if case .finished = self {
  124. return true
  125. }
  126. return false
  127. }
  128. }
  129. var error: OmnipodPairingError? {
  130. if case .error(let error) = state {
  131. return error
  132. }
  133. return nil
  134. }
  135. @Published var state: PairPodViewModelState = .ready
  136. var podIsActivated: Bool {
  137. return false // podPairer.podCommState != .noPod
  138. }
  139. var backButtonHidden: Bool {
  140. if case .pairing = state {
  141. return true
  142. }
  143. if podIsActivated {
  144. return true
  145. }
  146. return false
  147. }
  148. var didFinish: (() -> Void)?
  149. var didRequestDeactivation: (() -> Void)?
  150. var didCancelSetup: (() -> Void)?
  151. var podPairer: PodPairer
  152. init(podPairer: PodPairer) {
  153. self.podPairer = podPairer
  154. }
  155. private func pair() {
  156. state = .pairing
  157. podPairer.pair { (status) in
  158. DispatchQueue.main.async {
  159. switch status {
  160. case .failure(let error):
  161. let pairingError = OmnipodPairingError.pumpManagerError(error)
  162. self.state = .error(pairingError)
  163. case .success(let duration):
  164. if duration > 0 {
  165. self.state = .priming(finishTime: CACurrentMediaTime() + duration)
  166. DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
  167. self.state = .finished
  168. }
  169. } else {
  170. self.state = .finished
  171. }
  172. }
  173. }
  174. }
  175. }
  176. public func continueButtonTapped() {
  177. switch state {
  178. case .error(let error):
  179. if !error.recoverable {
  180. self.didRequestDeactivation?()
  181. } else {
  182. // Retry
  183. pair()
  184. }
  185. case .finished:
  186. didFinish?()
  187. default:
  188. pair()
  189. }
  190. }
  191. }
  192. // Pairing recovery suggestions
  193. enum OmnipodPairingError : LocalizedError {
  194. case pumpManagerError(PumpManagerError)
  195. var recoverySuggestion: String? {
  196. switch self {
  197. case .pumpManagerError(let error):
  198. return error.recoverySuggestion
  199. }
  200. }
  201. var errorDescription: String? {
  202. switch self {
  203. case .pumpManagerError(let error):
  204. return error.errorDescription
  205. }
  206. }
  207. var recoverable: Bool {
  208. // switch self {
  209. // case .pumpManagerError(let error):
  210. // TODO: check which errors are recoverable
  211. return true
  212. // }
  213. }
  214. }
  215. public protocol PodPairer {
  216. func pair(completion: @escaping (PumpManagerResult<TimeInterval>) -> Void)
  217. func discardPod(completion: @escaping (Bool) -> ())
  218. }
  219. extension OmnipodPumpManager: PodPairer {
  220. public func discardPod(completion: @escaping (Bool) -> ()) {
  221. }
  222. public func pair(completion: @escaping (PumpManagerResult<TimeInterval>) -> Void) {
  223. pairAndPrime(completion: completion)
  224. }
  225. }