Просмотр исходного кода

New Meal view and bolus views (#310)

* Chart for predictions Bolus view
* A bit cleaner Carbs View with better presets summary
* Back button and view meal entries in Bolus views
Jon B Mårtensson 2 лет назад
Родитель
Сommit
db99798ed9

+ 2 - 2
Dependencies/G7SensorKit/G7SensorKitUI/de.lproj/Localizable.strings

@@ -1,5 +1,5 @@
 /* No glucose value representation (3 dashes for mg/dL) */
-"– – –" = "– – –";
+"– – –" = "";
 
 /* Format string for glucose trend per minute. (1: glucose value and unit) */
 "%@/min" = "%@/min";
@@ -99,7 +99,7 @@
 "Sensor failed" = "Sensorfehler";
 
 /* title for g7 settings row showing sensor start time */
-"Sensor Start" = "Start sensor";
+"Sensor Start" = "Starte den Sensor";
 
 /* G7 Status highlight text for signal loss */
 "Signal\nLoss" = "Signal\nVerlust";

+ 3 - 3
Dependencies/G7SensorKit/G7SensorKitUI/it.lproj/Localizable.strings

@@ -2,7 +2,7 @@
 "– – –" = "– – –";
 
 /* Format string for glucose trend per minute. (1: glucose value and unit) */
-"%@/min" = "%@/minuto";
+"%@/min" = "%@/min";
 
 /* No comment provided by engineer. */
 "Are you sure you want to delete this CGM?" = "Sei sicuro di voler cancellare questo CGM?";
@@ -63,7 +63,7 @@
 "Name" = "Nome";
 
 /* No comment provided by engineer. */
-"Scan for new sensor" = "Scansiona per nuovo sensore";
+"Scan for new sensor" = "Scansiona nuovo sensore";
 
 /* title for g7 settings connection status when scanning */
 "Scanning" = "Lettura";
@@ -99,7 +99,7 @@
 "Sensor failed" = "Sensore fallito";
 
 /* title for g7 settings row showing sensor start time */
-"Sensor Start" = "Start sensor";
+"Sensor Start" = "Avvia sensore";
 
 /* G7 Status highlight text for signal loss */
 "Signal\nLoss" = "Perdita segnale \n";

+ 4 - 4
FreeAPS.xcodeproj/project.pbxproj

@@ -40,7 +40,7 @@
 		19D466A529AA2BD4004D5F33 /* FPUConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A429AA2BD4004D5F33 /* FPUConfigProvider.swift */; };
 		19D466A729AA2C22004D5F33 /* FPUConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A629AA2C22004D5F33 /* FPUConfigStateModel.swift */; };
 		19D466AA29AA3099004D5F33 /* FPUConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A929AA3099004D5F33 /* FPUConfigRootView.swift */; };
-		19D4E4EB29FC6A9F00351451 /* TIRforChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D4E4EA29FC6A9F00351451 /* TIRforChart.swift */; };
+		19D4E4EB29FC6A9F00351451 /* Charts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D4E4EA29FC6A9F00351451 /* Charts.swift */; };
 		19DA48E829CD339B00EEA1E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 19DA487F29CD2B8400EEA1E7 /* Assets.xcassets */; };
 		19DA48E929CD339C00EEA1E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 19DA487F29CD2B8400EEA1E7 /* Assets.xcassets */; };
 		19DA48EA29CD339C00EEA1E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 19DA487F29CD2B8400EEA1E7 /* Assets.xcassets */; };
@@ -568,7 +568,7 @@
 		19D466A429AA2BD4004D5F33 /* FPUConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUConfigProvider.swift; sourceTree = "<group>"; };
 		19D466A629AA2C22004D5F33 /* FPUConfigStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUConfigStateModel.swift; sourceTree = "<group>"; };
 		19D466A929AA3099004D5F33 /* FPUConfigRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUConfigRootView.swift; sourceTree = "<group>"; };
-		19D4E4EA29FC6A9F00351451 /* TIRforChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TIRforChart.swift; sourceTree = "<group>"; };
+		19D4E4EA29FC6A9F00351451 /* Charts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Charts.swift; sourceTree = "<group>"; };
 		19DA487F29CD2B8400EEA1E7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		19DC677E29CA675700FD9EC4 /* OverrideProfilesDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideProfilesDataFlow.swift; sourceTree = "<group>"; };
 		19DC678029CA676A00FD9EC4 /* OverrideProfilesProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideProfilesProvider.swift; sourceTree = "<group>"; };
@@ -1655,7 +1655,7 @@
 				FE41E4D529463EE20047FD55 /* NightscoutPreferences.swift */,
 				191F62672AD6B05A004D7911 /* NightscoutSettings.swift */,
 				1967DFBD29D052C200759F30 /* Icons.swift */,
-				19D4E4EA29FC6A9F00351451 /* TIRforChart.swift */,
+				19D4E4EA29FC6A9F00351451 /* Charts.swift */,
 				19A910352A24D6D700C8951B /* DateFilter.swift */,
 				193F6CDC2A512C8F001240FD /* Loops.swift */,
 				CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */,
@@ -2862,7 +2862,7 @@
 				0CEA2EA070AB041AF3E3745B /* BolusRootView.swift in Sources */,
 				1967DFC029D053AC00759F30 /* IconSelection.swift in Sources */,
 				BDFD165C2AE40688007F0DDA /* DefaultBolusCalcRootView.swift in Sources */,
-				19D4E4EB29FC6A9F00351451 /* TIRforChart.swift in Sources */,
+				19D4E4EB29FC6A9F00351451 /* Charts.swift in Sources */,
 				FEFFA7A22929FE49007B8193 /* UIDevice+Extensions.swift in Sources */,
 				F90692D3274B9A130037068D /* AppleHealthKitRootView.swift in Sources */,
 				3862CC1F273FDC9200BF832C /* CalibrationsChart.swift in Sources */,

Разница между файлами не показана из-за своего большого размера
+ 26 - 26
FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 45 - 45
FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings


+ 13 - 13
FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings

@@ -56,37 +56,37 @@
 "Error at" = "Foutmelding op";
 
 /* Bolus View Meal Summary Header */
-"Meal Summary" = "Meal Summary";
+"Meal Summary" = "Maaltijd overzicht";
 
 /* Bolus View Meal Edit Meal Button */
-"Edit Meal" = "Edit Meal";
+"Edit Meal" = "Wijzig maaltijd";
 
 /* Bolus View Meal Add Meal Button */
-"Add Meal" = "Add Meal";
+"Add Meal" = "Maaltijd toevoegen";
 
 /* Bolus View Bolus Summary Header */
-"Bolus Summary" = "Bolus Summary";
+"Bolus Summary" = "Bolus overzicht";
 
 /* For the  Bolus View pop-up */
-"Calculations" = "Calculations";
+"Calculations" = "Berekeningen";
 
 /* For the  Bolus View pop-up */
-"Fatty Meal" = "Fatty Meal";
+"Fatty Meal" = "Vette maaltijd";
 
 /* For the  Bolus View pop-up */
-"Full Bolus" = "Full Bolus";
+"Full Bolus" = "Volledige bolus";
 
 /* For the  Bolus View pop-up */
-"Fraction" = "Fraction";
+"Fraction" = "Fractie";
 
 /* For the  Bolus View pop-up */
-"Fatty Meal Factor" = "Fatty Meal Factor";
+"Fatty Meal Factor" = "Vette maaltijd factor";
 
 /* For the  Bolus View pop-up */
-"Result" = "Result";
+"Result" = "Resultaat";
 
 /* For the  Bolus View pop-up */
-"Your entered amount was limited by your max Bolus setting of %d%@" = "Your entered amount was limited by your max Bolus setting of %d%@";
+"Your entered amount was limited by your max Bolus setting of %d%@" = "Je ingevoerde hoeveelheid is beperkt door je maximale bolusinstelling van %d%@";
 
 /* Home title */
 "Home" = "Hoofdmenu";
@@ -603,7 +603,7 @@ Enact a temp Basal or a temp target */
 "Automatic" = "Automatisch";
 
 /* External insulin treatments */
-"External" = "External";
+"External" = "Extern";
 
 /* */
 "Other" = "Anders";
@@ -1208,7 +1208,7 @@ Enact a temp Basal or a temp target */
 "SMB" = "SMB";
 
 /* A manually entered dose of external insulin */
-"External Insulin" = "External Insulin";
+"External Insulin" = "Externe insuline";
 
 /* Status highlight when manual temp basal is running. */
 "Manual Basal" = "Handmatige tijdelijke basaal";

+ 1 - 1
FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings

@@ -71,7 +71,7 @@
 "Calculations" = "Beräkningar";
 
 /* For the  Bolus View pop-up */
-"Fatty Meal" = "Fettrik måltid";
+"Fatty Meal" = "Fet mat";
 
 /* For the  Bolus View pop-up */
 "Full Bolus" = "Total mängd";

+ 17 - 0
FreeAPS/Sources/Models/Charts.swift

@@ -0,0 +1,17 @@
+
+import Foundation
+
+struct ShapeModel: Identifiable {
+    var type: String
+    var percent: Decimal
+    var id = UUID()
+}
+
+struct ChartData: Identifiable {
+    var date: Date
+    var iob: Double
+    var zt: Double
+    var cob: Double
+    var uam: Double
+    var id = UUID()
+}

+ 0 - 8
FreeAPS/Sources/Models/TIRforChart.swift

@@ -1,8 +0,0 @@
-
-import Foundation
-
-struct ShapeModel: Identifiable {
-    var type: String
-    var percent: Decimal
-    var id = UUID()
-}

+ 17 - 15
FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift

@@ -140,16 +140,16 @@ extension AddCarbs {
             var addedString = ""
 
             if extracarbs > 0, filteredArray.isNotEmpty {
-                addedString += "Additional carbs: \(extracarbs) "
+                addedString += "Additional carbs: \(extracarbs) ,"
             } else if extracarbs < 0 { addedString += "Removed carbs: \(extracarbs) " }
 
             if extraFat > 0, filteredArray.isNotEmpty {
-                addedString += "Additional fat: \(extraFat) "
-            } else if extraFat < 0 { addedString += "Removed fat: \(extraFat) " }
+                addedString += "Additional fat: \(extraFat) ,"
+            } else if extraFat < 0 { addedString += "Removed fat: \(extraFat) ," }
 
             if extraProtein > 0, filteredArray.isNotEmpty {
-                addedString += "Additional protein: \(extraProtein) "
-            } else if extraProtein < 0 { addedString += "Removed protein: \(extraProtein) " }
+                addedString += "Additional protein: \(extraProtein) ,"
+            } else if extraProtein < 0 { addedString += "Removed protein: \(extraProtein) ," }
 
             if addedString != "" {
                 waitersNotepad.append(addedString)
@@ -170,7 +170,7 @@ extension AddCarbs {
 
         func loadEntries(_ editMode: Bool) {
             if editMode {
-                coredataContext.perform {
+                coredataContext.performAndWait {
                     var mealToEdit = [Meals]()
                     let requestMeal = Meals.fetchRequest() as NSFetchRequest<Meals>
                     let sortMeal = NSSortDescriptor(key: "createdAt", ascending: false)
@@ -188,15 +188,17 @@ extension AddCarbs {
         }
 
         func saveToCoreData(_ stored: [CarbsEntry]) {
-            let save = Meals(context: coredataContext)
-            save.createdAt = stored.first?.createdAt ?? .distantPast
-            save.id = stored.first?.collectionID ?? ""
-            save.carbs = Double(stored.first?.carbs ?? 0)
-            save.fat = Double(stored.first?.fat ?? 0)
-            save.protein = Double(stored.first?.protein ?? 0)
-            save.note = stored.first?.note ?? ""
-            if coredataContext.hasChanges {
-                try? coredataContext.save()
+            coredataContext.performAndWait {
+                let save = Meals(context: coredataContext)
+                if let entry = stored.first {
+                    save.createdAt = Date.now
+                    save.id = entry.collectionID ?? ""
+                    save.carbs = Double(entry.carbs)
+                    save.fat = Double(entry.fat ?? 0)
+                    save.protein = Double(entry.protein ?? 0)
+                    save.note = entry.note
+                    try? coredataContext.save()
+                }
             }
         }
     }

+ 163 - 98
FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift

@@ -10,6 +10,7 @@ extension AddCarbs {
         @State var dish: String = ""
         @State var isPromptPresented = false
         @State var saved = false
+        @State var pushed = false
         @State private var showAlert = false
         @FocusState private var isFocused: Bool
 
@@ -29,12 +30,12 @@ extension AddCarbs {
 
         var body: some View {
             Form {
-                if let carbsReq = state.carbsRequired {
+                if let carbsReq = state.carbsRequired, state.carbs < carbsReq {
                     Section {
                         HStack {
                             Text("Carbs required")
                             Spacer()
-                            Text(formatter.string(from: carbsReq as NSNumber)! + " g")
+                            Text((formatter.string(from: carbsReq as NSNumber) ?? "") + " g")
                         }
                     }
                 }
@@ -50,11 +51,55 @@ extension AddCarbs {
                             cleanInput: true
                         )
                         Text("grams").foregroundColor(.secondary)
-                    }.padding(.vertical)
+                    }
 
                     if state.useFPUconversion {
                         proteinAndFat()
                     }
+
+                    // Summary when combining presets
+                    if state.waitersNotepad() != "" {
+                        HStack {
+                            Text("Total")
+                            let test = state.waitersNotepad().components(separatedBy: ", ").removeDublicates()
+                            HStack(spacing: 0) {
+                                ForEach(test, id: \.self) {
+                                    Text($0).foregroundStyle(Color.randomGreen()).font(.footnote)
+                                    Text($0 == test[test.count - 1] ? "" : ", ")
+                                }
+                            }.frame(maxWidth: .infinity, alignment: .trailing)
+                        }
+                    }
+
+                    // Time
+                    HStack {
+                        let now = Date.now
+                        Text("Time")
+                        Spacer()
+                        if !pushed {
+                            Button {
+                                pushed = true
+                            } label: { Text("Now") }.buttonStyle(.borderless).foregroundColor(.secondary).padding(.trailing, 5)
+                        } else {
+                            Button { state.date = state.date.addingTimeInterval(-10.minutes.timeInterval) }
+                            label: { Image(systemName: "minus.circle") }.tint(.blue).buttonStyle(.borderless)
+                            DatePicker(
+                                "Time",
+                                selection: $state.date,
+                                in: ...now,
+                                displayedComponents: [.hourAndMinute]
+                            ).controlSize(.mini)
+                                .labelsHidden()
+                            Button {
+                                if state.date.addingTimeInterval(5.minutes.timeInterval) < now {
+                                    state.date = state.date.addingTimeInterval(10.minutes.timeInterval)
+                                }
+                            }
+                            label: { Image(systemName: "plus.circle") }.tint(.blue).buttonStyle(.borderless)
+                        }
+                    }
+
+                    // Optional meal note
                     HStack {
                         Text("Note").foregroundColor(.secondary)
                         TextField("", text: $state.note).multilineTextAlignment(.trailing)
@@ -62,14 +107,11 @@ extension AddCarbs {
                             Button { isFocused = false } label: { Image(systemName: "keyboard.chevron.compact.down") }
                                 .controlSize(.mini)
                         }
-                    }.focused($isFocused)
-                        .popover(isPresented: $isPromptPresented) {
-                            presetPopover
-                        }
-                }
-
-                Section {
-                    mealPresets
+                    }
+                    .focused($isFocused)
+                    .popover(isPresented: $isPromptPresented) {
+                        presetPopover
+                    }
                 }
 
                 Section {
@@ -77,10 +119,11 @@ extension AddCarbs {
                     label: { Text(state.skipBolus ? "Save" : "Continue") }
                         .disabled(state.carbs <= 0 && state.fat <= 0 && state.protein <= 0)
                         .frame(maxWidth: .infinity, alignment: .center)
-                } footer: { Text(state.waitersNotepad().description) }
+                }.listRowBackground(!empty ? Color(.systemBlue) : Color(.systemGray4))
+                    .tint(.white)
 
                 Section {
-                    DatePicker("Date", selection: $state.date)
+                    mealPresets
                 }
             }
             .onAppear {
@@ -93,7 +136,7 @@ extension AddCarbs {
             .navigationBarItems(leading: Button("Close", action: state.hideModal))
         }
 
-        var presetPopover: some View {
+        private var presetPopover: some View {
             Form {
                 Section {
                     TextField("Name Of Dish", text: $dish)
@@ -121,114 +164,125 @@ extension AddCarbs {
             }
         }
 
-        var notEmpty: Bool {
-            state.carbs > 0 || state.protein > 0 || state.fat > 0
+        private var empty: Bool {
+            state.carbs <= 0 && state.fat <= 0 && state.protein <= 0
+        }
+
+        private var minusButton: some View {
+            Button {
+                if state.carbs != 0,
+                   (state.carbs - (((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal) as Decimal) >= 0
+                {
+                    state.carbs -= (((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal)
+                } else { state.carbs = 0 }
+
+                if state.fat != 0,
+                   (state.fat - (((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal) as Decimal) >= 0
+                {
+                    state.fat -= (((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal)
+                } else { state.fat = 0 }
+
+                if state.protein != 0,
+                   (state.protein - (((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal) as Decimal) >= 0
+                {
+                    state.protein -= (((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal)
+                } else { state.protein = 0 }
+
+                state.removePresetFromNewMeal()
+                if state.carbs == 0, state.fat == 0, state.protein == 0 { state.summation = [] }
+            }
+            label: { Image(systemName: "minus.circle") }
+                .disabled(
+                    state
+                        .selection == nil ||
+                        (
+                            !state.summation
+                                .contains(state.selection?.dish ?? "") && (state.selection?.dish ?? "") != ""
+                        )
+                )
+                .buttonStyle(.borderless)
+                .tint(.blue)
+        }
+
+        private var plusButton: some View {
+            Button {
+                state.carbs += ((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal
+                state.fat += ((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal
+                state.protein += ((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal
+
+                state.addPresetToNewMeal()
+            }
+            label: { Image(systemName: "plus.circle") }
+                .disabled(state.selection == nil)
+                .buttonStyle(.borderless)
+                .tint(.blue)
         }
 
-        var mealPresets: some View {
+        private var mealPresets: some View {
             Section {
                 HStack {
-                    Button {
-                        isPromptPresented = true
+                    if state.selection != nil {
+                        minusButton
                     }
-                    label: { Text("Save as Preset") }
-                        .buttonStyle(BorderlessButtonStyle())
-                        .disabled(
-                            !notEmpty ||
-                                (
-                                    (((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal) == state
-                                        .carbs && (((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal) == state
-                                        .fat && (((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal) == state
-                                        .protein
-                                )
-                        )
-
-                    Picker("Select a Preset", selection: $state.selection) {
-                        Text("Presets").tag(nil as Presets?)
+                    Picker("Preset", selection: $state.selection) {
+                        Text("Saved Food").tag(nil as Presets?)
                         ForEach(carbPresets, id: \.self) { (preset: Presets) in
                             Text(preset.dish ?? "").tag(preset as Presets?)
                         }
                     }
                     .labelsHidden()
-                    .frame(maxWidth: .infinity, alignment: .trailing)
+                    .frame(maxWidth: .infinity, alignment: .center)
                     ._onBindingChange($state.selection) { _ in
                         state.carbs += ((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal
                         state.fat += ((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal
                         state.protein += ((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal
                         state.addToSummation()
                     }
+                    if state.selection != nil {
+                        plusButton
+                    }
                 }
 
-                if state.selection != nil {
-                    HStack {
-                        Button("Delete Preset") {
-                            showAlert.toggle()
-                        }
-                        .disabled(state.selection == nil)
-                        .tint(.orange)
-                        .buttonStyle(BorderlessButtonStyle())
-                        .alert(
-                            "Delete preset '\(state.selection?.dish ?? "")'?",
-                            isPresented: $showAlert,
-                            actions: {
-                                Button("No", role: .cancel) {}
-                                Button("Yes", role: .destructive) {
-                                    state.deletePreset()
+                HStack {
+                    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) {
+                                state.deletePreset()
 
-                                    state.carbs += ((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal
-                                    state.fat += ((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal
-                                    state.protein += ((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal
+                                state.carbs += ((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal
+                                state.fat += ((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal
+                                state.protein += ((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal
 
-                                    state.addPresetToNewMeal()
-                                }
+                                state.addPresetToNewMeal()
                             }
-                        )
-                        Button {
-                            if state.carbs != 0,
-                               (state.carbs - (((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal) as Decimal) >= 0
-                            {
-                                state.carbs -= (((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal)
-                            } else { state.carbs = 0 }
-
-                            if state.fat != 0,
-                               (state.fat - (((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal) as Decimal) >= 0
-                            {
-                                state.fat -= (((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal)
-                            } else { state.fat = 0 }
-
-                            if state.protein != 0,
-                               (state.protein - (((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal) as Decimal) >= 0
-                            {
-                                state.protein -= (((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal)
-                            } else { state.protein = 0 }
-
-                            state.removePresetFromNewMeal()
-                            if state.carbs == 0, state.fat == 0, state.protein == 0 { state.summation = [] }
                         }
-                        label: { Text("[ -1 ]") }
-                            .disabled(
-                                state
-                                    .selection == nil ||
-                                    (
-                                        !state.summation
-                                            .contains(state.selection?.dish ?? "") && (state.selection?.dish ?? "") != ""
-                                    )
-                            )
-                            .buttonStyle(BorderlessButtonStyle())
-                            .frame(maxWidth: .infinity, alignment: .trailing)
-                            .tint(.minus)
-                        Button {
-                            state.carbs += ((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal
-                            state.fat += ((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal
-                            state.protein += ((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal
+                    )
 
-                            state.addPresetToNewMeal()
-                        }
-                        label: { Text("[ +1 ]") }
-                            .disabled(state.selection == nil)
-                            .buttonStyle(BorderlessButtonStyle())
-                            .tint(.blue)
+                    Spacer()
+
+                    Button {
+                        isPromptPresented = true
                     }
+                    label: { Text("Save as Preset") }
+                        .buttonStyle(.borderless)
+                        .disabled(
+                            empty ||
+                                (
+                                    (((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal) == state
+                                        .carbs && (((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal) == state
+                                        .fat && (((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal) == state
+                                        .protein
+                                )
+                        )
                 }
             }
         }
@@ -262,3 +316,14 @@ extension AddCarbs {
         }
     }
 }
+
+public extension Color {
+    static func randomGreen(randomOpacity: Bool = false) -> Color {
+        Color(
+            red: .random(in: 0 ... 1),
+            green: .random(in: 0.4 ... 0.7),
+            blue: .random(in: 0.2 ... 1),
+            opacity: randomOpacity ? .random(in: 0.8 ... 1) : 1
+        )
+    }
+}

+ 0 - 4
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -1,5 +1,3 @@
-
-import CoreData
 import LoopKit
 import SwiftUI
 import Swinject
@@ -14,8 +12,6 @@ extension Bolus {
         @Injected() var settings: SettingsManager!
         @Injected() var nsManager: NightscoutManager!
 
-        let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
-
         @Published var suggestion: Suggestion?
         @Published var amount: Decimal = 0
         @Published var insulinRecommended: Decimal = 0

+ 102 - 15
FreeAPS/Sources/Modules/Bolus/View/AlternativeBolusCalcRootView.swift

@@ -1,3 +1,4 @@
+import Charts
 import CoreData
 import SwiftUI
 import Swinject
@@ -56,6 +57,13 @@ extension Bolus {
 
         var body: some View {
             Form {
+                Section {
+                    chart()
+                } header: {
+                    Text("Predictions")
+                }
+
+                Section {}
                 if fetch {
                     Section {
                         mealEntries
@@ -63,19 +71,6 @@ extension Bolus {
                 }
 
                 Section {
-                    Button {
-                        let id_ = meal.first?.id ?? ""
-                        if fetch {
-                            keepForNextWiew = true
-                            state.backToCarbsView(complexEntry: fetch, id_)
-                        } else {
-                            state.showModal(for: .addCarbs(editMode: false))
-                        }
-                    }
-                    label: { Text(fetch ? "Edit Meal" : "Add Meal") }.frame(maxWidth: .infinity, alignment: .center)
-                } header: { Text(!fetch ? "Meal Summary" : "") }
-
-                Section {
                     HStack {
                         Button(action: {
                             showInfo.toggle()
@@ -142,7 +137,7 @@ extension Bolus {
                             }
                         }
                     }
-                } header: { Text("Bolus Summary") }
+                } header: { Text("Bolus") }
 
                 if state.amount > 0 {
                     Section {
@@ -172,7 +167,12 @@ extension Bolus {
             .navigationTitle("Enact Bolus")
             .navigationBarTitleDisplayMode(.inline)
             .navigationBarItems(
-                leading: Button { state.hideModal() }
+                leading: Button {
+                    carbssView()
+                }
+                label: { Text(fetch ? "Back" : "Meal") },
+
+                trailing: Button { state.hideModal() }
                 label: { Text("Close") }
             )
             .onAppear {
@@ -194,6 +194,83 @@ extension Bolus {
             }
         }
 
+        func chart() -> some View {
+            // Data Source
+            let iob = state.provider.suggestion?.predictions?.iob ?? [Int]()
+            let cob = state.provider.suggestion?.predictions?.cob ?? [Int]()
+            let uam = state.provider.suggestion?.predictions?.uam ?? [Int]()
+            let zt = state.provider.suggestion?.predictions?.zt ?? [Int]()
+            let count = max(iob.count, cob.count, uam.count, zt.count)
+            var now = Date.now
+            var startIndex = 0
+            let conversion = state.units == .mmolL ? 0.0555 : 1
+            // Organize the data needed for prediction chart.
+            var data = [ChartData]()
+            repeat {
+                now = now.addingTimeInterval(5.minutes.timeInterval)
+                if startIndex < count {
+                    let addedData = ChartData(
+                        date: now,
+                        iob: startIndex < iob.count ? Double(iob[startIndex]) * conversion : 0,
+                        zt: startIndex < zt.count ? Double(zt[startIndex]) * conversion : 0,
+                        cob: startIndex < cob.count ? Double(cob[startIndex]) * conversion : 0,
+                        uam: startIndex < uam.count ? Double(uam[startIndex]) * conversion : 0,
+                        id: UUID()
+                    )
+                    data.append(addedData)
+                }
+                startIndex += 1
+            } while startIndex < count
+            // Chart
+            return Chart(data) { item in
+                // Remove 0 (empty) values
+                if item.iob != 0 {
+                    LineMark(
+                        x: .value("Time", item.date),
+                        y: .value("IOB", item.iob),
+                        series: .value("IOB", "A")
+                    )
+                    .foregroundStyle(Color(.insulin))
+                    .lineStyle(StrokeStyle(lineWidth: 2))
+                }
+                if item.uam != 0 {
+                    LineMark(
+                        x: .value("Time", item.date),
+                        y: .value("UAM", item.uam),
+                        series: .value("UAM", "B")
+                    )
+                    .foregroundStyle(Color(.UAM))
+                    .lineStyle(StrokeStyle(lineWidth: 2))
+                }
+                if item.cob != 0 {
+                    LineMark(
+                        x: .value("Time", item.date),
+                        y: .value("COB", item.cob),
+                        series: .value("COB", "C")
+                    )
+                    .foregroundStyle(Color(.loopYellow))
+                    .lineStyle(StrokeStyle(lineWidth: 2))
+                }
+                if item.zt != 0 {
+                    LineMark(
+                        x: .value("Time", item.date),
+                        y: .value("ZT", item.zt),
+                        series: .value("ZT", "D")
+                    )
+                    .foregroundStyle(Color(.ZT))
+                    .lineStyle(StrokeStyle(lineWidth: 2))
+                }
+            }
+            .frame(minHeight: 150)
+            .chartForegroundStyleScale([
+                "IOB": Color(.insulin),
+                "UAM": Color(.UAM),
+                "COB": Color(.loopYellow),
+                "ZT": Color(.ZT)
+            ])
+            .chartYAxisLabel("Glucose (" + state.units.rawValue + ")")
+        }
+
         // Pop-up
         var bolusInfoAlternativeCalculator: some View {
             VStack {
@@ -265,6 +342,16 @@ extension Bolus {
             ((meal.first?.fat ?? 0) > 0) || ((meal.first?.protein ?? 0) > 0)
         }
 
+        func carbssView() {
+            let id_ = meal.first?.id ?? ""
+            if fetch {
+                keepForNextWiew = true
+                state.backToCarbsView(complexEntry: fetch, id_)
+            } else {
+                state.showModal(for: .addCarbs(editMode: false))
+            }
+        }
+
         var mealEntries: some View {
             VStack {
                 if let carbs = meal.first?.carbs, carbs > 0 {

+ 65 - 56
FreeAPS/Sources/Modules/Bolus/View/DefaultBolusCalcRootView.swift

@@ -1,3 +1,4 @@
+import CoreData
 import SwiftUI
 import Swinject
 
@@ -37,56 +38,11 @@ extension Bolus {
             Form {
                 if fetch {
                     Section {
-                        VStack {
-                            if let carbs = meal.first?.carbs, carbs > 0 {
-                                HStack {
-                                    Text("Carbs")
-                                    Spacer()
-                                    Text(carbs.formatted())
-                                    Text("g")
-                                }.foregroundColor(.secondary)
-                            }
-                            if let fat = meal.first?.fat, fat > 0 {
-                                HStack {
-                                    Text("Fat")
-                                    Spacer()
-                                    Text(fat.formatted())
-                                    Text("g")
-                                }.foregroundColor(.secondary)
-                            }
-                            if let protein = meal.first?.protein, protein > 0 {
-                                HStack {
-                                    Text("Protein")
-                                    Spacer()
-                                    Text(protein.formatted())
-                                    Text("g")
-                                }.foregroundColor(.secondary)
-                            }
-                            if let note = meal.first?.note, note != "" {
-                                HStack {
-                                    Text("Note")
-                                    Spacer()
-                                    Text(note)
-                                }.foregroundColor(.secondary)
-                            }
-                        }
+                        mealEntries
                     } header: { Text("Meal Summary") }
                 }
 
                 Section {
-                    Button {
-                        let id_ = meal.first?.id ?? ""
-                        if fetch {
-                            keepForNextWiew = true
-                            state.backToCarbsView(complexEntry: fetch, id_)
-                        } else {
-                            state.showModal(for: .addCarbs(editMode: false))
-                        }
-                    }
-                    label: { Text(fetch ? "Edit Meal" : "Add Meal") }.frame(maxWidth: .infinity, alignment: .center)
-                } header: { Text(!fetch ? "Meal Summary" : "") }
-
-                Section {
                     if state.waitForSuggestion {
                         HStack {
                             Text("Wait please").foregroundColor(.secondary)
@@ -129,8 +85,7 @@ extension Bolus {
                             Text(!(state.amount > state.maxBolus) ? "U" : "😵").foregroundColor(.secondary)
                         }
                     }
-                }
-                header: { Text("Bolus Summary") }
+                } header: { Text("Bolus") }
 
                 if !state.waitForSuggestion {
                     if state.amount > 0 {
@@ -146,14 +101,14 @@ extension Bolus {
                                 )
                         }
                     }
-                    if state.amount <= 0 {
-                        Section {
-                            Button {
-                                keepForNextWiew = true
-                                state.showModal(for: nil)
-                            }
-                            label: { Text("Continue without bolus") }.frame(maxWidth: .infinity, alignment: .center)
+                }
+                if state.amount <= 0 {
+                    Section {
+                        Button {
+                            keepForNextWiew = true
+                            state.showModal(for: nil)
                         }
+                        label: { Text("Continue without bolus") }.frame(maxWidth: .infinity, alignment: .center)
                     }
                 }
             }
@@ -187,7 +142,15 @@ extension Bolus {
 
             .navigationTitle("Enact Bolus")
             .navigationBarTitleDisplayMode(.inline)
-            .navigationBarItems(leading: Button("Close", action: state.hideModal))
+            .navigationBarItems(
+                leading: Button {
+                    carbssView()
+                }
+                label: { Text(fetch ? "Back" : "Meal") },
+
+                trailing: Button { state.hideModal() }
+                label: { Text("Close") }
+            )
             .popup(isPresented: presentInfo, alignment: .center, direction: .bottom) {
                 bolusInfo
             }
@@ -201,6 +164,52 @@ extension Bolus {
             ((meal.first?.fat ?? 0) > 0) || ((meal.first?.protein ?? 0) > 0)
         }
 
+        func carbssView() {
+            let id_ = meal.first?.id ?? ""
+            if fetch {
+                keepForNextWiew = true
+                state.backToCarbsView(complexEntry: fetch, id_)
+            } else {
+                state.showModal(for: .addCarbs(editMode: false))
+            }
+        }
+
+        var mealEntries: some View {
+            VStack {
+                if let carbs = meal.first?.carbs, carbs > 0 {
+                    HStack {
+                        Text("Carbs")
+                        Spacer()
+                        Text(carbs.formatted())
+                        Text("g")
+                    }.foregroundColor(.secondary)
+                }
+                if let fat = meal.first?.fat, fat > 0 {
+                    HStack {
+                        Text("Fat")
+                        Spacer()
+                        Text(fat.formatted())
+                        Text("g")
+                    }.foregroundColor(.secondary)
+                }
+                if let protein = meal.first?.protein, protein > 0 {
+                    HStack {
+                        Text("Protein")
+                        Spacer()
+                        Text(protein.formatted())
+                        Text("g")
+                    }.foregroundColor(.secondary)
+                }
+                if let note = meal.first?.note, note != "" {
+                    HStack {
+                        Text("Note")
+                        Spacer()
+                        Text(note)
+                    }.foregroundColor(.secondary)
+                }
+            }
+        }
+
         var bolusInfo: some View {
             VStack {
                 // Variables

+ 1 - 1
FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift

@@ -62,7 +62,7 @@ extension StatConfig {
                 } header: { Text("Add Meal View settings ") }
             }
             .onAppear(perform: configureView)
-            .navigationBarTitle("UI/UX Settings")
+            .navigationBarTitle("UI/UX")
             .navigationBarTitleDisplayMode(.automatic)
         }
     }