EditProfileForm.swift 12 KB

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