CarbEntryEditorView.swift 8.8 KB

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