EditTempTargetForm.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import Foundation
  2. import SwiftUI
  3. struct EditTempTargetForm: View {
  4. @ObservedObject var tempTarget: TempTargetStored
  5. @Environment(\.presentationMode) var presentationMode
  6. @Environment(\.colorScheme) var colorScheme
  7. @StateObject var state: OverrideConfig.StateModel
  8. @State private var name: String
  9. @State private var target: Decimal
  10. @State private var duration: Decimal
  11. @State private var date: Date
  12. @State private var halfBasalTarget: Decimal
  13. @State private var percentage: Decimal
  14. @State private var hasChanges = false
  15. @State private var showAlert = false
  16. @State private var isUsingSlider = false
  17. init(tempTargetToEdit: TempTargetStored, state: OverrideConfig.StateModel) {
  18. tempTarget = tempTargetToEdit
  19. _state = StateObject(wrappedValue: state)
  20. _name = State(initialValue: tempTargetToEdit.name ?? "")
  21. _target = State(initialValue: tempTargetToEdit.target?.decimalValue ?? 0)
  22. _duration = State(initialValue: tempTargetToEdit.duration?.decimalValue ?? 0)
  23. _date = State(initialValue: tempTargetToEdit.date ?? Date())
  24. _halfBasalTarget = State(initialValue: tempTargetToEdit.halfBasalTarget?.decimalValue ?? 160)
  25. let normalTarget: Decimal = 100
  26. if let halfBasal = tempTargetToEdit.halfBasalTarget?.decimalValue {
  27. let target = state.units == .mgdL ? state.tempTargetTarget.asMgdL : state.tempTargetTarget
  28. let ratio = (halfBasal - normalTarget + (normalTarget * target) / 2) / normalTarget
  29. _percentage = State(initialValue: ratio * 100)
  30. } else {
  31. _percentage = State(initialValue: 100)
  32. }
  33. }
  34. var color: LinearGradient {
  35. colorScheme == .dark ? LinearGradient(
  36. gradient: Gradient(colors: [
  37. Color.bgDarkBlue,
  38. Color.bgDarkerDarkBlue
  39. ]),
  40. startPoint: .top,
  41. endPoint: .bottom
  42. ) :
  43. LinearGradient(
  44. gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
  45. startPoint: .top,
  46. endPoint: .bottom
  47. )
  48. }
  49. private var formatter: NumberFormatter {
  50. let formatter = NumberFormatter()
  51. formatter.numberStyle = .decimal
  52. formatter.maximumFractionDigits = 0
  53. return formatter
  54. }
  55. private var glucoseFormatter: NumberFormatter {
  56. let formatter = NumberFormatter()
  57. formatter.numberStyle = .decimal
  58. if state.units == .mmolL {
  59. formatter.maximumFractionDigits = 1
  60. } else {
  61. formatter.maximumFractionDigits = 0
  62. }
  63. formatter.roundingMode = .halfUp
  64. return formatter
  65. }
  66. var body: some View {
  67. NavigationView {
  68. Form {
  69. editTempTarget()
  70. saveButton
  71. }.scrollContentBackground(.hidden)
  72. .background(color)
  73. .navigationTitle("Edit Temp Target")
  74. .navigationBarTitleDisplayMode(.inline)
  75. .navigationBarItems(leading: Button("Close") {
  76. presentationMode.wrappedValue.dismiss()
  77. })
  78. .onDisappear {
  79. if !hasChanges {
  80. // Reset UI changes
  81. resetValues()
  82. }
  83. }
  84. .alert(isPresented: $state.showInvalidTargetAlert) {
  85. Alert(
  86. title: Text("Invalid Input"),
  87. message: Text("\(state.alertMessage)"),
  88. dismissButton: .default(Text("OK")) { state.showInvalidTargetAlert = false }
  89. )
  90. }
  91. }
  92. }
  93. @ViewBuilder private func editTempTarget() -> some View {
  94. Section {
  95. VStack {
  96. TextField("Name", text: $name)
  97. .onChange(of: name) { _ in hasChanges = true }
  98. }
  99. } header: {
  100. Text("Name")
  101. }.listRowBackground(Color.chart)
  102. Section {
  103. VStack {
  104. VStack {
  105. Text("\(state.percentage.formatted(.number)) % Insulin")
  106. .foregroundColor(isUsingSlider ? .orange : Color.tabBar)
  107. .font(.largeTitle)
  108. Slider(value: Binding(
  109. get: {
  110. Double(truncating: percentage as NSNumber) // Decimal in Double umwandeln
  111. },
  112. set: { newValue in
  113. percentage = Decimal(newValue) // Den neuen Slider-Wert speichern
  114. hasChanges = true
  115. // Berechne das halfBasalTarget basierend auf dem neuen percentage-Wert
  116. let ratio = Decimal(Int(percentage) / 100)
  117. let normalTarget: Decimal = 100
  118. var target: Decimal = state.tempTargetTarget
  119. if state.units == .mmolL {
  120. target = target.asMgdL
  121. }
  122. if ratio != 1 {
  123. let hbtcalc = ((2 * ratio * normalTarget) - normalTarget - (ratio * target)) / (ratio - 1)
  124. halfBasalTarget = hbtcalc
  125. } else {
  126. halfBasalTarget = normalTarget
  127. }
  128. }
  129. ), in: Double(state.computeSliderLow()) ... Double(state.computeSliderHigh()), step: 5) {}
  130. minimumValueLabel: {
  131. Text("\(state.computeSliderLow(), specifier: "%.0f")%")
  132. }
  133. maximumValueLabel: {
  134. Text("\(state.computeSliderHigh(), specifier: "%.0f")%")
  135. }
  136. onEditingChanged: { editing in
  137. isUsingSlider = editing
  138. state.halfBasalTarget = Decimal(state.computeHalfBasalTarget())
  139. }
  140. Divider()
  141. Text(
  142. state
  143. .units == .mgdL ?
  144. "Half Basal Exercise Target at: \(state.halfBasalTarget.formatted(.number.precision(.fractionLength(0)))) mg/dl" :
  145. "Half Basal Exercise Target at: \(state.halfBasalTarget.asMmolL.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))) mmol/L"
  146. )
  147. .foregroundColor(.secondary)
  148. .font(.caption).italic()
  149. }
  150. }
  151. } header: {
  152. Text("% Insulin")
  153. }.listRowBackground(Color.chart)
  154. Section {
  155. HStack {
  156. Text("Target")
  157. Spacer()
  158. TextFieldWithToolBar(
  159. text: Binding(
  160. get: { target },
  161. set: {
  162. target = $0
  163. hasChanges = true
  164. }
  165. ),
  166. placeholder: "0",
  167. numberFormatter: glucoseFormatter
  168. )
  169. Text(state.units.rawValue).foregroundColor(.secondary)
  170. }
  171. HStack {
  172. Text("Duration")
  173. Spacer()
  174. TextFieldWithToolBar(
  175. text: Binding(
  176. get: { duration },
  177. set: {
  178. duration = $0
  179. hasChanges = true
  180. }
  181. ),
  182. placeholder: "0",
  183. numberFormatter: formatter
  184. )
  185. Text("minutes").foregroundColor(.secondary)
  186. }
  187. DatePicker("Date", selection: $date)
  188. .onChange(of: date) { _ in hasChanges = true }
  189. }.listRowBackground(Color.chart)
  190. }
  191. private var saveButton: some View {
  192. HStack {
  193. Spacer()
  194. Button(action: {
  195. if !state.isInputInvalid(target: target) {
  196. saveChanges()
  197. do {
  198. guard let moc = tempTarget.managedObjectContext else { return }
  199. guard moc.hasChanges else { return }
  200. try moc.save()
  201. // Disable previous active Temp Target and update View
  202. if let currentActiveTempTarget = state.currentActiveTempTarget {
  203. Task {
  204. await state.disableAllActiveOverrides(
  205. except: currentActiveTempTarget.objectID,
  206. createOverrideRunEntry: false
  207. )
  208. state.updateLatestTempTargetConfiguration()
  209. }
  210. }
  211. hasChanges = false
  212. presentationMode.wrappedValue.dismiss()
  213. } catch {
  214. debugPrint("Failed to edit Temp Target")
  215. }
  216. }
  217. }, label: {
  218. Text("Save")
  219. })
  220. .disabled(!hasChanges)
  221. .frame(maxWidth: .infinity, alignment: .center)
  222. .tint(.white)
  223. Spacer()
  224. }.listRowBackground(hasChanges ? Color(.systemBlue) : Color(.systemGray4))
  225. }
  226. private func saveChanges() {
  227. tempTarget.name = name
  228. tempTarget.target = NSDecimalNumber(decimal: target)
  229. tempTarget.duration = NSDecimalNumber(decimal: duration)
  230. tempTarget.date = date
  231. tempTarget.isUploadedToNS = false
  232. tempTarget.halfBasalTarget = NSDecimalNumber(decimal: halfBasalTarget)
  233. }
  234. private func resetValues() {
  235. name = tempTarget.name ?? ""
  236. target = tempTarget.target?.decimalValue ?? 0
  237. duration = tempTarget.duration?.decimalValue ?? 0
  238. date = tempTarget.date ?? Date()
  239. }
  240. }