InsertCannulaViewModel.swift 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. //
  2. // InsertCannulaViewModel.swift
  3. // OmniKit
  4. //
  5. // Created by Pete Schwamb on 3/10/20.
  6. // Copyright © 2021 LoopKit Authors. All rights reserved.
  7. //
  8. import LoopKit
  9. import LoopKitUI
  10. import OmniKit
  11. public protocol CannulaInserter {
  12. func insertCannula(completion: @escaping (Result<TimeInterval,OmnipodPumpManagerError>) -> ())
  13. func checkCannulaInsertionFinished(completion: @escaping (OmnipodPumpManagerError?) -> Void)
  14. var cannulaInsertionSuccessfullyStarted: Bool { get }
  15. }
  16. extension OmnipodPumpManager: CannulaInserter {
  17. public var cannulaInsertionSuccessfullyStarted: Bool {
  18. return state.podState?.setupProgress.cannulaInsertionSuccessfullyStarted == true
  19. }
  20. }
  21. class InsertCannulaViewModel: ObservableObject, Identifiable {
  22. enum InsertCannulaViewModelState {
  23. case ready
  24. case startingInsertion
  25. case inserting(finishTime: CFTimeInterval)
  26. case checkingInsertion
  27. case error(OmnipodPumpManagerError)
  28. case finished
  29. var actionButtonAccessibilityLabel: String {
  30. switch self {
  31. case .ready:
  32. return LocalizedString("Slide Button to insert Cannula", comment: "Insert cannula slider button accessibility label while ready to pair")
  33. case .inserting, .startingInsertion:
  34. return LocalizedString("Inserting. Please wait.", comment: "Insert cannula action button accessibility label while pairing")
  35. case .checkingInsertion:
  36. return LocalizedString("Checking Insertion", comment: "Insert cannula action button accessibility label checking insertion")
  37. case .error(let error):
  38. return String(format: "%@ %@", error.errorDescription ?? "", error.recoverySuggestion ?? "")
  39. case .finished:
  40. return LocalizedString("Cannula inserted successfully. Continue.", comment: "Insert cannula action button accessibility label when cannula insertion succeeded")
  41. }
  42. }
  43. var instructionsDisabled: Bool {
  44. switch self {
  45. case .ready, .error:
  46. return false
  47. default:
  48. return true
  49. }
  50. }
  51. var nextActionButtonDescription: String {
  52. switch self {
  53. case .ready:
  54. return LocalizedString("Slide to Insert Cannula", comment: "Cannula insertion button text while ready to insert")
  55. case .error:
  56. return LocalizedString("Retry", comment: "Cannula insertion button text while showing error")
  57. case .inserting, .startingInsertion:
  58. return LocalizedString("Inserting...", comment: "Cannula insertion button text while inserting")
  59. case .checkingInsertion:
  60. return LocalizedString("Checking...", comment: "Cannula insertion button text while checking insertion")
  61. case .finished:
  62. return LocalizedString("Continue", comment: "Cannula insertion button text when inserted")
  63. }
  64. }
  65. var nextActionButtonStyle: ActionButton.ButtonType {
  66. switch self {
  67. case .error(let error):
  68. if !error.recoverable {
  69. return .destructive
  70. }
  71. default:
  72. break
  73. }
  74. return .primary
  75. }
  76. var progressState: ProgressIndicatorState {
  77. switch self {
  78. case .ready, .error:
  79. return .hidden
  80. case .startingInsertion, .checkingInsertion:
  81. return .indeterminantProgress
  82. case .inserting(let finishTime):
  83. return .timedProgress(finishTime: finishTime)
  84. case .finished:
  85. return .completed
  86. }
  87. }
  88. var showProgressDetail: Bool {
  89. switch self {
  90. case .ready:
  91. return false
  92. default:
  93. return true
  94. }
  95. }
  96. var isProcessing: Bool {
  97. switch self {
  98. case .startingInsertion, .inserting:
  99. return true
  100. default:
  101. return false
  102. }
  103. }
  104. var isFinished: Bool {
  105. if case .finished = self {
  106. return true
  107. }
  108. return false
  109. }
  110. }
  111. var error: OmnipodPumpManagerError? {
  112. if case .error(let error) = self.state {
  113. return error
  114. }
  115. return nil
  116. }
  117. @Published var state: InsertCannulaViewModelState = .ready
  118. public var stateNeedsDeliberateUserAcceptance : Bool {
  119. switch state {
  120. case .ready:
  121. true
  122. default:
  123. false
  124. }
  125. }
  126. var didFinish: (() -> Void)?
  127. var didRequestDeactivation: (() -> Void)?
  128. var cannulaInserter: CannulaInserter
  129. init(cannulaInserter: CannulaInserter) {
  130. self.cannulaInserter = cannulaInserter
  131. // If resuming, don't wait for the button action
  132. if cannulaInserter.cannulaInsertionSuccessfullyStarted {
  133. insertCannula()
  134. }
  135. }
  136. private func checkCannulaInsertionFinished() {
  137. state = .checkingInsertion
  138. cannulaInserter.checkCannulaInsertionFinished() { (error) in
  139. DispatchQueue.main.async {
  140. if let error = error {
  141. self.state = .error(error)
  142. } else {
  143. self.state = .finished
  144. }
  145. }
  146. }
  147. }
  148. private func insertCannula() {
  149. state = .startingInsertion
  150. cannulaInserter.insertCannula { (result) in
  151. DispatchQueue.main.async {
  152. switch(result) {
  153. case .success(let finishTime):
  154. self.state = .inserting(finishTime: CACurrentMediaTime() + finishTime)
  155. let delay = finishTime
  156. if delay > 0 {
  157. DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
  158. self.checkCannulaInsertionFinished() // now check if actually ready
  159. }
  160. } else {
  161. self.state = .finished
  162. }
  163. case .failure(let error):
  164. self.state = .error(error)
  165. }
  166. }
  167. }
  168. }
  169. public func continueButtonTapped() {
  170. switch state {
  171. case .finished:
  172. didFinish?()
  173. case .error(let error):
  174. if error.recoverable {
  175. insertCannula()
  176. } else {
  177. didRequestDeactivation?()
  178. }
  179. default:
  180. insertCannula()
  181. }
  182. }
  183. }
  184. public extension OmnipodPumpManagerError {
  185. var recoverable: Bool {
  186. //TODO
  187. return true
  188. // switch self {
  189. // case .podIsInAlarm:
  190. // return false
  191. // case .activationError(let activationErrorCode):
  192. // switch activationErrorCode {
  193. // case .podIsLumpOfCoal1Hour, .podIsLumpOfCoal2Hours:
  194. // return false
  195. // default:
  196. // return true
  197. // }
  198. // case .internalError(.incompatibleProductId):
  199. // return false
  200. // case .systemError:
  201. // return false
  202. // default:
  203. // return true
  204. // }
  205. }
  206. }