AddTempTargetForm.swift 8.1 KB

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