EditOverrideForm.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. import Foundation
  2. import SwiftUI
  3. struct EditOverrideForm: View {
  4. @ObservedObject var override: OverrideStored
  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 percentage: Double
  10. @State private var indefinite: Bool
  11. @State private var duration: Decimal
  12. @State private var target: Decimal?
  13. @State private var advancedSettings: Bool
  14. @State private var smbIsOff: Bool
  15. @State private var smbIsAlwaysOff: Bool
  16. @State private var start: Decimal?
  17. @State private var end: Decimal?
  18. @State private var isfAndCr: Bool
  19. @State private var isf: Bool
  20. @State private var cr: Bool
  21. @State private var smbMinutes: Decimal?
  22. @State private var uamMinutes: Decimal?
  23. @State private var hasChanges = false
  24. @State private var isEditing = false
  25. @State private var target_override = false
  26. @State private var showAlert = false
  27. init(overrideToEdit: OverrideStored, state: OverrideConfig.StateModel) {
  28. override = overrideToEdit
  29. _state = StateObject(wrappedValue: state)
  30. _name = State(initialValue: overrideToEdit.name ?? "")
  31. _percentage = State(initialValue: overrideToEdit.percentage)
  32. _indefinite = State(initialValue: overrideToEdit.indefinite)
  33. _duration = State(initialValue: overrideToEdit.duration?.decimalValue ?? 0)
  34. _target = State(
  35. initialValue: state.units == .mgdL ? overrideToEdit.target?.decimalValue : overrideToEdit.target?
  36. .decimalValue.asMmolL
  37. )
  38. _target_override = State(initialValue: overrideToEdit.target?.decimalValue != 0)
  39. _advancedSettings = State(initialValue: overrideToEdit.advancedSettings)
  40. _smbIsOff = State(initialValue: overrideToEdit.smbIsOff)
  41. _smbIsAlwaysOff = State(initialValue: overrideToEdit.smbIsAlwaysOff)
  42. _start = State(initialValue: overrideToEdit.start?.decimalValue)
  43. _end = State(initialValue: overrideToEdit.end?.decimalValue)
  44. _isfAndCr = State(initialValue: overrideToEdit.isfAndCr)
  45. _isf = State(initialValue: overrideToEdit.isf)
  46. _cr = State(initialValue: overrideToEdit.cr)
  47. _smbMinutes = State(initialValue: overrideToEdit.smbMinutes?.decimalValue)
  48. _uamMinutes = State(initialValue: overrideToEdit.uamMinutes?.decimalValue)
  49. }
  50. var color: LinearGradient {
  51. colorScheme == .dark ? LinearGradient(
  52. gradient: Gradient(colors: [
  53. Color.bgDarkBlue,
  54. Color.bgDarkerDarkBlue
  55. ]),
  56. startPoint: .top,
  57. endPoint: .bottom
  58. ) :
  59. LinearGradient(
  60. gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
  61. startPoint: .top,
  62. endPoint: .bottom
  63. )
  64. }
  65. private var formatter: NumberFormatter {
  66. let formatter = NumberFormatter()
  67. formatter.numberStyle = .decimal
  68. formatter.maximumFractionDigits = 0
  69. return formatter
  70. }
  71. private var glucoseFormatter: NumberFormatter {
  72. let formatter = NumberFormatter()
  73. formatter.numberStyle = .decimal
  74. formatter.maximumFractionDigits = 0
  75. if state.units == .mmolL {
  76. formatter.maximumFractionDigits = 1
  77. }
  78. formatter.roundingMode = .halfUp
  79. return formatter
  80. }
  81. var body: some View {
  82. NavigationView {
  83. Form {
  84. editOverride()
  85. saveButton
  86. }.scrollContentBackground(.hidden).background(color)
  87. .navigationTitle("Edit Override")
  88. .navigationBarTitleDisplayMode(.inline)
  89. .navigationBarItems(leading: Button("Close") {
  90. presentationMode.wrappedValue.dismiss()
  91. })
  92. .onDisappear {
  93. if !hasChanges {
  94. // Reset UI changes
  95. resetValues()
  96. }
  97. }
  98. .alert(isPresented: $state.showInvalidTargetAlert) {
  99. Alert(
  100. title: Text("Invalid Input"),
  101. message: Text("\(state.alertMessage)"),
  102. dismissButton: .default(Text("OK")) { state.showInvalidTargetAlert = false }
  103. )
  104. }
  105. }
  106. }
  107. @ViewBuilder private func editOverride() -> some View {
  108. if override.name != nil {
  109. Section {
  110. VStack {
  111. TextField("Name", text: $name)
  112. .onChange(of: name) { _ in hasChanges = true }
  113. }
  114. } header: {
  115. Text("Name")
  116. }.listRowBackground(Color.chart)
  117. }
  118. Section {
  119. VStack {
  120. Spacer()
  121. Text("\(percentage.formatted(.number)) %")
  122. .foregroundColor(
  123. state
  124. .overrideSliderPercentage >= 130 ? .red :
  125. (isEditing ? .orange : Color.tabBar)
  126. )
  127. .font(.largeTitle)
  128. Slider(
  129. value: $percentage,
  130. in: 10 ... 200,
  131. step: 1
  132. ).onChange(of: percentage) { _ in hasChanges = true }
  133. Spacer()
  134. Toggle(isOn: $indefinite) {
  135. Text("Enable indefinitely")
  136. }.onChange(of: indefinite) { _ in hasChanges = true }
  137. }
  138. if !indefinite {
  139. HStack {
  140. Text("Duration")
  141. TextFieldWithToolBar(
  142. text: Binding(
  143. get: { duration },
  144. set: {
  145. duration = $0
  146. hasChanges = true
  147. }
  148. ),
  149. placeholder: "0",
  150. numberFormatter: formatter
  151. )
  152. Text("minutes").foregroundColor(.secondary)
  153. }
  154. }
  155. HStack {
  156. Toggle(isOn: $target_override) {
  157. Text("Override Override Target")
  158. }.onChange(of: target_override) { _ in
  159. hasChanges = true
  160. }
  161. }
  162. if target_override {
  163. HStack {
  164. Text("Target Glucose")
  165. TextFieldWithToolBar(text: Binding(
  166. get: {
  167. target ?? 0
  168. },
  169. set: {
  170. target = $0
  171. hasChanges = true
  172. }
  173. ), placeholder: "0", numberFormatter: glucoseFormatter)
  174. Text(state.units.rawValue).foregroundColor(.secondary)
  175. }
  176. }
  177. Toggle(isOn: $advancedSettings) {
  178. Text("More options")
  179. }.onChange(of: advancedSettings) { _ in hasChanges = true }
  180. if advancedSettings {
  181. Toggle(isOn: $smbIsOff) {
  182. Text("Disable SMBs")
  183. }.onChange(of: smbIsOff) { _ in hasChanges = true }
  184. Toggle(isOn: $smbIsAlwaysOff) {
  185. Text("Schedule when SMBs are Off")
  186. }.onChange(of: smbIsAlwaysOff) { _ in hasChanges = true }
  187. if smbIsAlwaysOff {
  188. HStack {
  189. Text("First Hour SMBs are Off (24 hours)")
  190. TextFieldWithToolBar(
  191. text: Binding(
  192. get: { start ?? 0 },
  193. set: {
  194. start = $0
  195. hasChanges = true
  196. }
  197. ),
  198. placeholder: "0",
  199. numberFormatter: formatter
  200. )
  201. Text("hour").foregroundColor(.secondary)
  202. }
  203. HStack {
  204. Text("Last Hour SMBs are Off (24 hours)")
  205. TextFieldWithToolBar(
  206. text: Binding(
  207. get: { end ?? 23 },
  208. set: {
  209. end = $0
  210. hasChanges = true
  211. }
  212. ),
  213. placeholder: "0",
  214. numberFormatter: formatter
  215. )
  216. Text("hour").foregroundColor(.secondary)
  217. }
  218. }
  219. Toggle(isOn: $isfAndCr) {
  220. Text("Change ISF and CR")
  221. }.onChange(of: isfAndCr) { _ in hasChanges = true }
  222. if !isfAndCr {
  223. Toggle(isOn: $isf) {
  224. Text("Change ISF")
  225. }.onChange(of: isf) { _ in hasChanges = true }
  226. Toggle(isOn: $cr) {
  227. Text("Change CR")
  228. }.onChange(of: cr) { _ in hasChanges = true }
  229. }
  230. HStack {
  231. Text("SMB Minutes")
  232. TextFieldWithToolBar(
  233. text: Binding(
  234. get: { smbMinutes ?? state.defaultSmbMinutes },
  235. set: {
  236. smbMinutes = $0
  237. hasChanges = true
  238. }
  239. ),
  240. placeholder: "0",
  241. numberFormatter: formatter
  242. )
  243. Text("minutes").foregroundColor(.secondary)
  244. }
  245. HStack {
  246. Text("UAM SMB Minutes")
  247. TextFieldWithToolBar(
  248. text: Binding(
  249. get: { uamMinutes ?? state.defaultUamMinutes },
  250. set: {
  251. uamMinutes = $0
  252. hasChanges = true
  253. }
  254. ),
  255. placeholder: "0",
  256. numberFormatter: formatter
  257. )
  258. Text("minutes").foregroundColor(.secondary)
  259. }
  260. }
  261. }.listRowBackground(Color.chart)
  262. }
  263. private var saveButton: some View {
  264. HStack {
  265. Spacer()
  266. Button(action: {
  267. if !state.isInputInvalid(target: target ?? 0) {
  268. saveChanges()
  269. do {
  270. guard let moc = override.managedObjectContext else { return }
  271. guard moc.hasChanges else { return }
  272. try moc.save()
  273. // Disable previous active Override
  274. if let currentActiveOverride = state.currentActiveOverride {
  275. Task {
  276. await state.disableAllActiveOverrides(
  277. except: currentActiveOverride.objectID,
  278. createOverrideRunEntry: false
  279. )
  280. // Update View
  281. state.updateLatestOverrideConfiguration()
  282. }
  283. }
  284. hasChanges = false
  285. presentationMode.wrappedValue.dismiss()
  286. } catch {
  287. debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to edit Override")
  288. }
  289. }
  290. }, label: {
  291. Text("Save")
  292. })
  293. .disabled(!hasChanges)
  294. .frame(maxWidth: .infinity, alignment: .center)
  295. .tint(.white)
  296. Spacer()
  297. }.listRowBackground(hasChanges ? Color(.systemBlue) : Color(.systemGray4))
  298. }
  299. private func saveChanges() {
  300. if !override.isPreset, hasChanges, name == (override.name ?? "") {
  301. override.name = "Custom Override"
  302. } else {
  303. override.name = name
  304. }
  305. override.percentage = percentage
  306. override.indefinite = indefinite
  307. override.duration = NSDecimalNumber(decimal: duration)
  308. if target_override {
  309. override.target = target.map {
  310. state.units == .mmolL ? NSDecimalNumber(decimal: $0.asMgdL) : NSDecimalNumber(decimal: $0)
  311. }
  312. } else {
  313. override.target = 0
  314. }
  315. override.advancedSettings = advancedSettings
  316. override.smbIsOff = smbIsOff
  317. override.smbIsAlwaysOff = smbIsAlwaysOff
  318. override.start = start.map { NSDecimalNumber(decimal: $0) }
  319. override.end = end.map { NSDecimalNumber(decimal: $0) }
  320. override.isfAndCr = isfAndCr
  321. override.isf = isf
  322. override.cr = cr
  323. override.smbMinutes = smbMinutes.map { NSDecimalNumber(decimal: $0) }
  324. override.uamMinutes = uamMinutes.map { NSDecimalNumber(decimal: $0) }
  325. override.isUploadedToNS = false
  326. }
  327. private func resetValues() {
  328. name = override.name ?? ""
  329. percentage = override.percentage
  330. indefinite = override.indefinite
  331. duration = override.duration?.decimalValue ?? 0
  332. target = override.target?.decimalValue
  333. advancedSettings = override.advancedSettings
  334. smbIsOff = override.smbIsOff
  335. smbIsAlwaysOff = override.smbIsAlwaysOff
  336. start = override.start?.decimalValue
  337. end = override.end?.decimalValue
  338. isfAndCr = override.isfAndCr
  339. isf = override.isf
  340. cr = override.cr
  341. smbMinutes = override.smbMinutes?.decimalValue ?? state.defaultSmbMinutes
  342. uamMinutes = override.uamMinutes?.decimalValue ?? state.defaultUamMinutes
  343. }
  344. }