CarbEntryEditorView.swift 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. //
  2. // CarbEntryEditorView.swift
  3. // FreeAPS
  4. //
  5. // Created by Marvin Polscheit on 15.01.25.
  6. //
  7. import CoreData
  8. import SwiftUI
  9. struct CarbEntryEditorView: View {
  10. @Environment(\.dismiss) private var dismiss
  11. @Environment(\.colorScheme) var colorScheme
  12. @Environment(AppState.self) var appState
  13. var state: DataTable.StateModel
  14. let carbEntry: CarbEntryStored
  15. /*
  16. This is the objectID of the entry that the user is editing. It is NOT always the `carbEntry: CarbEntryStored` that we pass to the `CarbEntryEditorView`.
  17. We need this because FPUs and carbs are treated completely different and that complicates the update process.
  18. */
  19. @State private var entryToEdit: NSManagedObjectID?
  20. @State private var editedCarbs: Decimal
  21. @State private var editedFat: Decimal
  22. @State private var editedProtein: Decimal
  23. @State private var editedNote: String
  24. @State private var isFPU: Bool
  25. init(state: DataTable.StateModel, carbEntry: CarbEntryStored) {
  26. self.state = state
  27. self.carbEntry = carbEntry
  28. _editedCarbs = State(initialValue: 0) // gets updated in the task block
  29. _editedFat = State(initialValue: 0) // gets updated in the task block
  30. _editedProtein = State(initialValue: 0) // gets updated in the task block
  31. _editedNote = State(initialValue: carbEntry.note ?? "")
  32. _isFPU = State(initialValue: carbEntry.isFPU)
  33. _entryToEdit = State(initialValue: nil)
  34. }
  35. private var carbLimitExceeded: Bool {
  36. editedCarbs > state.settingsManager.settings.maxCarbs
  37. }
  38. private var fatLimitExceeded: Bool {
  39. editedFat > state.settingsManager.settings.maxFat
  40. }
  41. private var proteinLimitExceeded: Bool {
  42. editedProtein > state.settingsManager.settings.maxProtein
  43. }
  44. private var limitExceeded: Bool {
  45. carbLimitExceeded || fatLimitExceeded || proteinLimitExceeded
  46. }
  47. private var isButtonDisabled: Bool {
  48. editedCarbs == 0 && editedFat == 0 && editedProtein == 0
  49. }
  50. private var buttonLabel: some View {
  51. if carbLimitExceeded {
  52. return Text("Max Carbs of \(state.settingsManager.settings.maxCarbs.description) g Exceeded")
  53. } else if fatLimitExceeded {
  54. return Text("Max Fat of \(state.settingsManager.settings.maxFat.description) g Exceeded")
  55. } else if proteinLimitExceeded {
  56. return Text("Max Protein of \(state.settingsManager.settings.maxProtein.description) g Exceeded")
  57. }
  58. return Text("Save and Update")
  59. }
  60. private var buttonBackgroundColor: Color {
  61. var treatmentButtonBackground = Color(.systemBlue)
  62. if limitExceeded {
  63. treatmentButtonBackground = Color(.systemRed)
  64. } else if isButtonDisabled {
  65. treatmentButtonBackground = Color(.systemGray)
  66. }
  67. return treatmentButtonBackground
  68. }
  69. var stickyButton: some View {
  70. ZStack {
  71. Rectangle()
  72. .frame(width: UIScreen.main.bounds.width, height: 65)
  73. .foregroundStyle(colorScheme == .dark ? Color.bgDarkerDarkBlue : Color.white)
  74. .background(.thinMaterial)
  75. .opacity(0.8)
  76. .clipShape(Rectangle())
  77. Button(
  78. action: {
  79. guard let entryToEdit = entryToEdit else { return }
  80. state.updateEntry(
  81. entryToEdit,
  82. newCarbs: editedCarbs,
  83. newFat: editedFat,
  84. newProtein: editedProtein,
  85. newNote: editedNote
  86. )
  87. dismiss()
  88. }, label: {
  89. buttonLabel
  90. .font(.headline)
  91. .frame(maxWidth: .infinity, maxHeight: .infinity)
  92. .padding(10)
  93. }
  94. )
  95. .frame(width: UIScreen.main.bounds.width * 0.9, height: 40, alignment: .center)
  96. .disabled(isButtonDisabled)
  97. .background(buttonBackgroundColor)
  98. .tint(.white)
  99. .clipShape(RoundedRectangle(cornerRadius: 8))
  100. }
  101. }
  102. var body: some View {
  103. NavigationView {
  104. Form {
  105. Section {
  106. HStack {
  107. Text("Carbs")
  108. TextFieldWithToolBar(
  109. text: $editedCarbs,
  110. placeholder: "0",
  111. keyboardType: .numberPad,
  112. numberFormatter: Formatter.decimalFormatterWithOneFractionDigit
  113. )
  114. Text("g").foregroundStyle(.secondary)
  115. }
  116. if state.settingsManager.settings.useFPUconversion {
  117. HStack {
  118. Text("Protein")
  119. TextFieldWithToolBar(
  120. text: $editedProtein,
  121. placeholder: "0",
  122. keyboardType: .numberPad,
  123. numberFormatter: Formatter.decimalFormatterWithOneFractionDigit
  124. )
  125. Text("g").foregroundStyle(.secondary)
  126. }
  127. HStack {
  128. Text("Fat")
  129. TextFieldWithToolBar(
  130. text: $editedFat,
  131. placeholder: "0",
  132. keyboardType: .numberPad,
  133. numberFormatter: Formatter.decimalFormatterWithOneFractionDigit
  134. )
  135. Text("g").foregroundStyle(.secondary)
  136. }
  137. }
  138. HStack {
  139. Image(systemName: "square.and.pencil")
  140. TextFieldWithToolBarString(text: $editedNote, placeholder: "Note...", maxLength: 25)
  141. }
  142. }.listRowBackground(Color.chart)
  143. }
  144. .safeAreaInset(
  145. edge: .bottom,
  146. spacing: 30
  147. ) {
  148. stickyButton
  149. }
  150. .scrollContentBackground(.hidden)
  151. .background(appState.trioBackgroundColor(for: colorScheme))
  152. .navigationTitle("Edit Meal")
  153. .navigationBarTitleDisplayMode(.inline)
  154. .toolbar {
  155. ToolbarItem(placement: .topBarLeading) {
  156. Button("Cancel") {
  157. dismiss()
  158. }
  159. }
  160. }
  161. }
  162. .task {
  163. /*
  164. User taps on a FPU entry in the DataTable list. There are two cases:
  165. - the User has entered this FPU entry WITH carbs
  166. - the User has entered this FPU entry WITHOUT carbs
  167. In the first case, we simply need to load the corresponding carb entry. For this case THIS is the entry we want to edit.
  168. In the second case, we need to load the zero-carb entry that actualy holds the FPU values (and the carbs). For this case THIS is the entry we want to edit.
  169. */
  170. if carbEntry.isFPU {
  171. if let result = await state.handleFPUEntry(carbEntry.objectID) {
  172. editedCarbs = result.entryValues?.carbs ?? 0
  173. editedFat = result.entryValues?.fat ?? 0
  174. editedProtein = result.entryValues?.protein ?? 0
  175. editedNote = result.entryValues?.note ?? ""
  176. entryToEdit = result.entryID
  177. }
  178. /*
  179. User taps on a carb entry in the DataTable list. There are again two cases which don't need explicit handling:
  180. - the User has only entered carbs
  181. - the User has entered carbs with FPU
  182. In both cases, we need to simply load the carb entry that holds all the necessary values for us. This is the entry we want to edit.
  183. */
  184. } else {
  185. if let values = await state.loadEntryValues(from: carbEntry.objectID) {
  186. editedCarbs = values.carbs
  187. editedFat = values.fat
  188. editedProtein = values.protein
  189. editedNote = values.note
  190. entryToEdit = carbEntry.objectID
  191. }
  192. }
  193. }
  194. }
  195. }