CarbEntryEditorView.swift 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. //
  2. // CarbEntryEditorView.swift
  3. // FreeAPS
  4. //
  5. // Created by Marvin Polscheit on 15.01.25.
  6. //
  7. import SwiftUI
  8. struct CarbEntryEditorView: View {
  9. @Environment(\.dismiss) private var dismiss
  10. @Environment(\.colorScheme) var colorScheme
  11. @Environment(AppState.self) var appState
  12. var state: DataTable.StateModel
  13. let carbEntry: CarbEntryStored
  14. @State private var editedCarbs: Decimal
  15. @State private var editedFat: Decimal
  16. @State private var editedProtein: Decimal
  17. @State private var editedNote: String
  18. @State private var isFPU: Bool
  19. init(state: DataTable.StateModel, carbEntry: CarbEntryStored) {
  20. self.state = state
  21. self.carbEntry = carbEntry
  22. _editedCarbs = State(initialValue: Decimal(carbEntry.carbs))
  23. _editedFat = State(initialValue: 0) // gets updated in the task block
  24. _editedProtein = State(initialValue: 0) // gets updated in the task block
  25. _editedNote = State(initialValue: carbEntry.note ?? "")
  26. _isFPU = State(initialValue: carbEntry.isFPU)
  27. }
  28. private var carbLimitExceeded: Bool {
  29. editedCarbs > state.settingsManager.settings.maxCarbs
  30. }
  31. private var fatLimitExceeded: Bool {
  32. editedFat > state.settingsManager.settings.maxFat
  33. }
  34. private var proteinLimitExceeded: Bool {
  35. editedProtein > state.settingsManager.settings.maxProtein
  36. }
  37. private var limitExceeded: Bool {
  38. carbLimitExceeded || fatLimitExceeded || proteinLimitExceeded
  39. }
  40. private var isButtonDisabled: Bool {
  41. editedCarbs == 0 && editedFat == 0 && editedProtein == 0
  42. }
  43. private var buttonLabel: some View {
  44. if carbLimitExceeded {
  45. return Text("Max Carbs of \(state.settingsManager.settings.maxCarbs.description) g Exceeded")
  46. } else if fatLimitExceeded {
  47. return Text("Max Fat of \(state.settingsManager.settings.maxFat.description) g Exceeded")
  48. } else if proteinLimitExceeded {
  49. return Text("Max Protein of \(state.settingsManager.settings.maxProtein.description) g Exceeded")
  50. }
  51. return Text("Save and Update")
  52. }
  53. private var buttonBackgroundColor: Color {
  54. var treatmentButtonBackground = Color(.systemBlue)
  55. if limitExceeded {
  56. treatmentButtonBackground = Color(.systemRed)
  57. } else if isButtonDisabled {
  58. treatmentButtonBackground = Color(.systemGray)
  59. }
  60. return treatmentButtonBackground
  61. }
  62. var stickyButton: some View {
  63. ZStack {
  64. Rectangle()
  65. .frame(width: UIScreen.main.bounds.width, height: 65)
  66. .foregroundStyle(colorScheme == .dark ? Color.bgDarkerDarkBlue : Color.white)
  67. .background(.thinMaterial)
  68. .opacity(0.8)
  69. .clipShape(Rectangle())
  70. Button(
  71. action: {
  72. let treatmentObjectID = carbEntry.objectID
  73. state.updateEntry(
  74. treatmentObjectID,
  75. newCarbs: editedCarbs,
  76. newFat: editedFat,
  77. newProtein: editedProtein,
  78. newNote: editedNote
  79. )
  80. dismiss()
  81. }, label: {
  82. buttonLabel
  83. .font(.headline)
  84. .frame(maxWidth: .infinity, maxHeight: .infinity)
  85. .padding(10)
  86. }
  87. )
  88. .frame(width: UIScreen.main.bounds.width * 0.9, height: 40, alignment: .center)
  89. .disabled(isButtonDisabled)
  90. .background(buttonBackgroundColor)
  91. .tint(.white)
  92. .clipShape(RoundedRectangle(cornerRadius: 8))
  93. }
  94. }
  95. var body: some View {
  96. NavigationView {
  97. Form {
  98. Section {
  99. HStack {
  100. Text("Carbs")
  101. TextFieldWithToolBar(
  102. text: $editedCarbs,
  103. placeholder: "0",
  104. keyboardType: .numberPad,
  105. numberFormatter: Formatter.decimalFormatterWithOneFractionDigit
  106. )
  107. Text("g").foregroundStyle(.secondary)
  108. }
  109. if state.settingsManager.settings.useFPUconversion {
  110. HStack {
  111. Text("Fat")
  112. TextFieldWithToolBar(
  113. text: $editedFat,
  114. placeholder: "0",
  115. keyboardType: .numberPad,
  116. numberFormatter: Formatter.decimalFormatterWithOneFractionDigit
  117. )
  118. Text("g").foregroundStyle(.secondary)
  119. }
  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. }
  131. HStack {
  132. Image(systemName: "square.and.pencil")
  133. TextFieldWithToolBarString(text: $editedNote, placeholder: "Note...", maxLength: 25)
  134. }
  135. }.listRowBackground(Color.chart)
  136. }
  137. .safeAreaInset(
  138. edge: .bottom,
  139. spacing: 30
  140. ) {
  141. stickyButton
  142. }
  143. .scrollContentBackground(.hidden)
  144. .background(appState.trioBackgroundColor(for: colorScheme))
  145. .navigationTitle("Edit Meal")
  146. .navigationBarTitleDisplayMode(.inline)
  147. .toolbar {
  148. ToolbarItem(placement: .topBarLeading) {
  149. Button("Cancel") {
  150. dismiss()
  151. }
  152. }
  153. }
  154. }
  155. .task {
  156. // TODO: do we still need this, or is grabbing the entire "hold-it-all" entry enough?
  157. if carbEntry.isFPU {
  158. if let originalEntryID = await state.getZeroCarbNonFPUEntry(carbEntry.objectID) {
  159. let context = CoreDataStack.shared.persistentContainer.viewContext
  160. await context.perform {
  161. do {
  162. if let originalEntry = try context.existingObject(with: originalEntryID) as? CarbEntryStored {
  163. editedFat = Decimal(originalEntry.fat)
  164. editedProtein = Decimal(originalEntry.protein)
  165. editedNote = originalEntry.note ?? ""
  166. }
  167. } catch {
  168. debugPrint(
  169. "\(DebuggingIdentifiers.failed) Failed to fetch original entry: \(error.localizedDescription)"
  170. )
  171. }
  172. }
  173. } else {
  174. debugPrint("\(DebuggingIdentifiers.failed) No original entry ID found")
  175. }
  176. }
  177. }
  178. }
  179. }