EditOverrideForm.swift 13 KB

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