AddTempTargetForm.swift 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import Foundation
  2. import SwiftUI
  3. struct AddTempTargetForm: View {
  4. @StateObject var state: OverrideConfig.StateModel
  5. @Environment(\.presentationMode) var presentationMode
  6. @Environment(\.colorScheme) var colorScheme
  7. @Environment(\.dismiss) var dismiss
  8. @State private var showAlert = false
  9. @State private var showPresetAlert = false
  10. @State private var alertString = ""
  11. @State private var isUsingSlider = false
  12. var color: LinearGradient {
  13. colorScheme == .dark ? LinearGradient(
  14. gradient: Gradient(colors: [
  15. Color.bgDarkBlue,
  16. Color.bgDarkerDarkBlue
  17. ]),
  18. startPoint: .top,
  19. endPoint: .bottom
  20. )
  21. :
  22. LinearGradient(
  23. gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
  24. startPoint: .top,
  25. endPoint: .bottom
  26. )
  27. }
  28. private var formatter: NumberFormatter {
  29. let formatter = NumberFormatter()
  30. formatter.numberStyle = .decimal
  31. formatter.maximumFractionDigits = 0
  32. return formatter
  33. }
  34. private var glucoseFormatter: NumberFormatter {
  35. let formatter = NumberFormatter()
  36. formatter.numberStyle = .decimal
  37. formatter.maximumFractionDigits = 0
  38. if state.units == .mmolL {
  39. formatter.maximumFractionDigits = 1
  40. }
  41. formatter.roundingMode = .halfUp
  42. return formatter
  43. }
  44. var body: some View {
  45. NavigationView {
  46. Form {
  47. addTempTarget()
  48. }.scrollContentBackground(.hidden).background(color)
  49. .navigationTitle("Add Temp Target")
  50. .navigationBarItems(trailing: Button("Cancel") {
  51. presentationMode.wrappedValue.dismiss()
  52. })
  53. .alert(
  54. "Start Temp Target",
  55. isPresented: $showAlert,
  56. actions: {
  57. Button("Cancel", role: .cancel) { state.isTempTargetEnabled = false }
  58. Button("Start Temp Target", role: .destructive) {
  59. Task {
  60. await setupAlertString()
  61. state.isTempTargetEnabled.toggle()
  62. await state.saveCustomTempTarget()
  63. await state.resetTempTargetState()
  64. dismiss()
  65. }
  66. }
  67. },
  68. message: {
  69. Text(alertString)
  70. }
  71. )
  72. }
  73. }
  74. @ViewBuilder private func addTempTarget() -> some View {
  75. Section {
  76. VStack {
  77. TextField("Name", text: $state.tempTargetName)
  78. }
  79. } header: {
  80. Text("Name")
  81. }.listRowBackground(Color.chart)
  82. if state.computeSliderLow() != state.computeSliderHigh() {
  83. Section {
  84. VStack {
  85. Text("\(state.percentage.formatted(.number)) % Insulin")
  86. .foregroundColor(isUsingSlider ? .orange : Color.tabBar)
  87. .font(.largeTitle)
  88. Slider(value: $state.percentage, in: state.computeSliderLow() ... state.computeSliderHigh(), step: 5) {}
  89. minimumValueLabel: {
  90. Text("\(state.computeSliderLow(), specifier: "%.0f")%")
  91. }
  92. maximumValueLabel: {
  93. Text("\(state.computeSliderHigh(), specifier: "%.0f")%")
  94. }
  95. onEditingChanged: { editing in
  96. isUsingSlider = editing
  97. state.halfBasalTarget = Decimal(state.computeHalfBasalTarget())
  98. }
  99. .disabled(!sliderEnabled)
  100. Divider()
  101. Text(
  102. state
  103. .units == .mgdL ?
  104. "Half Basal Exercise Target at: \(state.computeHalfBasalTarget().formatted(.number.precision(.fractionLength(0)))) mg/dl" :
  105. "Half Basal Exercise Target at: \(state.computeHalfBasalTarget().asMmolL.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))) mmol/L"
  106. )
  107. .foregroundColor(.secondary)
  108. .font(.caption).italic()
  109. }
  110. } header: {
  111. Text("% Insulin")
  112. }.listRowBackground(Color.chart)
  113. }
  114. Section {
  115. HStack {
  116. Text("Target")
  117. Spacer()
  118. TextFieldWithToolBar(text: $state.tempTargetTarget, placeholder: "0", numberFormatter: glucoseFormatter)
  119. Text(state.units.rawValue).foregroundColor(.secondary)
  120. }
  121. HStack {
  122. Text("Duration")
  123. Spacer()
  124. TextFieldWithToolBar(text: $state.tempTargetDuration, placeholder: "0", numberFormatter: formatter)
  125. Text("minutes").foregroundColor(.secondary)
  126. }
  127. DatePicker("Date", selection: $state.date)
  128. HStack {
  129. Button {
  130. showAlert.toggle()
  131. }
  132. label: { Text("Enact") }
  133. .disabled(state.tempTargetDuration == 0)
  134. .buttonStyle(BorderlessButtonStyle())
  135. .font(.callout)
  136. .controlSize(.mini)
  137. Button {
  138. Task {
  139. await state.saveTempTargetPreset()
  140. dismiss()
  141. }
  142. }
  143. label: { Text("Save as preset") }
  144. .disabled(state.tempTargetDuration == 0)
  145. .tint(.orange)
  146. .frame(maxWidth: .infinity, alignment: .trailing)
  147. .buttonStyle(BorderlessButtonStyle())
  148. .controlSize(.mini)
  149. }
  150. } header: {
  151. Text("Add Custom Temp Target")
  152. }.listRowBackground(Color.chart)
  153. }
  154. var sliderEnabled: Bool {
  155. state.computeSliderHigh() > state.computeSliderLow()
  156. }
  157. private func setupAlertString() async {
  158. alertString =
  159. (
  160. state.tempTargetDuration > 0 ?
  161. (
  162. state
  163. .tempTargetDuration
  164. .formatted(.number.grouping(.never).rounded().precision(.fractionLength(0))) +
  165. " min."
  166. ) :
  167. NSLocalizedString(" infinite duration.", comment: "")
  168. ) +
  169. (
  170. state.tempTargetTarget == 0 ? "" :
  171. (" Target: " + state.tempTargetTarget.formatted() + " " + state.units.rawValue + ".")
  172. )
  173. +
  174. "\n\n"
  175. +
  176. NSLocalizedString(
  177. "Starting this Temp Target will change your profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Temp Target” will start your new Temp Target or edit your current active Temp Target.",
  178. comment: ""
  179. )
  180. }
  181. }