CarbEntryEditorView.swift 9.1 KB

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