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

Merge branch 'dev' into Crowdin

Jon B.M 2 лет назад
Родитель
Сommit
feff3e583f
52 измененных файлов с 626 добавлено и 313 удалено
  1. 6 0
      Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents
  2. 7 0
      FreeAPS/Sources/APS/DeviceDataManager.swift
  3. 41 14
      FreeAPS/Sources/APS/Storage/CarbsStorage.swift
  4. 3 3
      FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings
  5. 3 3
      FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings
  6. 3 3
      FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings
  7. 3 3
      FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings
  8. 24 3
      FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings
  9. 3 3
      FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings
  10. 3 3
      FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings
  11. 3 3
      FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings
  12. 3 3
      FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings
  13. 3 3
      FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings
  14. 3 3
      FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings
  15. 3 3
      FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings
  16. 3 3
      FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings
  17. 3 3
      FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings
  18. 3 3
      FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings
  19. 3 3
      FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings
  20. 3 3
      FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings
  21. 18 3
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  22. 3 3
      FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings
  23. 3 3
      FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings
  24. 3 3
      FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings
  25. 2 2
      FreeAPS/Sources/Models/CarbsEntry.swift
  26. 5 0
      FreeAPS/Sources/Models/FreeAPSSettings.swift
  27. 4 2
      FreeAPS/Sources/Models/NightscoutTreatment.swift
  28. 6 4
      FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift
  29. 25 14
      FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift
  30. 6 6
      FreeAPS/Sources/Modules/Bolus/View/AlternativeBolusCalcRootView.swift
  31. 6 6
      FreeAPS/Sources/Modules/Bolus/View/DefaultBolusCalcRootView.swift
  32. 4 1
      FreeAPS/Sources/Modules/Bolus/View/Predictions.swift
  33. 3 0
      FreeAPS/Sources/Modules/BolusCalculatorConfig/BolusCalculatorStateModel.swift
  34. 5 0
      FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift
  35. 1 6
      FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift
  36. 9 6
      FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift
  37. 107 92
      FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift
  38. 4 0
      FreeAPS/Sources/Modules/Home/HomeProvider.swift
  39. 22 4
      FreeAPS/Sources/Modules/Home/HomeStateModel.swift
  40. 7 3
      FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift
  41. 1 0
      FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift
  42. 11 0
      FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift
  43. 99 12
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  44. 0 8
      FreeAPS/Sources/Modules/StatConfig/StatConfigStateModel.swift
  45. 1 6
      FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift
  46. 32 33
      FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift
  47. 10 3
      FreeAPS/Sources/Services/Network/NightscoutAPI.swift
  48. 89 23
      FreeAPS/Sources/Services/Network/NightscoutManager.swift
  49. 1 2
      FreeAPS/Sources/Services/WatchManager/WatchManager.swift
  50. 1 1
      FreeAPS/Sources/Shortcuts/Carbs/CarbPresetIntentRequest.swift
  51. 0 1
      FreeAPSWatch WatchKit Extension/DataFlow.swift
  52. 12 1
      FreeAPSWatch WatchKit Extension/Views/MainView.swift

+ 6 - 0
Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents

@@ -68,7 +68,9 @@
     <entity name="Meals" representedClassName="Meals" syncable="YES" codeGenerationType="class">
         <attribute name="carbs" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
         <attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
         <attribute name="fat" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
+        <attribute name="fpuID" optional="YES" attributeType="String"/>
         <attribute name="id" optional="YES" attributeType="String"/>
         <attribute name="note" optional="YES" attributeType="String"/>
         <attribute name="protein" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
@@ -161,4 +163,8 @@
         <attribute name="id" optional="YES" attributeType="String" defaultValueString="empy"/>
         <attribute name="isPreset" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
     </entity>
+    <entity name="UXSettings" representedClassName="UXSettings" syncable="YES" codeGenerationType="class">
+        <attribute name="date" optional="YES" attributeType="Date" defaultDateTimeInterval="722095800" usesScalarValueType="NO"/>
+        <attribute name="hours" optional="YES" attributeType="Integer 16" defaultValueString="6" usesScalarValueType="YES"/>
+    </entity>
 </model>

+ 7 - 0
FreeAPS/Sources/APS/DeviceDataManager.swift

@@ -349,6 +349,9 @@ extension BaseDeviceDataManager: PumpManagerDelegate {
         broadcaster.notify(PumpBatteryObserver.self, on: processQueue) {
             $0.pumpBatteryDidChange(battery)
         }
+        broadcaster.notify(PumpTimeZoneObserver.self, on: processQueue) {
+            $0.pumpTimeZoneDidChange(status.timeZone)
+        }
 
         if let omnipod = pumpManager as? OmnipodPumpManager {
             let reservoirVal = omnipod.state.podState?.lastInsulinMeasurements?.reservoirLevel ?? 0xDEAD_BEEF
@@ -652,3 +655,7 @@ protocol PumpReservoirObserver {
 protocol PumpBatteryObserver {
     func pumpBatteryDidChange(_ battery: Battery)
 }
+
+protocol PumpTimeZoneObserver {
+    func pumpTimeZoneDidChange(_ timezone: TimeZone)
+}

+ 41 - 14
FreeAPS/Sources/APS/Storage/CarbsStorage.swift

@@ -12,7 +12,7 @@ protocol CarbsStorage {
     func syncDate() -> Date
     func recent() -> [CarbsEntry]
     func nightscoutTretmentsNotUploaded() -> [NigtscoutTreatment]
-    func deleteCarbs(at uniqueID: String)
+    func deleteCarbs(at uniqueID: String, fpuID: String, complex: Bool)
 }
 
 final class BaseCarbsStorage: CarbsStorage, Injectable {
@@ -71,7 +71,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                 // New date for each carb equivalent
                 var useDate = entries.last?.createdAt ?? Date()
                 // Group and Identify all FPUs together
-                let fpuID = (entries.last?.collectionID ?? "") + ".fpu"
+                let fpuID = entries.last?.fpuID ?? ""
                 // Create an array of all future carb equivalents.
                 var futureCarbArray = [CarbsEntry]()
                 while carbEquivalents > 0, numberOfEquivalents > 0 {
@@ -81,7 +81,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                     } else { useDate = useDate.addingTimeInterval(interval.minutes.timeInterval) }
 
                     let eachCarbEntry = CarbsEntry(
-                        collectionID: fpuID, createdAt: useDate, carbs: equivalent, fat: 0, protein: 0, note: nil,
+                        id: UUID().uuidString, createdAt: useDate, carbs: equivalent, fat: 0, protein: 0, note: nil,
                         enteredBy: CarbsEntry.manual, isFPU: true,
                         fpuID: fpuID
                     )
@@ -100,10 +100,22 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                 }
             } // ------------------------- END OF TPU ----------------------------------------
             // Store the actual (normal) carbs
-            if entries.last?.carbs ?? 0 > 0 {
+            if let entry = entries.last, entry.carbs > 0 {
                 // uniqEvents = []
+                let onlyCarbs = CarbsEntry(
+                    id: entry.id ?? "",
+                    createdAt: entry.createdAt,
+                    carbs: entry.carbs,
+                    fat: nil,
+                    protein: nil,
+                    note: entry.note ?? "",
+                    enteredBy: entry.enteredBy ?? "",
+                    isFPU: false,
+                    fpuID: ""
+                )
+
                 self.storage.transaction { storage in
-                    storage.append(entries, to: file, uniqBy: \.createdAt)
+                    storage.append(onlyCarbs, to: file, uniqBy: \.createdAt)
                     uniqEvents = storage.retrieve(file, as: [CarbsEntry].self)?
                         .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
                         .sorted { $0.createdAt > $1.createdAt } ?? []
@@ -143,17 +155,31 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
         storage.retrieve(OpenAPS.Monitor.carbHistory, as: [CarbsEntry].self)?.reversed() ?? []
     }
 
-    func deleteCarbs(at uniqueID: String) {
+    func deleteCarbs(at uniqueID: String, fpuID: String, complex: Bool) {
         processQueue.sync {
             var allValues = storage.retrieve(OpenAPS.Monitor.carbHistory, as: [CarbsEntry].self) ?? []
 
-            if allValues.firstIndex(where: { $0.collectionID == uniqueID }) == nil {
-                debug(.default, "Didn't find any carb entries to delete. ID to search for: " + uniqueID.description)
-            } else {
-                allValues.removeAll(where: { $0.collectionID == uniqueID })
-                storage.save(allValues, as: OpenAPS.Monitor.carbHistory)
-                broadcaster.notify(CarbsObserver.self, on: processQueue) {
-                    $0.carbsDidUpdate(allValues)
+            if fpuID != "" {
+                if allValues.firstIndex(where: { $0.fpuID == fpuID }) == nil {
+                    debug(.default, "Didn't find any carb equivalents to delete. ID to search for: " + fpuID.description)
+                } else {
+                    allValues.removeAll(where: { $0.fpuID == fpuID })
+                    storage.save(allValues, as: OpenAPS.Monitor.carbHistory)
+                    broadcaster.notify(CarbsObserver.self, on: processQueue) {
+                        $0.carbsDidUpdate(allValues)
+                    }
+                }
+            }
+
+            if fpuID == "" || complex {
+                if allValues.firstIndex(where: { $0.id == uniqueID }) == nil {
+                    debug(.default, "Didn't find any carb entries to delete. ID to search for: " + uniqueID.description)
+                } else {
+                    allValues.removeAll(where: { $0.id == uniqueID })
+                    storage.save(allValues, as: OpenAPS.Monitor.carbHistory)
+                    broadcaster.notify(CarbsObserver.self, on: processQueue) {
+                        $0.carbsDidUpdate(allValues)
+                    }
                 }
             }
         }
@@ -181,7 +207,8 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                 foodType: $0.note,
                 targetTop: nil,
                 targetBottom: nil,
-                collectionID: $0.collectionID
+                id: $0.id,
+                fpuID: $0.fpuID
             )
         }
         return Array(Set(treatments).subtracting(Set(uploaded)))

+ 3 - 3
FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Temp Targets";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Delete carbs?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Delete insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Treatments";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
 "Meal Presets" = "Meal Presets";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings

@@ -490,10 +490,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Temp Targets";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Delete carbs?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Delete insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Treatments";
@@ -1250,7 +1250,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
 "Meal Presets" = "Meal Presets";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Midlertidige Mål";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Slet kulhydrater?";
+"Delete Carbs?" = "Slet kulhydrater?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Slet insulin?";
+"Delete Insulin?" = "Slet insulin?";
 
 /* Treatments list */
 "Treatments" = "Behandlinger";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
 "Meal Presets" = "Meal Presets";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Temporäre Ziele";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Kohlenhydrate löschen?";
+"Delete Carbs?" = "Kohlenhydrate löschen?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Insulin löschen?";
+"Delete Insulin?" = "Insulin löschen?";
 
 /* Treatments list */
 "Treatments" = "Behandlungen";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistiken und Home-Ansicht";
 
 /* Alert text */
-"Delete carb equivalents?" = "Kohlenhydratäquivalente löschen?";
+"Delete Carb Equivalents?" = "Kohlenhydratäquivalente löschen?";
 
 /* */
 "Meal Presets" = "Mahlzeit Voreinstellungen";

+ 24 - 3
FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Temp Targets";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Delete carbs?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Delete insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Treatments";
@@ -1369,7 +1369,13 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
+
+/* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
 
 /* */
 "Meal Presets" = "Meal Presets";
@@ -1655,6 +1661,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Hours X-Axis (6 default)";
 
+/* */
+"2 hours" = "2 hours";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Average";
 

+ 3 - 3
FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Temp Targets";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "¿Eliminar carbohidratos?";
+"Delete Carbs?" = "¿Eliminar carbohidratos?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Delete insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Tratamientos";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
 "Meal Presets" = "Meal Presets";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Temp Targets";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Delete carbs?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Delete insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Treatments";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
 "Meal Presets" = "Meal Presets";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Cibles temporaires";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Supprimer les glucides ?";
+"Delete Carbs?" = "Supprimer les glucides ?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Delete insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Traitements";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
 "Meal Presets" = "Meal Presets";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Temp Targets";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Delete carbs?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Delete insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Treatments";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
 "Meal Presets" = "Meal Presets";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Obiettivi Temporanei";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Cancella carboidrati?";
+"Delete Carbs?" = "Cancella carboidrati?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Cancella l'insulina?";
+"Delete Insulin?" = "Cancella l'insulina?";
 
 /* Treatments list */
 "Treatments" = "Trattamenti";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistiche e Vista Iniziale";
 
 /* Alert text */
-"Delete carb equivalents?" = "Cancella i carb equivalenti?";
+"Delete Carb Equivalents?" = "Cancella i carb equivalenti?";
 
 /* */
 "Meal Presets" = "Pasto Predefinito";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Midlertidige mål";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Slette karbohydrater?";
+"Delete Carbs?" = "Slette karbohydrater?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Slette insulin?";
+"Delete Insulin?" = "Slette insulin?";
 
 /* Treatments list */
 "Treatments" = "Behandlinger";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistikk og startskjerm";
 
 /* Alert text */
-"Delete carb equivalents?" = "Slette karboekvivalenter?";
+"Delete Carb Equivalents?" = "Slette karboekvivalenter?";
 
 /* */
 "Meal Presets" = "Forvalg av måltid";

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

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Tijdelijk streefdoel";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Koolhydraten verwijderen?";
+"Delete Carbs?" = "Koolhydraten verwijderen?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Insuline verwijderen?";
+"Delete Insulin?" = "Insuline verwijderen?";
 
 /* Treatments list */
 "Treatments" = "Behandelingen";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistieken en Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Verwijder koolhydraten?";
+"Delete Carb Equivalents?" = "Verwijder koolhydraten?";
 
 /* */
 "Meal Presets" = "Maaltijd voorinstellingen";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings

@@ -578,10 +578,10 @@ Połączono z Nightscout!";
 "Temp Targets" = "Temp Targets";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Delete carbs?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Delete insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Treatments";
@@ -1370,7 +1370,7 @@ Połączono z Nightscout!";
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
 "Meal Presets" = "Meal Presets";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Alvos Temporários";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Deletar carboidratos?";
+"Delete Carbs?" = "Deletar carboidratos?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Delete insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Tratamentos";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
 "Meal Presets" = "Meal Presets";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Temp Targets";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Delete carbs?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Delete insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Treatments";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
 "Meal Presets" = "Meal Presets";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Временные цели";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Удалить углеводы?";
+"Delete Carbs?" = "Удалить углеводы?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Удалить инсулин?";
+"Delete Insulin?" = "Удалить инсулин?";
 
 /* Treatments list */
 "Treatments" = "События";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Статистика и экран";
 
 /* Alert text */
-"Delete carb equivalents?" = "Удалить эквиваленты углеводов?";
+"Delete Carb Equivalents?" = "Удалить эквиваленты углеводов?";
 
 /* */
 "Meal Presets" = "Шаблоны";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Temp Targets";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Delete carbs?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Delete insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Treatments";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
 "Meal Presets" = "Meal Presets";

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

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Målvärden";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Ta bort kolhydrater?";
+"Delete Carbs?" = "Ta bort kolhydrater?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Ta bort insulin?";
+"Delete Insulin?" = "Ta bort insulin?";
 
 /* Treatments list */
 "Treatments" = "Behandlingar";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistik och Diagram";
 
 /* Alert text */
-"Delete carb equivalents?" = "Radera dessa poster?";
+"Delete Carb Equivalents?" = "Radera dessa poster?";
 
 /* */
 "Meal Presets" = "Förval";
@@ -1652,6 +1652,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Antal timmar att visa för X-axel (6 standard)";
 
+/* */
+"2 hours" = "2 timmar";
+
+/* */
+"4 hours" = "4 timmar";
+
+/* */
+"6 hours" = "6 timmar";
+
+/* */
+"12 hours" = "12 timmar";
+
+/* */
+"24 hours" = "24 timmar";
+
 /* Average BG = */
 "Average" = "Medelvärde";
 

+ 3 - 3
FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Geçici Hedefler";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Karbonhidratları sil?";
+"Delete Carbs?" = "Karbonhidratları sil?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "İnsülin silinsin mi?";
+"Delete Insulin?" = "İnsülin silinsin mi?";
 
 /* Treatments list */
 "Treatments" = "Tedaviler";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
 "Meal Presets" = "Meal Presets";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Тимчасові цілі";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "Видалити вуглеводи?";
+"Delete Carbs?" = "Видалити вуглеводи?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Видалити інсулін?";
+"Delete Insulin?" = "Видалити інсулін?";
 
 /* Treatments list */
 "Treatments" = "Події";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Статистика та Домашня сторінка";
 
 /* Alert text */
-"Delete carb equivalents?" = "Видалити вуглеводні еквіваленти?";
+"Delete Carb Equivalents?" = "Видалити вуглеводні еквіваленти?";
 
 /* */
 "Meal Presets" = "Попередні Налаштування Їжі";

+ 3 - 3
FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "临时目标";
 
 /* Delete carbs from data table and Nightscout */
-"Delete carbs?" = "删除碳水?";
+"Delete Carbs?" = "删除碳水?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete insulin?" = "Delete insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "治疗";
@@ -1368,7 +1368,7 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistics and Home View";
 
 /* Alert text */
-"Delete carb equivalents?" = "Delete carb equivalents?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
 "Meal Presets" = "Meal Presets";

+ 2 - 2
FreeAPS/Sources/Models/CarbsEntry.swift

@@ -1,7 +1,7 @@
 import Foundation
 
 struct CarbsEntry: JSON, Equatable, Hashable {
-    let collectionID: String?
+    let id: String?
     let createdAt: Date
     let carbs: Decimal
     let fat: Decimal?
@@ -25,7 +25,7 @@ struct CarbsEntry: JSON, Equatable, Hashable {
 
 extension CarbsEntry {
     private enum CodingKeys: String, CodingKey {
-        case collectionID = "_id"
+        case id = "_id"
         case createdAt = "created_at"
         case carbs
         case fat

+ 5 - 0
FreeAPS/Sources/Models/FreeAPSSettings.swift

@@ -49,6 +49,7 @@ struct FreeAPSSettings: JSON, Equatable {
     var useCalc: Bool = false
     var fattyMeals: Bool = false
     var fattyMealFactor: Decimal = 0.7
+    var displayPredictions: Bool = true
 }
 
 extension FreeAPSSettings: Decodable {
@@ -254,6 +255,10 @@ extension FreeAPSSettings: Decodable {
             settings.onlyAutotuneBasals = onlyAutotuneBasals
         }
 
+        if let displayPredictions = try? container.decode(Bool.self, forKey: .displayPredictions) {
+            settings.displayPredictions = displayPredictions
+        }
+
         self = settings
     }
 }

+ 4 - 2
FreeAPS/Sources/Models/NightscoutTreatment.swift

@@ -21,7 +21,8 @@ struct NigtscoutTreatment: JSON, Hashable, Equatable {
     var glucoseType: String?
     var glucose: String?
     var units: String?
-    var collectionID: String?
+    var id: String?
+    var fpuID: String?
 
     static let local = "iAPS"
 
@@ -58,6 +59,7 @@ extension NigtscoutTreatment {
         case glucoseType
         case glucose
         case units
-        case collectionID
+        case id
+        case fpuID
     }
 }

+ 6 - 4
FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift

@@ -40,14 +40,14 @@ extension AddCarbs {
             id_ = UUID().uuidString
 
             let carbsToStore = [CarbsEntry(
-                collectionID: id_,
-                createdAt: date,
+                id: id_,
+                createdAt: Date.now,
                 carbs: carbs,
                 fat: fat,
                 protein: protein,
                 note: note,
                 enteredBy: CarbsEntry.manual,
-                isFPU: false, fpuID: nil
+                isFPU: false, fpuID: UUID().uuidString
             )]
             carbsStorage.storeCarbs(carbsToStore)
 
@@ -192,13 +192,15 @@ extension AddCarbs {
                 let save = Meals(context: coredataContext)
                 if let entry = stored.first {
                     save.createdAt = Date.now
-                    save.id = entry.collectionID ?? ""
+                    save.id = entry.id ?? ""
+                    save.fpuID = entry.fpuID ?? ""
                     save.carbs = Double(entry.carbs)
                     save.fat = Double(entry.fat ?? 0)
                     save.protein = Double(entry.protein ?? 0)
                     save.note = entry.note
                     try? coredataContext.save()
                 }
+                print("meals 1: ID: " + (save.id ?? "").description + " FPU ID: " + (save.fpuID ?? "").description)
             }
         }
     }

+ 25 - 14
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -57,6 +57,7 @@ extension Bolus {
         @Published var fattyMeals: Bool = false
         @Published var fattyMealFactor: Decimal = 0
         @Published var useFattyMealCorrectionFactor: Bool = false
+        @Published var displayPredictions: Bool = true
 
         @Published var meal: [CarbsEntry]?
         @Published var carbs: Decimal = 0
@@ -76,6 +77,7 @@ extension Bolus {
             useCalc = settings.settings.useCalc
             fattyMeals = settings.settings.fattyMeals
             fattyMealFactor = settings.settings.fattyMealFactor
+            displayPredictions = settings.settings.displayPredictions
 
             if waitForSuggestionInitial {
                 apsManager.determineBasal()
@@ -221,25 +223,34 @@ extension Bolus {
             }
         }
 
-        func backToCarbsView(complexEntry: Bool, _ id: String, override: Bool) {
-            delete(deleteTwice: complexEntry, id: id)
+        func backToCarbsView(complexEntry: Bool, _ meal: FetchedResults<Meals>, override: Bool) {
+            delete(deleteTwice: complexEntry, meal: meal)
             showModal(for: .addCarbs(editMode: complexEntry, override: override))
         }
 
-        func delete(deleteTwice: Bool, id: String) {
+        func delete(deleteTwice: Bool, meal: FetchedResults<Meals>) {
+            guard let meals = meal.first else {
+                return
+            }
+
+            let mealArray = DataTable.Treatment(
+                units: units,
+                type: .carbs,
+                date: meals.createdAt ?? Date(),
+                id: meals.id ?? "",
+                isFPU: deleteTwice ? true : false,
+                fpuID: deleteTwice ? (meals.fpuID ?? "") : ""
+            )
+
+            print(
+                "meals 2: ID: " + (mealArray.id ?? "").description + " FPU ID: " + (mealArray.fpuID ?? "")
+                    .description
+            )
+
             if deleteTwice {
-                // DispatchQueue.safeMainSync {
-                nsManager.deleteCarbs(
-                    at: id, isFPU: nil, fpuID: nil, syncID: id
-                )
-                nsManager.deleteCarbs(
-                    at: id + ".fpu", isFPU: nil, fpuID: nil, syncID: id
-                )
-                // }
+                nsManager.deleteCarbs(mealArray, complexMeal: true)
             } else {
-                nsManager.deleteCarbs(
-                    at: id, isFPU: nil, fpuID: nil, syncID: id
-                )
+                nsManager.deleteCarbs(mealArray, complexMeal: false)
             }
         }
     }

+ 6 - 6
FreeAPS/Sources/Modules/Bolus/View/AlternativeBolusCalcRootView.swift

@@ -188,9 +188,9 @@ extension Bolus {
             }
             .onDisappear {
                 if fetch, hasFatOrProtein, !keepForNextWiew, state.useCalc {
-                    state.delete(deleteTwice: true, id: meal.first?.id ?? "")
+                    state.delete(deleteTwice: true, meal: meal)
                 } else if fetch, !keepForNextWiew, state.useCalc {
-                    state.delete(deleteTwice: false, id: meal.first?.id ?? "")
+                    state.delete(deleteTwice: false, meal: meal)
                 }
             }
             .popup(isPresented: showInfo) {
@@ -201,7 +201,8 @@ extension Bolus {
         var predictionChart: some View {
             ZStack {
                 PredictionView(
-                    predictions: $state.predictions, units: $state.units, eventualBG: $state.evBG, target: $state.target
+                    predictions: $state.predictions, units: $state.units, eventualBG: $state.evBG, target: $state.target,
+                    displayPredictions: $state.displayPredictions
                 )
             }
         }
@@ -282,12 +283,11 @@ extension Bolus {
         }
 
         func carbsView() {
-            let id_ = meal.first?.id ?? ""
             if fetch {
                 keepForNextWiew = true
-                state.backToCarbsView(complexEntry: fetch, id_, override: false)
+                state.backToCarbsView(complexEntry: true, meal, override: false)
             } else {
-                state.backToCarbsView(complexEntry: false, id_, override: true)
+                state.backToCarbsView(complexEntry: false, meal, override: true)
             }
         }
 

+ 6 - 6
FreeAPS/Sources/Modules/Bolus/View/DefaultBolusCalcRootView.swift

@@ -143,9 +143,9 @@ extension Bolus {
 
             .onDisappear {
                 if fetch, hasFatOrProtein, !keepForNextWiew, !state.useCalc {
-                    state.delete(deleteTwice: true, id: meal.first?.id ?? "")
+                    state.delete(deleteTwice: true, meal: meal)
                 } else if fetch, !keepForNextWiew, !state.useCalc {
-                    state.delete(deleteTwice: false, id: meal.first?.id ?? "")
+                    state.delete(deleteTwice: false, meal: meal)
                 }
             }
 
@@ -176,7 +176,8 @@ extension Bolus {
         var predictionChart: some View {
             ZStack {
                 PredictionView(
-                    predictions: $state.predictions, units: $state.units, eventualBG: $state.evBG, target: $state.target
+                    predictions: $state.predictions, units: $state.units, eventualBG: $state.evBG, target: $state.target,
+                    displayPredictions: $state.displayPredictions
                 )
             }
         }
@@ -190,12 +191,11 @@ extension Bolus {
         }
 
         func carbsView() {
-            let id_ = meal.first?.id ?? ""
             if fetch {
                 keepForNextWiew = true
-                state.backToCarbsView(complexEntry: fetch, id_, override: false)
+                state.backToCarbsView(complexEntry: fetch, meal, override: false)
             } else {
-                state.backToCarbsView(complexEntry: false, id_, override: true)
+                state.backToCarbsView(complexEntry: false, meal, override: true)
             }
         }
 

+ 4 - 1
FreeAPS/Sources/Modules/Bolus/View/Predictions.swift

@@ -8,6 +8,7 @@ struct PredictionView: View {
     @Binding var units: GlucoseUnits
     @Binding var eventualBG: Int
     @Binding var target: Decimal
+    @Binding var displayPredictions: Bool
 
     private enum Config {
         static let height: CGFloat = 160
@@ -16,7 +17,9 @@ struct PredictionView: View {
 
     var body: some View {
         VStack {
-            chart()
+            if displayPredictions {
+                chart()
+            }
             HStack {
                 let conversion = units == .mmolL ? 0.0555 : 1
                 Text("Eventual Glucose")

+ 3 - 0
FreeAPS/Sources/Modules/BolusCalculatorConfig/BolusCalculatorStateModel.swift

@@ -7,6 +7,8 @@ extension BolusCalculatorConfig {
         @Published var fattyMeals: Bool = false
         @Published var fattyMealFactor: Decimal = 0
         @Published var insulinReqPercentage: Decimal = 70
+        @Published var displayPredictions: Bool = true
+
         override func subscribe() {
             subscribeSetting(\.overrideFactor, on: $overrideFactor, initial: {
                 let value = max(min($0, 1.2), 0.1)
@@ -16,6 +18,7 @@ extension BolusCalculatorConfig {
             })
             subscribeSetting(\.useCalc, on: $useCalc) { useCalc = $0 }
             subscribeSetting(\.fattyMeals, on: $fattyMeals) { fattyMeals = $0 }
+            subscribeSetting(\.displayPredictions, on: $displayPredictions) { displayPredictions = $0 }
             subscribeSetting(\.fattyMealFactor, on: $fattyMealFactor, initial: {
                 let value = max(min($0, 1.2), 0.1)
                 fattyMealFactor = value

+ 5 - 0
FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift

@@ -43,6 +43,11 @@ extension BolusCalculatorConfig {
                     }
                 } header: { Text("Calculator settings") }
 
+                Section {
+                    Toggle("Display Predictions", isOn: $state.displayPredictions)
+
+                } header: { Text("Smaller iPhone Screens") }
+
                 if state.useCalc {
                     Section {
                         HStack {

+ 1 - 6
FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift

@@ -32,12 +32,7 @@ extension DataTable {
         }
 
         func deleteCarbs(_ treatement: Treatment) {
-            nightscoutManager.deleteCarbs(
-                at: treatement.id,
-                isFPU: treatement.isFPU,
-                fpuID: treatement.fpuID,
-                syncID: treatement.id
-            )
+            nightscoutManager.deleteCarbs(treatement, complexMeal: false)
         }
 
         func deleteInsulin(_ treatement: Treatment) {

+ 9 - 6
FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -40,13 +40,14 @@ extension DataTable {
                 let carbs = self.provider.carbs()
                     .filter { !($0.isFPU ?? false) }
                     .map {
-                        if let id = $0.collectionID {
+                        if let id = $0.id {
                             return Treatment(
                                 units: units,
                                 type: .carbs,
                                 date: $0.createdAt,
                                 amount: $0.carbs,
                                 id: id,
+                                fpuID: $0.fpuID,
                                 note: $0.note
                             )
                         } else {
@@ -62,7 +63,7 @@ extension DataTable {
                             type: .fpus,
                             date: $0.createdAt,
                             amount: $0.carbs,
-                            id: $0.collectionID,
+                            id: $0.id,
                             isFPU: $0.isFPU,
                             fpuID: $0.fpuID,
                             note: $0.note
@@ -151,9 +152,10 @@ extension DataTable {
                 .store(in: &lifetime)
         }
 
-        func deleteGlucose(at index: Int) {
-            let id = glucose[index].id
+        func deleteGlucose(_ glucose: Glucose) {
+            let id = glucose.id
             provider.deleteGlucose(id: id)
+
             let fetchRequest: NSFetchRequest<NSFetchRequestResult>
             fetchRequest = NSFetchRequest(entityName: "Readings")
             fetchRequest.predicate = NSPredicate(format: "id == %@", id)
@@ -170,9 +172,10 @@ extension DataTable {
                     )
                 }
             } catch { /* To do: handle any thrown errors. */ }
+
             // Deletes Manual Glucose
-            if (glucose[index].glucose.type ?? "") == GlucoseType.manual.rawValue {
-                provider.deleteManualGlucose(date: glucose[index].glucose.dateString)
+            if (glucose.glucose.type ?? "") == GlucoseType.manual.rawValue {
+                provider.deleteManualGlucose(date: glucose.glucose.dateString)
             }
         }
 

+ 107 - 92
FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -6,11 +6,12 @@ extension DataTable {
     struct RootView: BaseView {
         let resolver: Resolver
         @StateObject var state = StateModel()
+        @State private var isRemoveHistoryItemAlertPresented: Bool = false
+        @State private var alertTitle: String = ""
+        @State private var alertMessage: String = ""
+        @State private var alertTreatmentToDelete: Treatment?
+        @State private var alertGlucoseToDelete: Glucose?
 
-        @State private var isRemoveCarbsAlertPresented = false
-        @State private var removeCarbsAlert: Alert?
-        @State private var isRemoveInsulinAlertPresented = false
-        @State private var removeInsulinAlert: Alert?
         @State private var showExternalInsulin: Bool = false
         @State private var showFutureEntries: Bool = false // default to hide future entries
         @State private var showManualGlucose: Bool = false
@@ -133,7 +134,6 @@ extension DataTable {
                     ForEach(state.glucose) { item in
                         glucoseView(item, isManual: item.glucose)
                     }
-                    .onDelete(perform: deleteGlucose)
                 } else {
                     HStack {
                         Text("No data.")
@@ -185,87 +185,70 @@ extension DataTable {
 
         @ViewBuilder private func treatmentView(_ item: Treatment) -> some View {
             HStack {
-                Image(systemName: "circle.fill").foregroundColor(item.color)
+                if item.type == .bolus || item.type == .carbs {
+                    Image(systemName: "circle.fill").foregroundColor(item.color).padding(.vertical)
+                } else {
+                    Image(systemName: "circle.fill").foregroundColor(item.color)
+                }
                 Text((item.isSMB ?? false) ? "SMB" : item.type.name)
                 Text(item.amountText).foregroundColor(.secondary)
 
                 if let duration = item.durationText {
                     Text(duration).foregroundColor(.secondary)
                 }
+                Spacer()
+                Text(dateFormatter.string(from: item.date))
+                    .moveDisabled(true)
+            }
+            .swipeActions {
+                Button(
+                    "Delete",
+                    systemImage: "trash.fill",
+                    role: .none,
+                    action: {
+                        alertTreatmentToDelete = item
 
-                if item.type == .carbs {
-                    if item.note != "" {
-                        Spacer()
-                        Text(item.note ?? "").foregroundColor(.brown)
-                    }
-                    Spacer()
-                    Image(systemName: "xmark.circle").foregroundColor(.secondary)
-                        .contentShape(Rectangle())
-                        .padding(.vertical)
-                        .onTapGesture {
-                            removeCarbsAlert = Alert(
-                                title: Text("Delete carbs?"),
-                                message: Text(item.amountText),
-                                primaryButton: .destructive(
-                                    Text("Delete"),
-                                    action: {
-                                        state.deleteCarbs(item) }
-                                ),
-                                secondaryButton: .cancel()
-                            )
-                            isRemoveCarbsAlertPresented = true
-                        }
-                        .alert(isPresented: $isRemoveCarbsAlertPresented) {
-                            removeCarbsAlert!
-                        }
-                }
+                        if item.type == .carbs {
+                            alertTitle = "Delete Carbs?"
+                            alertMessage = dateFormatter.string(from: item.date) + ", " + item.amountText
+                        } else if item.type == .fpus {
+                            alertTitle = "Delete Carb Equivalents?"
+                            alertMessage = "All FPUs of the meal will be deleted."
+                        } else {
+                            // item is insulin treatment; item.type == .bolus
+                            alertTitle = "Delete Insulin?"
+                            alertMessage = dateFormatter.string(from: item.date) + ", " + item.amountText
 
-                if item.type == .fpus {
-                    Spacer()
-                    Image(systemName: "xmark.circle").foregroundColor(.secondary)
-                        .contentShape(Rectangle())
-                        .padding(.vertical)
-                        .onTapGesture {
-                            removeCarbsAlert = Alert(
-                                title: Text("Delete carb equivalents?"),
-                                message: Text(""), // Temporary fix. New to fix real amount of carb equivalents later
-                                primaryButton: .destructive(
-                                    Text("Delete"),
-                                    action: { state.deleteCarbs(item) }
-                                ),
-                                secondaryButton: .cancel()
-                            )
-                            isRemoveCarbsAlertPresented = true
-                        }
-                        .alert(isPresented: $isRemoveCarbsAlertPresented) {
-                            removeCarbsAlert!
+                            if item.isSMB ?? false {
+                                // Add text snippet, so that alert message is more descriptive for SMBs
+                                alertMessage += "SMB"
+                            }
                         }
-                }
 
-                if item.type == .bolus {
-                    Spacer()
-                    Image(systemName: "xmark.circle").foregroundColor(.secondary)
-                        .contentShape(Rectangle())
-                        .padding(.vertical)
-                        .onTapGesture {
-                            removeInsulinAlert = Alert(
-                                title: Text("Delete insulin?"),
-                                message: Text(item.amountText),
-                                primaryButton: .destructive(
-                                    Text("Delete"),
-                                    action: { state.deleteInsulin(item) }
-                                ),
-                                secondaryButton: .cancel()
-                            )
-                            isRemoveInsulinAlertPresented = true
-                        }
-                        .alert(isPresented: $isRemoveInsulinAlertPresented) {
-                            removeInsulinAlert!
-                        }
+                        isRemoveHistoryItemAlertPresented = true
+                    }
+                ).tint(.red)
+            }
+            .disabled(item.type == .tempBasal || item.type == .tempTarget || item.type == .resume || item.type == .suspend)
+            .alert(
+                Text(NSLocalizedString(alertTitle, comment: "")),
+                isPresented: $isRemoveHistoryItemAlertPresented
+            ) {
+                Button("Cancel", role: .cancel) {}
+                Button("Delete", role: .destructive) {
+                    guard let treatmentToDelete = alertTreatmentToDelete else {
+                        debug(.default, "Cannot gracefully unwrap alertTreatmentToDelete!")
+                        return
+                    }
+
+                    if treatmentToDelete.type == .carbs || treatmentToDelete.type == .fpus {
+                        state.deleteCarbs(treatmentToDelete)
+                    } else {
+                        state.deleteInsulin(treatmentToDelete)
+                    }
                 }
-                Spacer()
-                Text(dateFormatter.string(from: item.date))
-                    .moveDisabled(true)
+            } message: {
+                Text("\n" + NSLocalizedString(alertMessage, comment: ""))
             }
         }
 
@@ -332,27 +315,59 @@ extension DataTable {
         }
 
         @ViewBuilder private func glucoseView(_ item: Glucose, isManual: BloodGlucose) -> some View {
-            VStack(alignment: .leading, spacing: 4) {
-                HStack {
-                    Text(item.glucose.glucose.map {
-                        glucoseFormatter.string(from: Double(
-                            state.units == .mmolL ? $0.asMmolL : Decimal($0)
-                        ) as NSNumber)!
-                    } ?? "--")
-                    if isManual.type == GlucoseType.manual.rawValue {
-                        Image(systemName: "drop.fill").symbolRenderingMode(.monochrome).foregroundStyle(.red)
-                    } else {
-                        Text(item.glucose.direction?.symbol ?? "--")
+            HStack {
+                Text(item.glucose.glucose.map {
+                    glucoseFormatter.string(from: Double(
+                        state.units == .mmolL ? $0.asMmolL : Decimal($0)
+                    ) as NSNumber)!
+                } ?? "--")
+                if isManual.type == GlucoseType.manual.rawValue {
+                    Image(systemName: "drop.fill").symbolRenderingMode(.monochrome).foregroundStyle(.red)
+                } else {
+                    Text(item.glucose.direction?.symbol ?? "--")
+                }
+                Spacer()
+
+                Text(dateFormatter.string(from: item.glucose.dateString))
+            }
+            .swipeActions {
+                Button(
+                    "Delete",
+                    systemImage: "trash.fill",
+                    role: .none,
+                    action: {
+                        alertGlucoseToDelete = item
+
+                        let valueText = glucoseFormatter.string(from: Double(
+                            state.units == .mmolL ? Double(item.glucose.value.asMmolL) : item.glucose.value
+                        ) as NSNumber)! + " " + state.units.rawValue
+
+                        alertTitle = "Delete Glucose?"
+                        alertMessage = dateFormatter.string(from: item.glucose.dateString) + ", " + valueText
+
+                        isRemoveHistoryItemAlertPresented = true
+                    }
+                ).tint(.red)
+            }
+            .alert(
+                Text(NSLocalizedString(alertTitle, comment: "")),
+                isPresented: $isRemoveHistoryItemAlertPresented
+            ) {
+                Button("Cancel", role: .cancel) {}
+                Button("Delete", role: .destructive) {
+                    // gracefully unwrap value here.
+                    // value cannot ever really be nil because it is an existing(!) table entry
+                    // but just to be sure.
+                    guard let glucoseToDelete = alertGlucoseToDelete else {
+                        print("Cannot gracefully unwrap alertTreatmentToDelete!")
+                        return
                     }
-                    Spacer()
 
-                    Text(dateFormatter.string(from: item.glucose.dateString))
+                    state.deleteGlucose(glucoseToDelete)
                 }
+            } message: {
+                Text("\n" + NSLocalizedString(alertMessage, comment: ""))
             }
         }
-
-        private func deleteGlucose(at offsets: IndexSet) {
-            state.deleteGlucose(at: offsets[offsets.startIndex])
-        }
     }
 }

+ 4 - 0
FreeAPS/Sources/Modules/Home/HomeProvider.swift

@@ -19,6 +19,10 @@ extension Home {
             storage.retrieve(OpenAPS.Enact.enacted, as: Suggestion.self)
         }
 
+        func pumpTimeZone() -> TimeZone? {
+            apsManager.pumpManager?.status.timeZone
+        }
+
         func heartbeatNow() {
             apsManager.heartbeat(date: Date())
         }

+ 22 - 4
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -56,10 +56,11 @@ extension Home {
         @Published var lowGlucose: Decimal = 4 / 0.0555
         @Published var highGlucose: Decimal = 10 / 0.0555
         @Published var overrideUnit: Bool = false
-        @Published var screenHours: Int = 6
         @Published var displayXgridLines: Bool = false
         @Published var displayYgridLines: Bool = false
         @Published var thresholdLines: Bool = false
+        @Published var timeZone: TimeZone?
+        @Published var hours: Int16 = 6
 
         let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
 
@@ -75,6 +76,7 @@ extension Home {
             setupBattery()
             setupReservoir()
             setupAnnouncements()
+            setupCurrentPumpTimezone()
 
             suggestion = provider.suggestion
             uploadStats = settingsManager.settings.uploadStats
@@ -93,7 +95,6 @@ extension Home {
             lowGlucose = settingsManager.settings.low
             highGlucose = settingsManager.settings.high
             overrideUnit = settingsManager.settings.overrideHbA1cUnit
-            screenHours = settingsManager.settings.hours
             displayXgridLines = settingsManager.settings.xGridLines
             displayYgridLines = settingsManager.settings.yGridLines
             thresholdLines = settingsManager.settings.rulerMarks
@@ -216,6 +217,15 @@ extension Home {
             }
         }
 
+        func saveSettings() {
+            coredataContext.perform {
+                let settings = UXSettings(context: self.coredataContext)
+                settings.hours = self.hours
+                settings.date = Date.now
+                try? self.coredataContext.save()
+            }
+        }
+
         private func setupGlucose() {
             DispatchQueue.main.async { [weak self] in
                 guard let self = self else { return }
@@ -360,6 +370,10 @@ extension Home {
             tempTarget = provider.tempTarget()
         }
 
+        private func setupCurrentPumpTimezone() {
+            timeZone = provider.pumpTimeZone()
+        }
+
         func openCGM() {
             guard var url = nightscoutManager.cgmURL else { return }
 
@@ -397,7 +411,8 @@ extension Home.StateModel:
     CarbsObserver,
     EnactedSuggestionObserver,
     PumpBatteryObserver,
-    PumpReservoirObserver
+    PumpReservoirObserver,
+    PumpTimeZoneObserver
 {
     func glucoseDidUpdate(_: [BloodGlucose]) {
         setupGlucose()
@@ -420,7 +435,6 @@ extension Home.StateModel:
         lowGlucose = settingsManager.settings.low
         highGlucose = settingsManager.settings.high
         overrideUnit = settingsManager.settings.overrideHbA1cUnit
-        screenHours = settingsManager.settings.hours
         displayXgridLines = settingsManager.settings.xGridLines
         displayYgridLines = settingsManager.settings.yGridLines
         thresholdLines = settingsManager.settings.rulerMarks
@@ -463,6 +477,10 @@ extension Home.StateModel:
     func pumpReservoirDidChange(_: Decimal) {
         setupReservoir()
     }
+
+    func pumpTimeZoneDidChange(_: TimeZone) {
+        setupCurrentPumpTimezone()
+    }
 }
 
 extension Home.StateModel: CompletionDelegate {

+ 7 - 3
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -72,7 +72,7 @@ struct MainChartView: View {
     @Binding var smooth: Bool
     @Binding var highGlucose: Decimal
     @Binding var lowGlucose: Decimal
-    @Binding var screenHours: Int
+    @Binding var screenHours: Int16
     @Binding var displayXgridLines: Bool
     @Binding var displayYgridLines: Bool
     @Binding var thresholdLines: Bool
@@ -195,6 +195,10 @@ struct MainChartView: View {
                         .onChange(of: tempBasals) { _ in
                             scroll.scrollTo(Config.endID, anchor: .trailing)
                         }
+                        .onChange(of: screenHours) { _ in
+                            scroll.scrollTo(Config.endID, anchor: .trailing)
+                        }
+
                         .onAppear {
                             // add trigger to the end of main queue
                             DispatchQueue.main.async {
@@ -819,8 +823,8 @@ extension MainChartView {
                 path.addLine(to: CGPoint(x: 0, y: Config.basalHeight))
             }
             let adjustForOptionalExtraHours = screenHours > 12 ? screenHours - 12 : 0
-            let endDateTime = dayAgoTime + min(max(screenHours - adjustForOptionalExtraHours, 12), 24).hours
-                .timeInterval + min(max(screenHours - adjustForOptionalExtraHours, 12), 24).hours
+            let endDateTime = dayAgoTime + min(max(Int(screenHours - adjustForOptionalExtraHours), 12), 24).hours
+                .timeInterval + min(max(Int(screenHours - adjustForOptionalExtraHours), 12), 24).hours
                 .timeInterval
             let autotunedBasalPoints = findRegularBasalPoints(
                 timeBegin: dayAgoTime,

+ 1 - 0
FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift

@@ -22,6 +22,7 @@ struct LoopView: View {
     }
 
     private let rect = CGRect(x: 0, y: 0, width: 28, height: 28)
+
     var body: some View {
         VStack(alignment: .center) {
             ZStack {

+ 11 - 0
FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift

@@ -6,6 +6,7 @@ struct PumpView: View {
     @Binding var name: String
     @Binding var expiresAtDate: Date?
     @Binding var timerDate: Date
+    @Binding var timeZone: TimeZone?
 
     private var reservoirFormatter: NumberFormatter {
         let formatter = NumberFormatter()
@@ -39,6 +40,16 @@ struct PumpView: View {
                         )
                         .font(.footnote).fontWeight(.bold)
                     }
+
+                    if let timeZone = timeZone, timeZone.secondsFromGMT() != TimeZone.current.secondsFromGMT() {
+                        Image(systemName: "clock.badge.exclamationmark.fill")
+                            .resizable()
+                            .aspectRatio(contentMode: .fit)
+                            .frame(maxHeight: 13)
+                            .symbolRenderingMode(.palette)
+                            .foregroundStyle(.red, Color(.warning))
+                            .padding(.bottom, 10)
+                    }
                 }.frame(alignment: .top)
             }
             if let battery = battery, battery.display ?? false, expiresAtDate == nil {

+ 99 - 12
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -12,6 +12,24 @@ extension Home {
         @State var isStatusPopupPresented = false
         @State var showCancelAlert = false
 
+        struct Buttons: Identifiable {
+            let label: String
+            let number: String
+            var active: Bool
+            let hours: Int16
+            var id: String { label }
+        }
+
+        @State var timeButtons: [Buttons] = [
+            Buttons(label: "2 hours", number: "2", active: false, hours: 2),
+            Buttons(label: "4 hours", number: "4", active: false, hours: 4),
+            Buttons(label: "6 hours", number: "6", active: false, hours: 6),
+            Buttons(label: "12 hours", number: "12", active: false, hours: 12),
+            Buttons(label: "24 hours", number: "24", active: false, hours: 24)
+        ]
+
+        let buttonFont = Font.custom("TimeButtonFont", size: 14)
+
         @Environment(\.managedObjectContext) var moc
         @Environment(\.colorScheme) var colorScheme
 
@@ -37,6 +55,11 @@ extension Home {
             sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
         ) var enactedSliderTT: FetchedResults<TempTargetsSlider>
 
+        @FetchRequest(
+            entity: UXSettings.entity(),
+            sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
+        ) var fetchedSettings: FetchedResults<UXSettings>
+
         private var numberFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             formatter.numberStyle = .decimal
@@ -153,7 +176,8 @@ extension Home {
                 battery: $state.battery,
                 name: $state.pumpName,
                 expiresAtDate: $state.pumpExpiresAtDate,
-                timerDate: $state.timerDate
+                timerDate: $state.timerDate,
+                timeZone: $state.timeZone
             )
             .onTapGesture {
                 if state.pumpDisplayState != nil {
@@ -314,19 +338,49 @@ extension Home {
                 }
 
                 if let progress = state.bolusProgress {
-                    Text("Bolusing")
-                        .font(.system(size: 12, weight: .bold)).foregroundColor(.insulin)
-                    ProgressView(value: Double(progress))
-                        .progressViewStyle(BolusProgressViewStyle())
-                        .padding(.trailing, 8)
-                        .onTapGesture {
-                            state.cancelBolus()
-                        }
+                    HStack {
+                        Text("Bolusing")
+                            .font(.system(size: 12, weight: .bold)).foregroundColor(.insulin)
+                        ProgressView(value: Double(progress))
+                            .progressViewStyle(BolusProgressViewStyle())
+                            .padding(.trailing, 8)
+                    }
+                    .onTapGesture {
+                        state.cancelBolus()
+                    }
                 }
             }
             .frame(maxWidth: .infinity, maxHeight: 30)
         }
 
+        var timeInterval: some View {
+            HStack(alignment: .center) {
+                let saveButton = UXSettings(context: moc)
+                ForEach(timeButtons) { button in
+                    Text(button.active ? NSLocalizedString(button.label, comment: "") : button.number).onTapGesture {
+                        let index = timeButtons.firstIndex(where: { $0.label == button.label }) ?? 0
+                        highlightButtons(index, onAppear: false)
+                        saveButton.hours = button.hours
+                        saveButton.date = Date.now
+                        try? moc.save()
+                        state.hours = button.hours
+                    }
+                    .foregroundStyle(button.active ? .primary : .secondary)
+                    .frame(maxHeight: 20).padding(.horizontal)
+                    .background(button.active ? Color(.systemGray5) : .clear, in: .capsule(style: .circular))
+                }
+                Image(systemName: "ellipsis.circle.fill")
+                    .foregroundStyle(.secondary)
+                    .padding(.leading)
+                    .onTapGesture {
+                        state.showModal(for: .statisticsConfig)
+                    }
+            }
+            .font(buttonFont)
+            .padding(.top, 20)
+            .padding(.bottom, 40)
+        }
+
         var legendPanel: some View {
             ZStack {
                 HStack(alignment: .center) {
@@ -401,13 +455,13 @@ extension Home {
                     smooth: $state.smooth,
                     highGlucose: $state.highGlucose,
                     lowGlucose: $state.lowGlucose,
-                    screenHours: $state.screenHours,
+                    screenHours: $state.hours,
                     displayXgridLines: $state.displayXgridLines,
                     displayYgridLines: $state.displayYgridLines,
                     thresholdLines: $state.thresholdLines
                 )
             }
-            .padding(.bottom)
+            // .padding(.bottom)
             .modal(for: .dataTable, from: self)
         }
 
@@ -476,6 +530,31 @@ extension Home {
             return (name: profileString, isOn: display)
         }
 
+        func highlightButtons(_ int: Int?, onAppear: Bool) {
+            var index = 0
+            if let integer = int, !onAppear {
+                repeat {
+                    if index == integer {
+                        timeButtons[index].active = true
+                    } else {
+                        timeButtons[index].active = false
+                    }
+                    index += 1
+                } while index < timeButtons.count
+            } else if onAppear {
+                let i = timeButtons.firstIndex(where: { $0.hours == (fetchedSettings.first?.hours ?? 6) }) ?? 2
+                index = 0
+                repeat {
+                    if index == i {
+                        timeButtons[index].active = true
+                    } else {
+                        timeButtons[index].active = false
+                    }
+                    index += 1
+                } while index < timeButtons.count
+            }
+        }
+
         @ViewBuilder private func bottomPanel(_ geo: GeometryProxy) -> some View {
             ZStack {
                 Rectangle().fill(Color.gray.opacity(0.3)).frame(height: 50 + geo.safeAreaInsets.bottom)
@@ -574,13 +653,18 @@ extension Home {
                     header(geo)
                     infoPanel
                     mainChart
+                    timeInterval
                     legendPanel
                     profiles(geo)
                     bottomPanel(geo)
                 }
                 .edgesIgnoringSafeArea(.vertical)
             }
-            .onAppear(perform: configureView)
+            .onAppear {
+                configureView {
+                    highlightButtons(nil, onAppear: true)
+                }
+            }
             .navigationTitle("Home")
             .navigationBarHidden(true)
             .ignoresSafeArea(.keyboard)
@@ -603,6 +687,9 @@ extension Home {
                             }
                     )
             }
+            .onDisappear {
+                state.saveSettings()
+            }
         }
 
         private var popup: some View {

+ 0 - 8
FreeAPS/Sources/Modules/StatConfig/StatConfigStateModel.swift

@@ -5,7 +5,6 @@ extension StatConfig {
         @Published var overrideHbA1cUnit = false
         @Published var low: Decimal = 4 / 0.0555
         @Published var high: Decimal = 10 / 0.0555
-        @Published var hours: Decimal = 6
         @Published var xGridLines = false
         @Published var yGridLines: Bool = false
         @Published var oneDimensionalGraph = false
@@ -41,13 +40,6 @@ extension StatConfig {
                 guard units == .mmolL else { return $0 }
                 return $0.asMgdL
             })
-
-            subscribeSetting(\.hours, on: $hours.map(Int.init), initial: {
-                let value = max(min($0, 24), 2)
-                hours = Decimal(value)
-            }, map: {
-                $0
-            })
         }
     }
 }

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

@@ -31,12 +31,6 @@ extension StatConfig {
                     Toggle("Display Chart Y - Grid lines", isOn: $state.yGridLines)
                     Toggle("Display Chart Threshold lines for Low and High", isOn: $state.rulerMarks)
                     Toggle("Standing / Laying TIR Chart", isOn: $state.oneDimensionalGraph)
-                    HStack {
-                        Text("Hours X-Axis (6 default)")
-                        Spacer()
-                        DecimalTextField("6", value: $state.hours, formatter: carbsFormatter)
-                        Text("hours").foregroundColor(.secondary)
-                    }
                 } header: { Text("Home Chart settings ") }
 
                 Section {
@@ -64,6 +58,7 @@ extension StatConfig {
             .onAppear(perform: configureView)
             .navigationBarTitle("UI/UX")
             .navigationBarTitleDisplayMode(.automatic)
+            .navigationBarItems(trailing: Button("Close", action: state.hideModal))
         }
     }
 }

+ 32 - 33
FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift

@@ -26,7 +26,7 @@ protocol HealthKitManager: GlucoseSource {
     /// Delete glucose with syncID
     func deleteGlucose(syncID: String)
     /// delete carbs with syncID
-    func deleteCarbs(syncID: String, isFPU: Bool?, fpuID: String?)
+    func deleteCarbs(syncID: String, fpuID: String)
     /// delete insulin with syncID
     func deleteInsulin(syncID: String)
 }
@@ -46,7 +46,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver, P
         static let healthInsulinObject = HKObjectType.quantityType(forIdentifier: .insulinDelivery)
 
         // Meta-data key of FreeASPX data in HealthStore
-        static let freeAPSMetaKey = "fromFreeAPSX"
+        static let freeAPSMetaKey = "From iAPS"
     }
 
     @Injected() private var glucoseStorage: GlucoseStorage!
@@ -186,7 +186,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver, P
         else { return }
 
         let carbsWithId = carbs.filter { c in
-            guard c.collectionID != nil else { return false }
+            guard c.id != nil else { return false }
             return true
         }
 
@@ -194,7 +194,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver, P
             let sampleIDs = samples.compactMap(\.syncIdentifier)
             let sampleDates = samples.map(\.startDate)
             let samplesToSave = carbsWithId
-                .filter { !sampleIDs.contains($0.collectionID!) } // id existing in AH
+                .filter { !sampleIDs.contains($0.id ?? "") } // id existing in AH
                 .filter { !sampleDates.contains($0.createdAt) } // not id but exaclty the same datetime
                 .map {
                     HKQuantitySample(
@@ -203,8 +203,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver, P
                         start: $0.createdAt,
                         end: $0.createdAt,
                         metadata: [
-                            HKMetadataKeyExternalUUID: $0.collectionID ?? "_id",
-                            HKMetadataKeySyncIdentifier: $0.collectionID ?? "_id",
+                            HKMetadataKeySyncIdentifier: $0.id ?? "_id",
                             HKMetadataKeySyncVersion: 1,
                             Config.freeAPSMetaKey: true
                         ]
@@ -568,41 +567,41 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver, P
 
     // - MARK Carbs function
 
-    func deleteCarbs(syncID: String, isFPU: Bool?, fpuID: String?) {
+    func deleteCarbs(syncID: String, fpuID: String) {
         guard settingsManager.settings.useAppleHealth,
               let sampleType = Config.healthCarbObject,
               checkAvailabilitySave(objectTypeToHealthStore: sampleType)
         else { return }
 
-        if let isFPU = isFPU, !isFPU {
-            processQueue.async {
-                let predicate = HKQuery.predicateForObjects(
-                    withMetadataKey: HKMetadataKeySyncIdentifier,
-                    operatorType: .equalTo,
-                    value: syncID
-                )
-                self.healthKitStore.deleteObjects(of: sampleType, predicate: predicate) { _, _, error in
-                    guard let error = error else { return }
-                    warning(.service, "Cannot delete sample with syncID: \(syncID)", error: error)
-                }
-            }
-        } else {
-            // need to find all syncID
-            guard let fpuID = fpuID else { return }
+        print("meals 4: ID: " + syncID + " FPU ID: " + fpuID)
 
-            processQueue.async {
-                let recentCarbs: [CarbsEntry] = self.carbsStorage.recent()
-                let ids = recentCarbs.filter { $0.fpuID == fpuID }.compactMap(\.id)
-                let predicate = HKQuery.predicateForObjects(
-                    withMetadataKey: HKMetadataKeySyncIdentifier,
-                    allowedValues: ids
-                )
+        if syncID != "" {
+            let predicate = HKQuery.predicateForObjects(
+                withMetadataKey: HKMetadataKeySyncIdentifier,
+                operatorType: .equalTo,
+                value: syncID
+            )
 
-                self.healthKitStore.deleteObjects(of: sampleType, predicate: predicate) { _, _, error in
-                    guard let error = error else { return }
-                    warning(.service, "Cannot delete sample with fpuID: \(fpuID)", error: error)
-                }
+            healthKitStore.deleteObjects(of: sampleType, predicate: predicate) { _, _, error in
+                guard let error = error else { return }
+                warning(.service, "Cannot delete sample with syncID: \(syncID)", error: error)
+            }
+        }
+
+        if fpuID != "" {
+            // processQueue.async {
+            let recentCarbs: [CarbsEntry] = carbsStorage.recent()
+            let ids = recentCarbs.filter { $0.fpuID == fpuID }.compactMap(\.id)
+            let predicate = HKQuery.predicateForObjects(
+                withMetadataKey: HKMetadataKeySyncIdentifier,
+                allowedValues: ids
+            )
+            print("found IDs: " + ids.description)
+            healthKitStore.deleteObjects(of: sampleType, predicate: predicate) { _, _, error in
+                guard let error = error else { return }
+                warning(.service, "Cannot delete sample with fpuID: \(fpuID)", error: error)
             }
+            // }
         }
     }
 

+ 10 - 3
FreeAPS/Sources/Services/Network/NightscoutAPI.swift

@@ -141,18 +141,25 @@ extension NightscoutAPI {
             .eraseToAnyPublisher()
     }
 
-    func deleteCarbs(at uniqueID: String) -> AnyPublisher<Void, Swift.Error> {
+    func deleteCarbs(_ treatement: DataTable.Treatment) -> AnyPublisher<Void, Swift.Error> {
         var components = URLComponents()
         components.scheme = url.scheme
         components.host = url.host
         components.port = url.port
         components.path = Config.treatmentsPath
+
+        var arguments = "find[_id][$eq]"
+        if treatement.isFPU ?? false {
+            arguments = "find[fpuID][$eq]"
+        }
+        let value = !(treatement.isFPU ?? false) ? treatement.id : (treatement.fpuID ?? "")
+
         components.queryItems = [
             // Removed below because it prevented all futire entries to be deleted. Don't know why?
             /* URLQueryItem(name: "find[carbs][$exists]", value: "true"), */
             URLQueryItem(
-                name: "find[collectionID][$eq]",
-                value: uniqueID
+                name: arguments,
+                value: value
             )
         ]
 

+ 89 - 23
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -9,7 +9,7 @@ protocol NightscoutManager: GlucoseSource {
     func fetchCarbs() -> AnyPublisher<[CarbsEntry], Never>
     func fetchTempTargets() -> AnyPublisher<[TempTarget], Never>
     func fetchAnnouncements() -> AnyPublisher<[Announcement], Never>
-    func deleteCarbs(at uniqueID: String, isFPU: Bool?, fpuID: String?, syncID: String)
+    func deleteCarbs(_ treatement: DataTable.Treatment, complexMeal: Bool)
     func deleteInsulin(at date: Date)
     func deleteManualGlucose(at: Date)
     func uploadStatus()
@@ -177,32 +177,98 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
             .eraseToAnyPublisher()
     }
 
-    func deleteCarbs(at uniqueID: String, isFPU: Bool?, fpuID: String?, syncID: String) {
-        // remove in AH
-        healthkitManager.deleteCarbs(syncID: syncID, isFPU: isFPU, fpuID: fpuID)
-
+    func deleteCarbs(_ treatement: DataTable.Treatment, complexMeal: Bool) {
         guard let nightscout = nightscoutAPI, isUploadEnabled else {
-            carbsStorage.deleteCarbs(at: uniqueID)
+            carbsStorage.deleteCarbs(at: treatement.id, fpuID: treatement.fpuID ?? "", complex: complexMeal)
             return
         }
 
-        nightscout.deleteCarbs(at: uniqueID)
-            .collect()
-            .sink { completion in
-                self.carbsStorage.deleteCarbs(at: uniqueID)
-                switch completion {
-                case .finished:
-                    debug(.nightscout, "Carbs deleted")
-                case let .failure(error):
-                    info(
-                        .nightscout,
-                        "Deletion of carbs in NightScout not done \n \(error.localizedDescription)",
-                        type: MessageType.warning
-                    )
-                }
-            } receiveValue: { _ in }
-            .store(in: &lifetime)
-        // }
+        print("meals 3: ID: " + (treatement.id ?? "").description + " FPU ID: " + (treatement.fpuID ?? "").description)
+
+        var arg1 = ""
+        var arg2 = ""
+        if complexMeal {
+            arg1 = treatement.id ?? ""
+            arg2 = treatement.fpuID ?? ""
+        } else if treatement.isFPU ?? false {
+            arg1 = ""
+            arg2 = treatement.fpuID ?? ""
+        } else {
+            arg1 = treatement.id
+            arg2 = ""
+        }
+        healthkitManager.deleteCarbs(syncID: arg1, fpuID: arg2)
+
+        if complexMeal {
+            nightscout.deleteCarbs(treatement)
+                .collect()
+                .sink { completion in
+                    self.carbsStorage.deleteCarbs(at: treatement.id ?? "", fpuID: treatement.fpuID ?? "", complex: true)
+                    switch completion {
+                    case .finished:
+                        debug(.nightscout, "Carbs deleted")
+                    case let .failure(error):
+                        info(
+                            .nightscout,
+                            "Deletion of carbs in NightScout not done \n \(error.localizedDescription)",
+                            type: MessageType.warning
+                        )
+                    }
+                } receiveValue: { _ in }
+                .store(in: &lifetime)
+
+            if (treatement.fpuID ?? "") != "" {
+                nightscout.deleteCarbs(treatement)
+                    .collect()
+                    .sink { completion in
+                        switch completion {
+                        case .finished:
+                            debug(.nightscout, "Carb equivalents deleted from NS")
+                        case let .failure(error):
+                            info(
+                                .nightscout,
+                                "Deletion of carb equivalents in NightScout not done \n \(error.localizedDescription)",
+                                type: MessageType.warning
+                            )
+                        }
+                    } receiveValue: { _ in }
+                    .store(in: &lifetime)
+            }
+        } else if treatement.isFPU ?? false {
+            nightscout.deleteCarbs(treatement)
+                .collect()
+                .sink { completion in
+                    self.carbsStorage.deleteCarbs(at: "", fpuID: treatement.fpuID ?? "", complex: false)
+                    switch completion {
+                    case .finished:
+                        debug(.nightscout, "Carb equivalents deleted")
+                    case let .failure(error):
+                        info(
+                            .nightscout,
+                            "Deletion of carb equivalents in NightScout not done \n \(error.localizedDescription)",
+                            type: MessageType.warning
+                        )
+                    }
+                } receiveValue: { _ in }
+                .store(in: &lifetime)
+        } else {
+            nightscout.deleteCarbs(treatement)
+                .collect()
+                .sink { completion in
+                    self.carbsStorage.deleteCarbs(at: treatement.id, fpuID: "", complex: false)
+                    switch completion {
+                    case .finished:
+                        debug(.nightscout, "Carbs deleted")
+                    case let .failure(error):
+                        info(
+                            .nightscout,
+                            "Deletion of carbs in NightScout not done \n \(error.localizedDescription)",
+                            type: MessageType.warning
+                        )
+                    }
+                } receiveValue: { _ in }
+                .store(in: &lifetime)
+        }
     }
 
     func deleteInsulin(at date: Date) {

+ 1 - 2
FreeAPS/Sources/Services/WatchManager/WatchManager.swift

@@ -62,7 +62,6 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
             self.state.delta = glucoseValues.delta
             self.state.trendRaw = readings.first?.direction ?? "↔︎"
             self.state.glucoseDate = readings.first?.date ?? .distantPast
-            self.state.glucoseDateInterval = self.state.glucoseDate.map { UInt64($0.timeIntervalSince1970) }
             self.state.lastLoopDate = self.enactedSuggestion?.recieved == true ? self.enactedSuggestion?.deliverAt : self
                 .apsManager.lastLoopDate
             self.state.lastLoopDateInterval = self.state.lastLoopDate.map {
@@ -331,7 +330,7 @@ extension BaseWatchManager: WCSessionDelegate {
         {
             carbsStorage.storeCarbs(
                 [CarbsEntry(
-                    collectionID: UUID().uuidString,
+                    id: UUID().uuidString,
                     createdAt: Date(),
                     carbs: Decimal(carbs),
                     fat: Decimal(fat),

+ 1 - 1
FreeAPS/Sources/Shortcuts/Carbs/CarbPresetIntentRequest.swift

@@ -11,7 +11,7 @@ import Foundation
 
         carbsStorage.storeCarbs(
             [CarbsEntry(
-                collectionID: UUID().uuidString,
+                id: UUID().uuidString,
                 createdAt: dateAdded,
                 carbs: carbs,
                 fat: Decimal(quantityFat),

+ 0 - 1
FreeAPSWatch WatchKit Extension/DataFlow.swift

@@ -6,7 +6,6 @@ struct WatchState: Codable {
     var trendRaw: String?
     var delta: String?
     var glucoseDate: Date?
-    var glucoseDateInterval: UInt64?
     var lastLoopDate: Date?
     var lastLoopDateInterval: UInt64?
     var bolusIncrement: Decimal?

+ 12 - 1
FreeAPSWatch WatchKit Extension/Views/MainView.swift

@@ -80,7 +80,18 @@ struct MainView: View {
                             .scaledToFill()
                             .minimumScaleFactor(0.5)
                     }
-                    Text(state.delta).font(.caption2).foregroundColor(.gray)
+                    /* IF YOU WANT TO DISPLAY MINUTES AGO, UNCOMMENT the gray code below
+                     let minutesAgo: TimeInterval = -1 * (state.glucoseDate ?? .distantPast).timeIntervalSinceNow / 60
+                     let minuteString = minutesAgo.formatted(.number.grouping(.never).rounded().precision(.fractionLength(0)))
+                     */
+                    HStack {
+                        /* if minutesAgo > 0 {
+                             Text(minuteString)
+                             Text("min")
+                         } */
+                        Text(state.delta)
+                    }
+                    .font(.caption2).foregroundColor(.gray)
                 }
                 Spacer()