MockPumpManagerSettingsView.swift 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. //
  2. // MockPumpManagerSettingsView.swift
  3. // MockKitUI
  4. //
  5. // Created by Nathaniel Hamming on 2023-05-18.
  6. // Copyright © 2023 LoopKit Authors. All rights reserved.
  7. //
  8. import SwiftUI
  9. import LoopKit
  10. import LoopKitUI
  11. import MockKit
  12. struct MockPumpManagerSettingsView: View {
  13. fileprivate enum PresentedAlert {
  14. case resumeInsulinDeliveryError(Error)
  15. case suspendInsulinDeliveryError(Error)
  16. }
  17. @Environment(\.dismissAction) private var dismiss
  18. @Environment(\.guidanceColors) private var guidanceColors
  19. @Environment(\.insulinTintColor) private var insulinTintColor
  20. @ObservedObject var viewModel: MockPumpManagerSettingsViewModel
  21. @State private var showSuspendOptions = false
  22. @State private var presentedAlert: PresentedAlert?
  23. private var supportedInsulinTypes: [InsulinType]
  24. private var appName: String
  25. private let allowDebugFeatures : Bool
  26. private var title: String
  27. init(pumpManager: MockPumpManager, supportedInsulinTypes: [InsulinType], appName: String, allowDebugFeatures: Bool) {
  28. viewModel = MockPumpManagerSettingsViewModel(pumpManager: pumpManager)
  29. title = pumpManager.localizedTitle
  30. self.supportedInsulinTypes = supportedInsulinTypes
  31. self.appName = appName
  32. self.allowDebugFeatures = allowDebugFeatures
  33. }
  34. var body: some View {
  35. List {
  36. statusSection
  37. activitySection
  38. configurationSection
  39. supportSection
  40. }
  41. .insetGroupedListStyle()
  42. .navigationBarItems(trailing: doneButton)
  43. .navigationBarTitle(Text(title), displayMode: .large)
  44. .alert(item: $presentedAlert, content: alert(for:))
  45. }
  46. @ViewBuilder
  47. private var statusSection: some View {
  48. Section {
  49. VStack(spacing: 8) {
  50. pumpProgressView
  51. .openMockPumpSettingsOnLongPress(enabled: true, pumpManager: viewModel.pumpManager, supportedInsulinTypes: supportedInsulinTypes)
  52. Divider()
  53. insulinInfo
  54. }
  55. }
  56. }
  57. private var pumpProgressView: some View {
  58. HStack(alignment: .center, spacing: 16) {
  59. pumpImage
  60. expirationArea
  61. .offset(y: -3)
  62. }
  63. }
  64. private var pumpImage: some View {
  65. ZStack {
  66. RoundedRectangle(cornerRadius: 5)
  67. .fill(Color(frameworkColor: "LightGrey")!)
  68. .frame(width: 77, height: 76)
  69. Image(frameworkImage: "Pump Simulator")
  70. .resizable()
  71. .aspectRatio(contentMode: ContentMode.fit)
  72. .frame(maxHeight: 70)
  73. .frame(width: 70)
  74. }
  75. }
  76. private var expirationArea: some View {
  77. VStack(alignment: .leading) {
  78. expirationText
  79. .offset(y: 4)
  80. expirationTime
  81. .offset(y: 10)
  82. progressBar
  83. }
  84. }
  85. private var expirationText: some View {
  86. Text("Pump expires in ")
  87. .font(.subheadline)
  88. .foregroundColor(.secondary)
  89. }
  90. private var expirationTime: some View {
  91. HStack(alignment: .lastTextBaseline) {
  92. Text("2")
  93. .font(.system(size: 24, weight: .heavy, design: .default))
  94. Text("days")
  95. .font(.system(size: 15, weight: .regular, design: .default))
  96. .foregroundColor(.secondary)
  97. .offset(x: -3)
  98. }
  99. }
  100. private var progressBar: some View {
  101. ProgressView(progress: viewModel.pumpExpirationPercentComplete)
  102. .accentColor(insulinTintColor)
  103. }
  104. var insulinInfo: some View {
  105. InsulinStatusView(viewModel: viewModel)
  106. .environment(\.guidanceColors, guidanceColors)
  107. .environment(\.insulinTintColor, insulinTintColor)
  108. }
  109. @ViewBuilder
  110. private var activitySection: some View {
  111. if (allowDebugFeatures) {
  112. settingsSubSection
  113. }
  114. suspendResumeInsulinSubSection
  115. deviceDetailsSubSection
  116. replaceSystemComponentsSubSection
  117. }
  118. private var suspendResumeInsulinSubSection: some View {
  119. Section(header: SectionHeader(label: LocalizedString("Activity", comment: "Section header for the activity section"))) {
  120. Button(action: suspendResumeTapped) {
  121. HStack {
  122. Image(systemName: "pause.circle.fill")
  123. .foregroundColor(viewModel.isDeliverySuspended ? guidanceColors.warning : .accentColor)
  124. Text(viewModel.suspendResumeInsulinDeliveryLabel)
  125. Spacer()
  126. if viewModel.transitioningSuspendResumeInsulinDelivery {
  127. ActivityIndicator(isAnimating: .constant(true), style: .medium)
  128. }
  129. }
  130. }
  131. .disabled(viewModel.transitioningSuspendResumeInsulinDelivery)
  132. if viewModel.isDeliverySuspended {
  133. LabeledValueView(label: LocalizedString("Suspended At", comment: "Label for suspended at field"),
  134. value: viewModel.suspendedAtString)
  135. }
  136. }
  137. }
  138. private func suspendResumeTapped() {
  139. if viewModel.isDeliverySuspended {
  140. viewModel.resumeDelivery() { error in
  141. if let error = error {
  142. self.presentedAlert = .resumeInsulinDeliveryError(error)
  143. }
  144. }
  145. } else {
  146. viewModel.suspendDelivery() { error in
  147. if let error = error {
  148. self.presentedAlert = .suspendInsulinDeliveryError(error)
  149. }
  150. }
  151. }
  152. }
  153. private var deviceDetailsSubSection: some View {
  154. Section {
  155. LabeledValueView(label: "Pump Paired", value: viewModel.lastPumpPairedDateTimeString)
  156. LabeledValueView(label: "Pump Expires", value: viewModel.pumpExpirationDateTimeString)
  157. NavigationLink(destination: DemoPlaceHolderView(appName: appName)) {
  158. Text("Device Details")
  159. }
  160. }
  161. }
  162. private var replaceSystemComponentsSubSection: some View {
  163. Section {
  164. NavigationLink(destination: DemoPlaceHolderView(appName: appName)) {
  165. Text("Replace Pump")
  166. .foregroundColor(.accentColor)
  167. }
  168. }
  169. }
  170. private var settingsSubSection: some View {
  171. Section {
  172. NavigationLink(destination: MockPumpManagerControlsView(pumpManager: viewModel.pumpManager, supportedInsulinTypes: supportedInsulinTypes)) {
  173. Text("Simulator Settings")
  174. }
  175. }
  176. }
  177. @ViewBuilder
  178. private var configurationSection: some View {
  179. notificationSubSection
  180. pumpTimeSubSection
  181. }
  182. private var notificationSubSection: some View {
  183. Section(header: SectionHeader(label: "Configuration")) {
  184. NavigationLink(destination: DemoPlaceHolderView(appName: appName)) {
  185. Text("Notification Settings")
  186. }
  187. }
  188. }
  189. private var pumpTimeSubSection: some View {
  190. Section {
  191. TimeView(label: "Pump Time")
  192. }
  193. }
  194. private var supportSection: some View {
  195. Section(header: SectionHeader(label: "Support")) {
  196. NavigationLink(destination: DemoPlaceHolderView(appName: appName)) {
  197. Text("Get help with your pump")
  198. }
  199. }
  200. }
  201. private var doneButton: some View {
  202. Button(LocalizedString("Done", comment: "Settings done button label"), action: dismiss)
  203. }
  204. private func alert(for presentedAlert: PresentedAlert) -> SwiftUI.Alert {
  205. switch presentedAlert {
  206. case .suspendInsulinDeliveryError(let error):
  207. return Alert(
  208. title: Text("Failed to Suspend Insulin Delivery"),
  209. message: Text(error.localizedDescription)
  210. )
  211. case .resumeInsulinDeliveryError(let error):
  212. return Alert(
  213. title: Text("Failed to Resume Insulin Delivery"),
  214. message: Text(error.localizedDescription)
  215. )
  216. }
  217. }
  218. }
  219. extension MockPumpManagerSettingsView.PresentedAlert: Identifiable {
  220. var id: Int {
  221. switch self {
  222. case .resumeInsulinDeliveryError:
  223. return 0
  224. case .suspendInsulinDeliveryError:
  225. return 1
  226. }
  227. }
  228. }
  229. struct MockPumpManagerSettingsView_Previews: PreviewProvider {
  230. static var previews: some View {
  231. MockPumpManagerSettingsView(pumpManager: MockPumpManager(), supportedInsulinTypes: [], appName: "Loop", allowDebugFeatures: false)
  232. }
  233. }