ManualTempBasalEntryView.swift 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. //
  2. // ManualTempBasalEntryView.swift
  3. // OmniKit
  4. //
  5. // Created by Pete Schwamb on 5/14/22.
  6. // Copyright © 2022 LoopKit Authors. All rights reserved.
  7. //
  8. import SwiftUI
  9. import LoopKitUI
  10. import LoopKit
  11. import HealthKit
  12. import OmniKit
  13. struct ManualTempBasalEntryView: View {
  14. @Environment(\.guidanceColors) var guidanceColors
  15. var enactBasal: ((Double,TimeInterval,@escaping (PumpManagerError?)->Void) -> Void)?
  16. var didCancel: (() -> Void)?
  17. @State private var rateEntered: Double = 0.0
  18. @State private var durationEntered: TimeInterval = .hours(0.5)
  19. @State private var showPicker: Bool = false
  20. @State private var error: PumpManagerError?
  21. @State private var enacting: Bool = false
  22. @State private var showingErrorAlert: Bool = false
  23. @State private var showingMissingConfigAlert: Bool = false
  24. var allowedRates: [Double]
  25. init(enactBasal: ((Double,TimeInterval,@escaping (PumpManagerError?)->Void) -> Void)? = nil, didCancel: (() -> Void)? = nil, allowedRates: [Double]) {
  26. self.enactBasal = enactBasal
  27. self.didCancel = didCancel
  28. self.allowedRates = allowedRates
  29. // This is to handle users migrating from OmnipodPumpManagerState with no max temp basal set
  30. if allowedRates.count <= 1 {
  31. _showingMissingConfigAlert = State(initialValue: true)
  32. }
  33. }
  34. private static let rateFormatter: QuantityFormatter = {
  35. let quantityFormatter = QuantityFormatter()
  36. quantityFormatter.setPreferredNumberFormatter(for: .internationalUnitsPerHour)
  37. quantityFormatter.numberFormatter.minimumFractionDigits = 2
  38. return quantityFormatter
  39. }()
  40. private var rateUnitsLabel: some View {
  41. Text(QuantityFormatter().string(from: .internationalUnitsPerHour))
  42. .foregroundColor(Color(.secondaryLabel))
  43. }
  44. private static let durationFormatter: QuantityFormatter = {
  45. let quantityFormatter = QuantityFormatter()
  46. quantityFormatter.setPreferredNumberFormatter(for: .hour())
  47. quantityFormatter.numberFormatter.minimumFractionDigits = 1
  48. quantityFormatter.numberFormatter.maximumFractionDigits = 1
  49. quantityFormatter.unitStyle = .long
  50. return quantityFormatter
  51. }()
  52. private var durationUnitsLabel: some View {
  53. Text(QuantityFormatter().string(from: .hour()))
  54. .foregroundColor(Color(.secondaryLabel))
  55. }
  56. func formatRate(_ rate: Double) -> String {
  57. let unit = HKUnit.internationalUnitsPerHour
  58. return ManualTempBasalEntryView.rateFormatter.string(from: HKQuantity(unit: unit, doubleValue: rate), for: unit) ?? ""
  59. }
  60. func formatDuration(_ duration: TimeInterval) -> String {
  61. let unit = HKUnit.hour()
  62. return ManualTempBasalEntryView.durationFormatter.string(from: HKQuantity(unit: unit, doubleValue: duration.hours), for: unit) ?? ""
  63. }
  64. var body: some View {
  65. NavigationView {
  66. VStack {
  67. List {
  68. HStack {
  69. Text(LocalizedString("Rate", comment: "Label text for basal rate summary"))
  70. Spacer()
  71. Text(String(format: LocalizedString("%1$@ for %2$@", comment: "Summary string for temporary basal rate configuration page"), formatRate(rateEntered), formatDuration(durationEntered)))
  72. }
  73. HStack {
  74. ResizeablePicker(selection: $rateEntered,
  75. data: allowedRates,
  76. formatter: { formatRate($0) })
  77. ResizeablePicker(selection: $durationEntered,
  78. data: Pod.supportedTempBasalDurations,
  79. formatter: { formatDuration($0) })
  80. }
  81. .frame(maxHeight: 162.0)
  82. .alert(isPresented: $showingMissingConfigAlert, content: { missingConfigAlert })
  83. Section {
  84. Text(LocalizedString("Your insulin delivery will not be automatically adjusted until the temporary basal rate finishes or is canceled.", comment: "Description text on manual temp basal action sheet"))
  85. .font(.footnote)
  86. .foregroundColor(.secondary)
  87. .fixedSize(horizontal: false, vertical: true)
  88. }
  89. }
  90. Button(action: {
  91. enacting = true
  92. enactBasal?(rateEntered, durationEntered) { (error) in
  93. if let error = error {
  94. self.error = error
  95. showingErrorAlert = true
  96. }
  97. enacting = false
  98. }
  99. }) {
  100. HStack {
  101. if enacting {
  102. ProgressView()
  103. } else {
  104. Text(LocalizedString("Set Temporary Basal", comment: "Button text for setting manual temporary basal rate"))
  105. }
  106. }
  107. }
  108. .buttonStyle(ActionButtonStyle(.primary))
  109. .padding()
  110. }
  111. .navigationTitle(LocalizedString("Temporary Basal", comment: "Navigation Title for ManualTempBasalEntryView"))
  112. .navigationBarItems(trailing: cancelButton)
  113. .alert(isPresented: $showingErrorAlert, content: { errorAlert })
  114. .disabled(enacting)
  115. }
  116. }
  117. var errorAlert: SwiftUI.Alert {
  118. let errorMessage = errorMessage(error: error!)
  119. return SwiftUI.Alert(
  120. title: Text(LocalizedString("Temporary Basal Failed", comment: "Alert title for a failure to set temporary basal")),
  121. message: errorMessage)
  122. }
  123. func errorMessage(error: PumpManagerError) -> Text {
  124. if let recovery = error.recoverySuggestion {
  125. return Text(String(format: LocalizedString("Unable to set a temporary basal rate: %1$@\n\n%2$@", comment: "Alert format string for a failure to set temporary basal with recovery suggestion. (1: error description) (2: recovery text)"), error.localizedDescription, recovery))
  126. } else {
  127. return Text(String(format: LocalizedString("Unable to set a temporary basal rate: %1$@", comment: "Alert format string for a failure to set temporary basal. (1: error description)"), error.localizedDescription))
  128. }
  129. }
  130. var missingConfigAlert: SwiftUI.Alert {
  131. return SwiftUI.Alert(
  132. title: Text(LocalizedString("Missing Config", comment: "Alert title for missing temp basal configuration")),
  133. message: Text(LocalizedString("This Pump has not been configured with a maximum basal rate because it was added before manual temp basal was a feature. Please go to Pump Settings in the settings CONFIGURATION section to set a new Max Basal.", comment: "Alert format string for missing temp basal configuration."))
  134. )
  135. }
  136. var cancelButton: some View {
  137. Button(LocalizedString("Cancel", comment: "Cancel button text in navigation bar on insert cannula screen")) {
  138. didCancel?()
  139. }
  140. .accessibility(identifier: "button_cancel")
  141. }
  142. }