| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- import CoreData
- import Foundation
- import SwiftUI
- struct MealPresetView: View {
- @StateObject var state: Bolus.StateModel
- @Environment(\.colorScheme) var colorScheme
- @Environment(\.dismiss) var dismiss
- @Environment(\.managedObjectContext) var moc
- @State private var showAlert = false
- @State private var dish: String = ""
- @State private var showAddNewPresetSheet = false
- @State private var presetCarbs: Decimal = 0
- @State private var presetFat: Decimal = 0
- @State private var presetProtein: Decimal = 0
- @State private var carbs: Decimal = 0
- @State private var fat: Decimal = 0
- @State private var protein: Decimal = 0
- @FetchRequest(
- entity: MealPresetStored.entity(),
- sortDescriptors: [NSSortDescriptor(key: "dish", ascending: true)]
- ) var carbPresets: FetchedResults<MealPresetStored>
- private var mealFormatter: NumberFormatter {
- let formatter = NumberFormatter()
- formatter.numberStyle = .decimal
- formatter.maximumFractionDigits = 1
- return formatter
- }
- private var color: LinearGradient {
- colorScheme == .dark ? LinearGradient(
- gradient: Gradient(colors: [
- Color.bgDarkBlue,
- Color.bgDarkerDarkBlue
- ]),
- startPoint: .top,
- endPoint: .bottom
- )
- :
- LinearGradient(
- gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
- startPoint: .top,
- endPoint: .bottom
- )
- }
- var body: some View {
- NavigationStack {
- Form {
- mealPresets
- dishInfos()
- addPresetToTreatmentsButton
- }
- .scrollContentBackground(.hidden).background(color)
- .navigationTitle("Meal Presets")
- .navigationBarTitleDisplayMode(.automatic)
- .toolbar(content: {
- ToolbarItem(placement: .topBarLeading) {
- Button {
- dismiss()
- resetValues()
- } label: {
- Text("Close")
- }
- }
- ToolbarItem(placement: .topBarTrailing) {
- Button(action: {
- showAddNewPresetSheet.toggle()
- resetValues()
- }, label: {
- HStack {
- Text("New Preset")
- Image(systemName: "plus")
- }
- })
- }
- })
- .sheet(isPresented: $showAddNewPresetSheet) {
- AddMealPresetView(
- dish: $dish,
- presetCarbs: $presetCarbs,
- presetFat: $presetFat,
- presetProtein: $presetProtein,
- onSave: savePreset,
- onCancel: {
- showAddNewPresetSheet.toggle()
- resetValues()
- }
- )
- }
- .onDisappear {
- resetValues()
- }
- }
- }
- private var mealPresets: some View {
- Section {
- HStack {
- if state.selection != nil {
- minusButton
- }
- Picker("Preset", selection: $state.selection) {
- Text("Saved Food").tag(nil as MealPresetStored?)
- ForEach(carbPresets, id: \.self) { (preset: MealPresetStored) in
- Text(preset.dish ?? "").tag(preset as MealPresetStored?)
- }
- }
- .labelsHidden()
- .frame(maxWidth: .infinity, alignment: .center)
- if state.selection != nil {
- plusButton
- }
- }
- HStack {
- Spacer()
- Button("Delete Preset") {
- showAlert.toggle()
- }
- .disabled(state.selection == nil)
- .tint(.orange)
- .buttonStyle(.borderless)
- .alert(
- "Delete preset '\(state.selection?.dish ?? "")'?",
- isPresented: $showAlert,
- actions: {
- Button("No", role: .cancel) {}
- Button("Yes", role: .destructive) {
- if let selection = state.selection {
- let previousSelection = state.selection
- let count = state.summation.filter { $0 == selection.dish }.count
- state.summation.removeAll { $0 == selection.dish }
- carbs -= (((selection.carbs ?? 0) as NSDecimalNumber) as Decimal) * Decimal(count)
- fat -= (((selection.fat ?? 0) as NSDecimalNumber) as Decimal) * Decimal(count)
- protein -= (((selection.protein ?? 0) as NSDecimalNumber) as Decimal) * Decimal(count)
- state.deletePreset()
- state.selection = previousSelection
- }
- }
- }
- )
- Spacer()
- }
- }.listRowBackground(Color.chart)
- }
- private var addPresetToTreatmentsButton: some View {
- Button {
- state.carbs += carbs
- state.fat += fat
- state.protein += protein
- dismiss()
- }
- label: {
- Text("Add to treatments")
- .font(.headline)
- .foregroundStyle(Color.white)
- .frame(maxWidth: .infinity, alignment: .center)
- }
- .disabled(noPresetChosen)
- .listRowBackground(noPresetChosen ? Color(.systemGray3) : Color(.systemBlue))
- .shadow(radius: 3)
- .clipShape(RoundedRectangle(cornerRadius: 8))
- }
- private var noPresetChosen: Bool {
- state.selection == nil || carbs == 0 || fat == 0 || protein == 0
- }
- @ViewBuilder private func dishInfos() -> some View {
- if !state.summation.isEmpty {
- let presetSummary = generatePresetSummary()
- Section(header: Text("Summary")) {
- presetSummary
- .lineLimit(nil) // In case the text is too long, allow it to wrap to the next line
- LazyVGrid(columns: [
- GridItem(.flexible(), alignment: .leading),
- GridItem(.flexible(), alignment: .trailing)
- ], spacing: 0) {
- Group {
- Text("Carbs: ")
- .font(.footnote)
- .foregroundStyle(.secondary)
- HStack(spacing: 2) {
- Text("\(carbs as NSNumber, formatter: mealFormatter)")
- .font(.footnote)
- Text(" g")
- .font(.footnote)
- .foregroundStyle(.secondary)
- }
- }
- Group {
- Text("Fat: ")
- .font(.footnote)
- .foregroundStyle(.secondary)
- HStack(spacing: 2) {
- Text("\(fat as NSNumber, formatter: mealFormatter)")
- .font(.footnote)
- Text(" g")
- .font(.footnote)
- .foregroundStyle(.secondary)
- }
- }
- Group {
- Text("Protein: ")
- .font(.footnote)
- .foregroundStyle(.secondary)
- HStack(spacing: 2) {
- Text("\(protein as NSNumber, formatter: mealFormatter)")
- .font(.footnote)
- Text(" g")
- .font(.footnote)
- .foregroundStyle(.secondary)
- }
- }
- }
- }.listRowBackground(Color.chart)
- }
- }
- private func generatePresetSummary() -> some View {
- var counts = [String: Int]()
- for preset in state.summation {
- counts[preset, default: 0] += 1
- }
- return VStack(alignment: .leading) {
- ForEach(counts.sorted(by: { $0.key < $1.key }), id: \.key) { key, value in
- if value > 0 {
- HStack {
- Text("\(value) x")
- .foregroundColor(.blue)
- Text(key)
- }
- }
- }
- }
- }
- private func resetValues() {
- dish = ""
- presetCarbs = 0
- presetFat = 0
- presetProtein = 0
- state.selection = nil
- state.summation.removeAll()
- }
- private var minusButton: some View {
- Button {
- if carbs != 0 {
- carbs -= (((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal)
- } else { carbs = 0 }
- if fat != 0,
- (fat - (((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal) as Decimal) >= 0
- {
- fat -= (((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal)
- } else { fat = 0 }
- if protein != 0,
- (protein - (((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal) as Decimal) >= 0
- {
- protein -= (((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal)
- } else { protein = 0 }
- state.removePresetFromNewMeal()
- if carbs == 0, fat == 0, protein == 0 { state.summation = [] }
- }
- label: { Image(systemName: "minus.circle.fill")
- .font(.system(size: 20))
- }
- .disabled(
- state
- .selection == nil ||
- (
- !state.summation
- .contains(state.selection?.dish ?? "") && (state.selection?.dish ?? "") != ""
- )
- )
- .buttonStyle(.borderless)
- .tint(.blue)
- }
- private var plusButton: some View {
- Button {
- carbs += ((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal
- fat += ((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal
- protein += ((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal
- state.addPresetToNewMeal()
- }
- label: { Image(systemName: "plus.circle.fill")
- .font(.system(size: 20))
- }
- .disabled(state.selection == nil)
- .buttonStyle(.borderless)
- .tint(.blue)
- }
- private func savePreset() {
- if dish != "" {
- let preset = MealPresetStored(context: moc)
- preset.dish = dish
- preset.fat = presetFat as NSDecimalNumber
- preset.protein = presetProtein as NSDecimalNumber
- preset.carbs = presetCarbs as NSDecimalNumber
- do {
- guard moc.hasChanges else { return }
- try moc.save()
- showAddNewPresetSheet.toggle()
- resetValues()
- } catch let error as NSError {
- debugPrint("\(DebuggingIdentifiers.failed) Failed to save Meal Preset with error: \(error.userInfo)")
- }
- }
- }
- }
|