Sfoglia il codice sorgente

Version 1.0.0 (#565)

* New slim format for statPanel

* Better format for Apple Watch Home View.
Same size for HR as other items. Longpress the Heart for 1 seconds to make the heart and HR font bigger size. Long press again to reduce to before.
Align Loop Circle and min ago with BG and delta BG horisontally.
Remove inverted logic.

* Change on tap
Change display of Average / Median and CV / SD and Loops/Errors when tapping

* Fix refresh SD/CV and Average/Median values

* Revert to deafault filterTooFrequentGlucose min interval t0 4.5 minutes to prevent too frequent BG readings from GlucoseDirect and xdrip with Libre2

* Crowdin updates

* Don't run function statistics() when disabled in preferences

* Suggested test for red loops with MDT

* Revert alias back to BRANCH

* Delete Insulin from data table and from Nightscout, with required authorization

* Make carbs button same color as carb dots.

* Color glucose in Header View

* Delete code for 90 days (not necessary)

* CGM Readings in statPanel, when tapping.

* Test to increase to 90 seconds scanning when using Dexcom Manager.

* Header View UI improvements.
Dynamic fonts for Header View elements.
More prominent Glucose.
Improved horisontal alignment.
Display time delta instad of timestamp also for current glucose.

* Improve description for Sigmoid Function

* Bump version to 1
Jon B Mårtensson 3 anni fa
parent
commit
03d8b58e75
42 ha cambiato i file con 461 aggiunte e 390 eliminazioni
  1. 2 2
      Config.xcconfig
  2. 40 40
      Dependencies/rileylink_ios/MinimedKitUI/tr.lproj/Localizable.strings
  3. 1 1
      FreeAPS/Resources/Info.plist
  4. 0 112
      FreeAPS/Resources/json/defaults/monitor/statistics.json
  5. 8 53
      FreeAPS/Sources/APS/APSManager.swift
  6. 1 1
      FreeAPS/Sources/APS/CGM/DexcomSource.swift
  7. 1 1
      FreeAPS/Sources/APS/DeviceDataManager.swift
  8. 15 0
      FreeAPS/Sources/APS/Storage/PumpHistoryStorage.swift
  9. 8 2
      FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings
  10. 8 2
      FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings
  11. 8 2
      FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings
  12. 9 3
      FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings
  13. 8 2
      FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings
  14. 8 2
      FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings
  15. 8 2
      FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings
  16. 8 2
      FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings
  17. 8 2
      FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings
  18. 8 2
      FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings
  19. 8 2
      FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings
  20. 11 5
      FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings
  21. 8 2
      FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings
  22. 8 2
      FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings
  23. 8 2
      FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings
  24. 11 5
      FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings
  25. 8 2
      FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings
  26. 8 2
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  27. 39 33
      FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings
  28. 8 2
      FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings
  29. 8 2
      FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings
  30. 2 1
      FreeAPS/Sources/Models/Statistics.swift
  31. 1 0
      FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift
  32. 4 0
      FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift
  33. 11 0
      FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift
  34. 26 0
      FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift
  35. 0 1
      FreeAPS/Sources/Modules/Home/DurationButton.swift
  36. 47 16
      FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift
  37. 4 4
      FreeAPS/Sources/Modules/Home/View/Header/LoopView.swift
  38. 13 10
      FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift
  39. 29 67
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  40. 1 1
      FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift
  41. 29 0
      FreeAPS/Sources/Services/Network/NightscoutAPI.swift
  42. 20 0
      FreeAPS/Sources/Services/Network/NightscoutManager.swift

+ 2 - 2
Config.xcconfig

@@ -1,7 +1,7 @@
 APP_DISPLAY_NAME = FreeAPS X
-APP_VERSION = 0.6.4
+APP_VERSION = 1.0.0
 APP_BUILD_NUMBER = 1
-COPYRIGHT_NOTICE = 
+BRANCH = 
 DEVELOPER_TEAM = ##TEAM_ID##
 BUNDLE_IDENTIFIER = ru.artpancreas.$(DEVELOPMENT_TEAM).FreeAPS
 APP_GROUP_ID = group.com.$(DEVELOPMENT_TEAM).loopkit.LoopGroup

File diff suppressed because it is too large
+ 40 - 40
Dependencies/rileylink_ios/MinimedKitUI/tr.lproj/Localizable.strings


+ 1 - 1
FreeAPS/Resources/Info.plist

@@ -70,7 +70,7 @@
 	<key>NSHealthUpdateUsageDescription</key>
 	<string>Health App is used to store blood glucose data</string>
 	<key>NSHumanReadableCopyright</key>
-	<string>$(COPYRIGHT_NOTICE)</string>
+	<string>$(BRANCH)</string>
 	<key>UIApplicationSceneManifest</key>
 	<dict>
 		<key>UIApplicationSupportsMultipleScenes</key>

+ 0 - 112
FreeAPS/Resources/json/defaults/monitor/statistics.json

@@ -1,114 +1,2 @@
 [
-  {
-    "createdAt" : "1978-02-22T11:43:54.659Z",
-    "iPhone" : "Default",
-    "iOS" : "Default",
-    "Build_Version" : "Default",
-    "Build_Number2" : "Default",
-    "Branch" : "Default",
-    "Build_Date" : "1978-02-22T11:59:59.659Z",
-    "Algorithm" : "Default",
-    "AdjustmentFactor" : 1,
-    "Pump" : "Default",
-    "CGM" : "Default",
-    "insulinType" : "Default",
-    "peakActivityTime" : 65,
-    "TDD" : 0,
-    "Carbs_24h":  0,
-    "GlucoseStorage_Days" : 0,
-    "Statistics" : {
-      "Distribution" : [
-        {
-          "TIR" : [
-            {
-              "day" : 0,
-              "sevenDays" : 0,
-              "thirtyDays" : 0,
-              "ninetyDays" : 0,
-              "totalDays" : 0
-            }
-          ],
-          "Hypos" : [
-            {
-              "day" : 0,
-              "sevenDays" : 0,
-              "thirtyDays" : 0,
-              "ninetyDays" : 0,
-              "totalDays" : 0
-            }
-          ],
-          "Hypers" : [
-            {
-              "day" : 0,
-              "sevenDays" : 0,
-              "thirtyDays" : 0,
-              "ninetyDays" : 0,
-              "totalDays" : 0
-            }
-          ]
-        }
-      ],
-      "Glucose" : [
-        {
-          "Average" : [
-            {
-              "oneDay_mmol" : 0,
-              "day" : 0,
-              "sevenDays_mmol" : 0,
-              "sevenDays" : 0,
-              "thirtyDays_mmol" : 0,
-              "thirtyDays" : 0,
-              "ninetyDays_mmol" : 0,
-              "ninetyDays" : 0,
-              "totalDays_mmol" : 0,
-              "totalDays" : 0
-            }
-          ],
-          "Median" : [
-            {
-              "oneDay_mmol" : 0,
-              "day" : 0,
-              "sevenDays_mmol" : 0,
-              "sevenDays" : 0,
-              "thirtyDays_mmol":  0,
-              "thirtyDays" : 0,
-              "ninetyDays_mmol" : 0,
-              "ninetyDays" : 0,
-              "totalDays_mmol" : 0,
-              "totalDays" : 0
-            }
-          ]
-        }
-      ],
-      "HbA1c" : [
-        {
-          "oneDay_mmolMol" : 0,
-          "day" : 0,
-          "sevenDays_mmolMol" : 0,
-          "sevenDays" : 0,
-          "thirtyDays_mmolMol" : 0,
-          "thirtyDays" : 0,
-          "ninetyDays_mmolMol" : 0,
-          "ninetyDays" : 0,
-          "totalDays_mmolMol" : 0,
-          "totalDays" : 0
-        }
-      ],
-      "LoopCycles" : [
-        {
-          "loops" : 0,
-          "errors" : 0,
-          "success_rate" : 0,
-          "avg_interval" : 0,
-          "median_interval" : 0,
-          "min_interval" : 0,
-          "max_interval" : 0,
-          "avg_duration" : 0,
-          "median_duration" : 0,
-          "min_duration" : 0,
-          "max_duration" : 0
-        }
-      ]
-    }
-  }
 ]

+ 8 - 53
FreeAPS/Sources/APS/APSManager.swift

@@ -240,7 +240,9 @@ final class BaseAPSManager: APSManager, Injectable {
         loopStats(loopStatRecord: loopStatRecord)
 
         // Create a statistics.json
-        statistics()
+        if settings.displayStatistics {
+            statistics()
+        }
 
         if settings.closedLoop {
             reportEnacted(received: error == nil)
@@ -763,7 +765,7 @@ final class BaseAPSManager: APSManager, Injectable {
 
         let updateThisOften = Int(settingsManager.preferences.updateInterval)
 
-        // Only run every 30 minutes or when pressing statPanel
+        // Only run every 30 minutesl
         if testIfEmpty != 0 {
             guard testFile[0].created_at.addingTimeInterval(updateThisOften.minutes.timeInterval) < Date()
             else {
@@ -905,18 +907,15 @@ final class BaseAPSManager: APSManager, Injectable {
         var bgArray_1_: [Double] = []
         var bgArray_7_: [Double] = []
         var bgArray_30_: [Double] = []
-        var bgArray_90_: [Double] = []
         var bgArrayForTIR: [(bg_: Double, date_: Date)] = []
         var bgArray_1: [(bg_: Double, date_: Date)] = []
         var bgArray_7: [(bg_: Double, date_: Date)] = []
         var bgArray_30: [(bg_: Double, date_: Date)] = []
-        var bgArray_90: [(bg_: Double, date_: Date)] = []
         var medianBG = 0.0
         var nr_bgs: Decimal = 0
         var nr_bgs_1: Decimal = 0
         var nr_bgs_7: Decimal = 0
         var nr_bgs_30: Decimal = 0
-        var nr_bgs_90: Decimal = 0
 
         var startDate = Date("1978-02-22T11:43:54.659Z")
         if endIndex >= 0 {
@@ -925,11 +924,9 @@ final class BaseAPSManager: APSManager, Injectable {
         var end1 = false
         var end7 = false
         var end30 = false
-        var end90 = false
         var bg_1: Decimal = 0
         var bg_7: Decimal = 0
         var bg_30: Decimal = 0
-        var bg_90: Decimal = 0
         var bg_total: Decimal = 0
         var j = -1
 
@@ -967,14 +964,6 @@ final class BaseAPSManager: APSManager, Injectable {
                         nr_bgs_30 = nr_bgs
                         // time_30 = ((startDate ?? Date()) - entry.date).timeInterval
                     }
-                    if (startDate! - entry.date).timeInterval >= 7.776E6, !end90 {
-                        end90 = true
-                        bg_90 = bg / nr_bgs
-                        bgArray_90 = bgArrayForTIR
-                        bgArray_90_ = bgArray
-                        nr_bgs_90 = nr_bgs
-                        // time_90 = ((startDate ?? Date()) - entry.date).timeInterval
-                    }
                 }
             }
         }
@@ -1067,14 +1056,6 @@ final class BaseAPSManager: APSManager, Injectable {
             IFCCa1CStatisticValue_30 = 10.929 *
                 (NGSPa1CStatisticValue_30 - 2.152) // IFCC (mmol/mol)  A1C(mmol/mol) = 10.929 * (A1C(%) - 2.15)
         }
-        // 90 Days
-        var NGSPa1CStatisticValue_90: Decimal = 0.0
-        var IFCCa1CStatisticValue_90: Decimal = 0.0
-        if end90 {
-            NGSPa1CStatisticValue_90 = (46.7 + bg_90) / 28.7 // NGSP (%)
-            IFCCa1CStatisticValue_90 = 10.929 *
-                (NGSPa1CStatisticValue_90 - 2.152) // IFCC (mmol/mol)  A1C(mmol/mol) = 10.929 * (A1C(%) - 2.15)
-        }
         // Total days
         var NGSPa1CStatisticValue_total: Decimal = 0.0
         var IFCCa1CStatisticValue_total: Decimal = 0.0
@@ -1088,7 +1069,6 @@ final class BaseAPSManager: APSManager, Injectable {
             day: roundDecimal(Decimal(medianCalculation(array: bgArray_1.map(\.bg_))), 1),
             week: roundDecimal(Decimal(medianCalculation(array: bgArray_7.map(\.bg_))), 1),
             month: roundDecimal(Decimal(medianCalculation(array: bgArray_30.map(\.bg_))), 1),
-            ninetyDays: roundDecimal(Decimal(medianCalculation(array: bgArray_90.map(\.bg_))), 1),
             total: roundDecimal(Decimal(medianBG), 1)
         )
 
@@ -1096,7 +1076,6 @@ final class BaseAPSManager: APSManager, Injectable {
             day: roundDecimal(NGSPa1CStatisticValue, 1),
             week: roundDecimal(NGSPa1CStatisticValue_7, 1),
             month: roundDecimal(NGSPa1CStatisticValue_30, 1),
-            ninetyDays: roundDecimal(NGSPa1CStatisticValue_90, 1),
             total: roundDecimal(NGSPa1CStatisticValue_total, 1)
         )
 
@@ -1107,14 +1086,12 @@ final class BaseAPSManager: APSManager, Injectable {
             bg_1 = bg_1.asMmolL
             bg_7 = bg_7.asMmolL
             bg_30 = bg_30.asMmolL
-            bg_90 = bg_90.asMmolL
             bg_total = bg_total.asMmolL
 
             median = Durations(
                 day: roundDecimal(Decimal(medianCalculation(array: bgArray_1.map(\.bg_))).asMmolL, 1),
                 week: roundDecimal(Decimal(medianCalculation(array: bgArray_7.map(\.bg_))).asMmolL, 1),
                 month: roundDecimal(Decimal(medianCalculation(array: bgArray_30.map(\.bg_))).asMmolL, 1),
-                ninetyDays: roundDecimal(Decimal(medianCalculation(array: bgArray_90.map(\.bg_))).asMmolL, 1),
                 total: roundDecimal(Decimal(medianBG).asMmolL, 1)
             )
 
@@ -1124,7 +1101,6 @@ final class BaseAPSManager: APSManager, Injectable {
                     day: roundDecimal(IFCCa1CStatisticValue, 1),
                     week: roundDecimal(IFCCa1CStatisticValue_7, 1),
                     month: roundDecimal(IFCCa1CStatisticValue_30, 1),
-                    ninetyDays: roundDecimal(IFCCa1CStatisticValue_90, 1),
                     total: roundDecimal(IFCCa1CStatisticValue_total, 1)
                 )
             }
@@ -1133,7 +1109,6 @@ final class BaseAPSManager: APSManager, Injectable {
                 day: roundDecimal(IFCCa1CStatisticValue, 1),
                 week: roundDecimal(IFCCa1CStatisticValue_7, 1),
                 month: roundDecimal(IFCCa1CStatisticValue_30, 1),
-                ninetyDays: roundDecimal(IFCCa1CStatisticValue_90, 1),
                 total: roundDecimal(IFCCa1CStatisticValue_total, 1)
             )
         }
@@ -1141,9 +1116,13 @@ final class BaseAPSManager: APSManager, Injectable {
         // round output values
         daysBG = roundDouble(daysBG, 1)
 
+        let glucose24Hours = storage.retrieve(OpenAPS.Monitor.glucose, as: [BloodGlucose].self)
+        let nrOfCGMReadings = glucose24Hours?.count ?? 0
+
         let loopstat = LoopCycles(
             loops: Int(successNR + errorNR),
             errors: Int(errorNR),
+            readings: nrOfCGMReadings,
             success_rate: Decimal(round(successRate ?? 0)),
             avg_interval: roundDecimal(Decimal(averageIntervalLoops), 1),
             median_interval: roundDecimal(Decimal(medianInterval), 1),
@@ -1159,7 +1138,6 @@ final class BaseAPSManager: APSManager, Injectable {
         var oneDay_: (TIR: Double, hypos: Double, hypers: Double) = (0.0, 0.0, 0.0)
         var sevenDays_: (TIR: Double, hypos: Double, hypers: Double) = (0.0, 0.0, 0.0)
         var thirtyDays_: (TIR: Double, hypos: Double, hypers: Double) = (0.0, 0.0, 0.0)
-        var ninetyDays_: (TIR: Double, hypos: Double, hypers: Double) = (0.0, 0.0, 0.0)
         var totalDays_: (TIR: Double, hypos: Double, hypers: Double) = (0.0, 0.0, 0.0)
 
         // Get all TIR calcs for every case
@@ -1172,10 +1150,6 @@ final class BaseAPSManager: APSManager, Injectable {
         if end30 {
             thirtyDays_ = tir(bgArray_30)
         }
-        if end90 {
-            ninetyDays_ = tir(bgArray_90)
-        }
-
         if nr_bgs > 0 {
             totalDays_ = tir(bgArrayForTIR)
         }
@@ -1184,7 +1158,6 @@ final class BaseAPSManager: APSManager, Injectable {
             day: roundDecimal(Decimal(oneDay_.TIR), 1),
             week: roundDecimal(Decimal(sevenDays_.TIR), 1),
             month: roundDecimal(Decimal(thirtyDays_.TIR), 1),
-            ninetyDays: roundDecimal(Decimal(ninetyDays_.TIR), 1),
             total: roundDecimal(Decimal(totalDays_.TIR), 1)
         )
 
@@ -1192,7 +1165,6 @@ final class BaseAPSManager: APSManager, Injectable {
             day: Decimal(oneDay_.hypos),
             week: Decimal(sevenDays_.hypos),
             month: Decimal(thirtyDays_.hypos),
-            ninetyDays: Decimal(ninetyDays_.hypos),
             total: Decimal(totalDays_.hypos)
         )
 
@@ -1200,7 +1172,6 @@ final class BaseAPSManager: APSManager, Injectable {
             day: Decimal(oneDay_.hypers),
             week: Decimal(sevenDays_.hypers),
             month: Decimal(thirtyDays_.hypers),
-            ninetyDays: Decimal(ninetyDays_.hypers),
             total: Decimal(totalDays_.hypers)
         )
 
@@ -1210,7 +1181,6 @@ final class BaseAPSManager: APSManager, Injectable {
             day: roundDecimal(bg_1, 1),
             week: roundDecimal(bg_7, 1),
             month: roundDecimal(bg_30, 1),
-            ninetyDays: roundDecimal(bg_90, 1),
             total: roundDecimal(bg_total, 1)
         )
 
@@ -1231,7 +1201,6 @@ final class BaseAPSManager: APSManager, Injectable {
         var sumOfSquares_1: Decimal = 0
         var sumOfSquares_7: Decimal = 0
         var sumOfSquares_30: Decimal = 0
-        var sumOfSquares_90: Decimal = 0
 
         // Total
         for array in bgArray {
@@ -1257,12 +1226,6 @@ final class BaseAPSManager: APSManager, Injectable {
                 sumOfSquares_30 += pow(Decimal(array_30).asMmolL - bg_30, 2)
             } else { sumOfSquares_30 += pow(Decimal(array_30) - bg_30, 2) }
         }
-        // 90 days
-        for array_90 in bgArray_90_ {
-            if units == .mmolL {
-                sumOfSquares_90 += pow(Decimal(array_90).asMmolL - bg_90, 2)
-            } else { sumOfSquares_90 += pow(Decimal(array_90) - bg_90, 2) }
-        }
 
         // Standard deviation and Coefficient of variation
         var sd_total = 0.0
@@ -1273,8 +1236,6 @@ final class BaseAPSManager: APSManager, Injectable {
         var cv_7 = 0.0
         var sd_30 = 0.0
         var cv_30 = 0.0
-        var sd_90 = 0.0
-        var cv_90 = 0.0
 
         // Avoid division by zero
         if avgs.total < 1 || nr_bgs < 1 { sd_total = 0
@@ -1300,17 +1261,12 @@ final class BaseAPSManager: APSManager, Injectable {
             cv_30 = 0 } else { sd_30 = sqrt(Double(sumOfSquares_30 / nr_bgs_30))
             cv_30 = sd_30 / Double(bg_30) * 100
         }
-        if avgs.ninetyDays < 1 || nr_bgs_90 < 1 { sd_90 = 0
-            cv_90 = 0 } else { sd_90 = sqrt(Double(sumOfSquares_90 / nr_bgs_90))
-            cv_90 = sd_90 / Double(bg_90) * 100
-        }
 
         // Standard Deviations
         let standardDeviations = Durations(
             day: roundDecimal(Decimal(sd_1), 1),
             week: roundDecimal(Decimal(sd_7), 1),
             month: roundDecimal(Decimal(sd_30), 1),
-            ninetyDays: roundDecimal(Decimal(sd_90), 1),
             total: roundDecimal(Decimal(sd_total), 1)
         )
 
@@ -1319,7 +1275,6 @@ final class BaseAPSManager: APSManager, Injectable {
             day: roundDecimal(Decimal(cv_1), 1),
             week: roundDecimal(Decimal(cv_7), 1),
             month: roundDecimal(Decimal(cv_30), 1),
-            ninetyDays: roundDecimal(Decimal(cv_90), 1),
             total: roundDecimal(Decimal(cv_total), 1)
         )
 

+ 1 - 1
FreeAPS/Sources/APS/CGM/DexcomSource.swift

@@ -24,7 +24,7 @@ final class DexcomSource: GlucoseSource {
         return Future<[BloodGlucose], Error> { [weak self] promise in
             self?.promise = promise
         }
-        .timeout(60, scheduler: processQueue, options: nil, customError: nil)
+        .timeout(90, scheduler: processQueue, options: nil, customError: nil)
         .replaceError(with: [])
         .replaceEmpty(with: [])
         .eraseToAnyPublisher()

+ 1 - 1
FreeAPS/Sources/APS/DeviceDataManager.swift

@@ -163,7 +163,7 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
                 }
             }
         }
-        .timeout(20, scheduler: processQueue)
+        .timeout(30, scheduler: processQueue)
         .replaceError(with: false)
         .replaceEmpty(with: false)
         .sink(receiveValue: updateUpdateFinished)

+ 15 - 0
FreeAPS/Sources/APS/Storage/PumpHistoryStorage.swift

@@ -14,6 +14,7 @@ protocol PumpHistoryStorage {
     func recent() -> [PumpHistoryEvent]
     func nightscoutTretmentsNotUploaded() -> [NigtscoutTreatment]
     func saveCancelTempEvents()
+    func deleteInsulin(at date: Date)
 }
 
 final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
@@ -194,6 +195,20 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
         storage.retrieve(OpenAPS.Monitor.pumpHistory, as: [PumpHistoryEvent].self)?.reversed() ?? []
     }
 
+    func deleteInsulin(at date: Date) {
+        processQueue.sync {
+            var allValues = storage.retrieve(OpenAPS.Monitor.pumpHistory, as: [PumpHistoryEvent].self) ?? []
+            guard let entryIndex = allValues.firstIndex(where: { $0.timestamp == date }) else {
+                return
+            }
+            allValues.remove(at: entryIndex)
+            storage.save(allValues, as: OpenAPS.Monitor.pumpHistory)
+            broadcaster.notify(PumpHistoryObserver.self, on: processQueue) {
+                $0.pumpHistoryDidUpdate(allValues)
+            }
+        }
+    }
+
     func nightscoutTretmentsNotUploaded() -> [NigtscoutTreatment] {
         let events = recent()
         guard !events.isEmpty else { return [] }

File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings


File diff suppressed because it is too large
+ 9 - 3
FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings


File diff suppressed because it is too large
+ 11 - 5
FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings


File diff suppressed because it is too large
+ 11 - 5
FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings


File diff suppressed because it is too large
+ 39 - 33
FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings


File diff suppressed because it is too large
+ 8 - 2
FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings


+ 2 - 1
FreeAPS/Sources/Models/Statistics.swift

@@ -87,6 +87,7 @@ extension Statistics {
 struct LoopCycles: JSON, Equatable {
     var loops: Int
     var errors: Int
+    var readings: Int
     var success_rate: Decimal
     var avg_interval: Decimal
     var median_interval: Decimal
@@ -107,7 +108,6 @@ struct Durations: JSON, Equatable {
     var day: Decimal
     var week: Decimal
     var month: Decimal
-    var ninetyDays: Decimal
     var total: Decimal
 }
 
@@ -142,6 +142,7 @@ extension LoopCycles {
     private enum CodingKeys: String, CodingKey {
         case loops
         case errors
+        case readings
         case success_rate
         case avg_interval
         case median_interval

+ 1 - 0
FreeAPS/Sources/Modules/DataTable/DataTableDataFlow.swift

@@ -173,5 +173,6 @@ protocol DataTableProvider: Provider {
     func carbs() -> [CarbsEntry]
     func glucose() -> [BloodGlucose]
     func deleteCarbs(at date: Date)
+    func deleteInsulin(at date: Date)
     func deleteGlucose(id: String)
 }

+ 4 - 0
FreeAPS/Sources/Modules/DataTable/DataTableProvider.swift

@@ -25,6 +25,10 @@ extension DataTable {
             nightscoutManager.deleteCarbs(at: date)
         }
 
+        func deleteInsulin(at date: Date) {
+            nightscoutManager.deleteInsulin(at: date)
+        }
+
         func glucose() -> [BloodGlucose] {
             glucoseStorage.recent().sorted { $0.date > $1.date }
         }

+ 11 - 0
FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -3,6 +3,8 @@ import SwiftUI
 extension DataTable {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var broadcaster: Broadcaster!
+        @Injected() var unlockmanager: UnlockManager!
+
         @Published var mode: Mode = .treatments
         @Published var treatments: [Treatment] = []
         @Published var glucose: [Glucose] = []
@@ -92,6 +94,15 @@ extension DataTable {
             provider.deleteCarbs(at: date)
         }
 
+        func deleteInsulin(at date: Date) {
+            unlockmanager.unlock()
+                .sink { _ in } receiveValue: { [weak self] _ in
+                    guard let self = self else { return }
+                    self.provider.deleteInsulin(at: date)
+                }
+                .store(in: &lifetime)
+        }
+
         func deleteGlucose(at index: Int) {
             let id = glucose[index].id
             provider.deleteGlucose(id: id)

+ 26 - 0
FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -5,9 +5,13 @@ extension DataTable {
     struct RootView: BaseView {
         let resolver: Resolver
         @StateObject var state = StateModel()
+
         @State private var isRemoveCarbsAlertPresented = false
         @State private var removeCarbsAlert: Alert?
 
+        @State private var isRemoveInsulinAlertPresented = false
+        @State private var removeInsulinAlert: Alert?
+
         private var glucoseFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             formatter.numberStyle = .decimal
@@ -100,6 +104,28 @@ extension DataTable {
                             removeCarbsAlert!
                         }
                 }
+
+                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(at: item.date) }
+                                ),
+                                secondaryButton: .cancel()
+                            )
+                            isRemoveInsulinAlertPresented = true
+                        }
+                        .alert(isPresented: $isRemoveInsulinAlertPresented) {
+                            removeInsulinAlert!
+                        }
+                }
             }
         }
 

+ 0 - 1
FreeAPS/Sources/Modules/Home/DurationButton.swift

@@ -14,7 +14,6 @@ enum durationState: String, DurationButton {
     case day = "Past 24 Hours "
     case week = "Past Week "
     case month = "Past Month "
-    case ninetyDays = "Past 90 Days "
     case total = "All Past Days of Data "
 }
 

+ 47 - 16
FreeAPS/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift

@@ -27,6 +27,14 @@ struct CurrentGlucoseView: View {
         return formatter
     }
 
+    private var timaAgoFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        formatter.negativePrefix = ""
+        return formatter
+    }
+
     private var dateFormatter: DateFormatter {
         let formatter = DateFormatter()
         formatter.timeStyle = .short
@@ -34,8 +42,8 @@ struct CurrentGlucoseView: View {
     }
 
     var body: some View {
-        VStack(alignment: .center, spacing: 6) {
-            HStack(spacing: 8) {
+        VStack(alignment: .center) {
+            HStack {
                 Text(
                     recentGlucose?.glucose
                         .map {
@@ -43,24 +51,30 @@ struct CurrentGlucoseView: View {
                                 .string(from: Double(units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)! }
                         ?? "--"
                 )
-                .font(.system(size: 24, weight: .bold))
-                .fixedSize()
-                .foregroundColor(alarm == nil ? .primary : .loopRed)
-                image.padding(.bottom, 2)
+                .font(.title).fontWeight(.bold)
+                .foregroundColor(alarm == nil ? colorOfGlucose : .loopRed)
 
-            }.padding(.leading, 4)
-            HStack(alignment: .lastTextBaseline, spacing: 2) {
+                image
+            }
+            HStack {
+                let minutes = (recentGlucose?.dateString.timeIntervalSinceNow ?? 0) / 60
+                let text = timaAgoFormatter.string(for: Double(minutes)) ?? ""
                 Text(
-                    recentGlucose.map { dateFormatter.string(from: $0.dateString) } ?? "--"
-                ).font(.caption2).foregroundColor(.secondary)
+                    text == "0" ? "< 1 " + NSLocalizedString("min", comment: "Short form for minutes") : (
+                        text + " " +
+                            NSLocalizedString("min", comment: "Short form for minutes") + " "
+                    )
+                )
+                .font(.caption2).foregroundColor(.secondary)
+
                 Text(
                     delta
-                        .map { deltaFormatter.string(from: Double(units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)!
-                        } ??
-                        "--"
-
-                ).font(.system(size: 12, weight: .bold))
-            }
+                        .map {
+                            deltaFormatter.string(from: Double(units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)!
+                        } ?? "--"
+                )
+                .font(.caption2).foregroundColor(.secondary)
+            }.frame(alignment: .top)
         }
     }
 
@@ -91,4 +105,21 @@ struct CurrentGlucoseView: View {
             return Image(systemName: "arrow.left.and.right")
         }
     }
+
+    var colorOfGlucose: Color {
+        let whichGlucose = recentGlucose?.glucose ?? 0
+
+        switch whichGlucose {
+        case 71 ... 145:
+            return .loopGreen
+        case 1 ... 55,
+             217...:
+            return .loopRed
+        case 56 ... 70,
+             146 ... 216:
+            return .loopYellow
+        default:
+            return .primary
+        }
+    }
 }

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

@@ -21,13 +21,13 @@ struct LoopView: View {
         return formatter
     }
 
-    private let rect = CGRect(x: 0, y: 0, width: 32, height: 32)
+    private let rect = CGRect(x: 0, y: 0, width: 28, height: 28)
     var body: some View {
         VStack(alignment: .center) {
             ZStack {
                 Circle()
-                    .strokeBorder(color, lineWidth: 6)
-                    .frame(width: rect.width, height: rect.height)
+                    .strokeBorder(color, lineWidth: 5)
+                    .frame(width: rect.width, height: rect.height, alignment: .bottom)
                     .mask(mask(in: rect).fill(style: FillStyle(eoFill: true)))
                 if isLooping {
                     ProgressView()
@@ -51,7 +51,7 @@ struct LoopView: View {
         if minAgo > 1440 {
             return "--"
         }
-        return "\(minAgo) " + NSLocalizedString("min ago", comment: "Minutes ago since last loop")
+        return "\(minAgo) " + NSLocalizedString("min", comment: "Minutes ago since last loop")
     }
 
     private var color: Color {

+ 13 - 10
FreeAPS/Sources/Modules/Home/View/Header/PumpView.swift

@@ -27,28 +27,30 @@ struct PumpView: View {
                     Image(systemName: "drop.fill")
                         .resizable()
                         .aspectRatio(contentMode: .fit)
-                        .frame(height: 8)
+                        .frame(maxHeight: 10)
                         .foregroundColor(reservoirColor)
                     if reservoir == 0xDEAD_BEEF {
-                        Text("50+ " + NSLocalizedString("U", comment: "Insulin unit")).font(.system(size: 12, weight: .bold))
+                        Text("50+ " + NSLocalizedString("U", comment: "Insulin unit")).font(.footnote)
+                            .fontWeight(.bold)
                     } else {
                         Text(
                             reservoirFormatter
                                 .string(from: reservoir as NSNumber)! + NSLocalizedString(" U", comment: "Insulin unit")
                         )
-                        .font(.system(size: 12, weight: .bold))
+                        .font(.footnote).fontWeight(.bold)
                     }
-                }
+                }.frame(alignment: .top)
             }
             if let battery = battery, battery.display ?? false, expiresAtDate == nil {
                 HStack {
                     Image(systemName: "battery.100")
                         .resizable()
                         .aspectRatio(contentMode: .fit)
-                        .frame(height: 8)
+                        .frame(maxHeight: 10)
                         .foregroundColor(batteryColor)
-                    Text("\(Int(battery.percent ?? 100)) %").font(.system(size: 12, weight: .bold))
-                }
+                    Text("\(Int(battery.percent ?? 100)) %").font(.footnote)
+                        .fontWeight(.bold)
+                }.frame(alignment: .bottom)
             }
 
             if let date = expiresAtDate {
@@ -56,10 +58,11 @@ struct PumpView: View {
                     Image(systemName: "stopwatch.fill")
                         .resizable()
                         .aspectRatio(contentMode: .fit)
-                        .frame(height: 8)
+                        .frame(maxHeight: 10)
                         .foregroundColor(timerColor)
-                    Text(remainingTimeString(time: date.timeIntervalSince(timerDate))).font(.system(size: 12, weight: .bold))
-                }
+                    Text(remainingTimeString(time: date.timeIntervalSince(timerDate))).font(.footnote)
+                        .fontWeight(.bold)
+                }.frame(alignment: .bottom)
             }
         }
     }

+ 29 - 67
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -11,20 +11,19 @@ extension Home {
         @State var isStatusPopupPresented = false
         @State var selectedState: durationState
 
-        // Average/Median and CV/SD titles and values switches when you click them
+        // Average/Median/Readings and CV/SD titles and values switches when you tap them
         @State var averageOrMedianTitle = NSLocalizedString("Average", comment: "")
         @State var median_ = ""
         @State var average_ = ""
+        @State var readings = ""
+
         @State var averageOrmedian = ""
         @State var CV_or_SD_Title = NSLocalizedString("CV", comment: "CV")
         @State var cv_ = ""
         @State var sd_ = ""
         @State var CVorSD = ""
-        // Switch between Loops and Errors when tapping in ststPanel
+        // Switch between Loops and Errors when tapping in statPanel
         @State var loopStatTitle = NSLocalizedString("Loops", comment: "Nr of Loops in statPanel")
-        @State var loopsOrerrors = ""
-
-        public let paddingSpace: CGFloat = 15
 
         private var numberFormatter: NumberFormatter {
             let formatter = NumberFormatter()
@@ -73,29 +72,29 @@ extension Home {
                 Spacer()
             }
             .frame(maxWidth: .infinity)
-            .frame(maxHeight: 70)
             .padding(.top, geo.safeAreaInsets.top)
+            .padding(.bottom)
             .background(Color.gray.opacity(0.2))
         }
 
         var cobIobView: some View {
             VStack(alignment: .leading, spacing: 12) {
                 HStack {
-                    Text("IOB").font(.caption2).foregroundColor(.secondary)
+                    Text("IOB").font(.footnote).foregroundColor(.secondary)
                     Text(
                         (numberFormatter.string(from: (state.suggestion?.iob ?? 0) as NSNumber) ?? "0") +
                             NSLocalizedString(" U", comment: "Insulin unit")
                     )
-                    .font(.system(size: 12, weight: .bold))
-                }
+                    .font(.footnote).fontWeight(.bold)
+                }.frame(alignment: .top)
                 HStack {
-                    Text("COB").font(.caption2).foregroundColor(.secondary)
+                    Text("COB").font(.footnote).foregroundColor(.secondary)
                     Text(
                         (numberFormatter.string(from: (state.suggestion?.cob ?? 0) as NSNumber) ?? "0") +
                             NSLocalizedString(" g", comment: "gram of carbs")
                     )
-                    .font(.system(size: 12, weight: .bold))
-                }
+                    .font(.footnote).fontWeight(.bold)
+                }.frame(alignment: .bottom)
             }
         }
 
@@ -313,37 +312,6 @@ extension Home {
 
                         averageTIRhca1c(hba1c_all, average_, median_, tir_low, tir_high, tir_, hba1c_, sd_, cv_)
 
-                    case .ninetyDays:
-                        let hba1c_all = numberFormatter
-                            .string(from: (state.statistics?.Statistics.HbA1c.total ?? 0) as NSNumber) ?? ""
-                        let average_ = targetFormatter
-                            .string(from: (state.statistics?.Statistics.Glucose.Average.ninetyDays ?? 0) as NSNumber) ??
-                            ""
-                        let median_ = targetFormatter
-                            .string(from: (state.statistics?.Statistics.Glucose.Median.ninetyDays ?? 0) as NSNumber) ??
-                            ""
-                        let tir_low = tirFormatter
-                            .string(
-                                from: (state.statistics?.Statistics.Distribution.Hypos.ninetyDays ?? 0) as NSNumber
-                            ) ??
-                            ""
-                        let tir_high = tirFormatter
-                            .string(
-                                from: (state.statistics?.Statistics.Distribution.Hypers.ninetyDays ?? 0) as NSNumber
-                            ) ??
-                            ""
-                        let tir_ = tirFormatter
-                            .string(from: (state.statistics?.Statistics.Distribution.TIR.ninetyDays ?? 0) as NSNumber) ??
-                            ""
-                        let hba1c_ = numberFormatter
-                            .string(from: (state.statistics?.Statistics.HbA1c.ninetyDays ?? 0) as NSNumber) ?? ""
-                        let sd_ = numberFormatter
-                            .string(from: (state.statistics?.Statistics.Variance.SD.ninetyDays ?? 0) as NSNumber) ?? ""
-                        let cv_ = tirFormatter
-                            .string(from: (state.statistics?.Statistics.Variance.CV.ninetyDays ?? 0) as NSNumber) ?? ""
-
-                        averageTIRhca1c(hba1c_all, average_, median_, tir_low, tir_high, tir_, hba1c_, sd_, cv_)
-
                     case .total:
                         let hba1c_all = numberFormatter
                             .string(from: (state.statistics?.Statistics.HbA1c.total ?? 0) as NSNumber) ?? ""
@@ -403,19 +371,30 @@ extension Home {
                     // Average as default. Changes to Median when clicking.
                     let textAverageTitle = NSLocalizedString("Average", comment: "")
                     let textMedianTitle = NSLocalizedString("Median", comment: "")
+                    let cgmReadingsTitle = NSLocalizedString("Readings", comment: "CGM readings in statPanel")
 
                     HStack {
                         Text(averageOrMedianTitle).font(.footnote).foregroundColor(.secondary)
                         if averageOrMedianTitle == textAverageTitle {
                             Text(averageOrmedian == "" ? average_ : average_).font(.footnote)
-                        } else {
+                        } else if averageOrMedianTitle == textMedianTitle {
                             Text(averageOrmedian == "" ? median_ : median_).font(.footnote)
+                        } else if averageOrMedianTitle == cgmReadingsTitle {
+                            Text(
+                                averageOrmedian != "0" ? tirFormatter
+                                    .string(from: (state.statistics?.Statistics.LoopCycles.readings ?? 0) as NSNumber) ?? "" : ""
+                            )
+                            .font(.footnote)
                         }
                     }.onTapGesture {
                         if averageOrMedianTitle == textAverageTitle {
                             averageOrMedianTitle = textMedianTitle
                             averageOrmedian = median_
-                        } else {
+                        } else if averageOrMedianTitle == textMedianTitle {
+                            averageOrMedianTitle = cgmReadingsTitle
+                            averageOrmedian = tirFormatter
+                                .string(from: (state.statistics?.Statistics.LoopCycles.readings ?? 0) as NSNumber) ?? ""
+                        } else if averageOrMedianTitle == cgmReadingsTitle {
                             averageOrMedianTitle = textAverageTitle
                             averageOrmedian = average_
                         }
@@ -480,19 +459,16 @@ extension Home {
                         HStack {
                             Text(loopStatTitle).font(.footnote).foregroundColor(.secondary)
                             Text(
-                                loopsOrerrors == "" ? tirFormatter
+                                loopStatTitle == loopTitle ? tirFormatter
                                     .string(from: (state.statistics?.Statistics.LoopCycles.loops ?? 0) as NSNumber) ?? "" :
-                                    loopsOrerrors
+                                    tirFormatter
+                                    .string(from: (state.statistics?.Statistics.LoopCycles.errors ?? 0) as NSNumber) ?? ""
                             ).font(.footnote)
                         }.onTapGesture {
                             if loopStatTitle == loopTitle {
                                 loopStatTitle = errorTitle
-                                loopsOrerrors = tirFormatter
-                                    .string(from: (state.statistics?.Statistics.LoopCycles.errors ?? 0) as NSNumber) ?? ""
                             } else if loopStatTitle == errorTitle {
                                 loopStatTitle = loopTitle
-                                loopsOrerrors = tirFormatter
-                                    .string(from: (state.statistics?.Statistics.LoopCycles.loops ?? 0) as NSNumber) ?? ""
                             }
                         }
 
@@ -609,7 +585,7 @@ extension Home {
                                 .renderingMode(.template)
                                 .resizable()
                                 .frame(width: 24, height: 24)
-                                .foregroundColor(.loopGreen)
+                                .foregroundColor(.loopYellow)
                                 .padding(8)
                             if let carbsReq = state.carbsRequired {
                                 Text(numberFormatter.string(from: carbsReq as NSNumber)!)
@@ -628,7 +604,7 @@ extension Home {
                             .resizable()
                             .frame(width: 24, height: 24)
                             .padding(8)
-                    }.foregroundColor(.loopYellow)
+                    }.foregroundColor(.loopGreen)
                     Spacer()
                     Button { state.showModal(for: .bolus(waitForSuggestion: false)) }
                     label: {
@@ -724,19 +700,5 @@ extension Home {
                 }
             }
         }
-
-        private func colorOfGlucose(_ glucose: Decimal) -> Color {
-            switch glucose {
-            case 4 ... 8,
-                 30 ... 46,
-                 72 ... 144:
-                return .loopGreen
-            case 0 ... 4,
-                 20 ... 71:
-                return .loopRed
-            default:
-                return .loopYellow
-            }
-        }
     }
 }

File diff suppressed because it is too large
+ 1 - 1
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorStateModel.swift


+ 29 - 0
FreeAPS/Sources/Services/Network/NightscoutAPI.swift

@@ -166,6 +166,35 @@ extension NightscoutAPI {
             .eraseToAnyPublisher()
     }
 
+    func deleteInsulin(at date: Date) -> AnyPublisher<Void, Swift.Error> {
+        var components = URLComponents()
+        components.scheme = url.scheme
+        components.host = url.host
+        components.port = url.port
+        components.path = Config.treatmentsPath
+        components.queryItems = [
+            URLQueryItem(name: "find[bolus][$exists]", value: "true"),
+            URLQueryItem(
+                name: "find[created_at][$eq]",
+                value: Formatter.iso8601withFractionalSeconds.string(from: date)
+            )
+        ]
+
+        var request = URLRequest(url: components.url!)
+        request.allowsConstrainedNetworkAccess = false
+        request.timeoutInterval = Config.timeout
+        request.httpMethod = "DELETE"
+
+        if let secret = secret {
+            request.addValue(secret.sha1(), forHTTPHeaderField: "api-secret")
+        }
+
+        return service.run(request)
+            .retry(Config.retryCount)
+            .map { _ in () }
+            .eraseToAnyPublisher()
+    }
+
     func fetchTempTargets(sinceDate: Date? = nil) -> AnyPublisher<[TempTarget], Swift.Error> {
         var components = URLComponents()
         components.scheme = url.scheme

+ 20 - 0
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -9,6 +9,7 @@ protocol NightscoutManager: GlucoseSource {
     func fetchTempTargets() -> AnyPublisher<[TempTarget], Never>
     func fetchAnnouncements() -> AnyPublisher<[Announcement], Never>
     func deleteCarbs(at date: Date)
+    func deleteInsulin(at date: Date)
     func uploadStatus()
     func uploadStatistics(dailystat: Statistics)
     func uploadPreferences()
@@ -180,6 +181,25 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
             .store(in: &lifetime)
     }
 
+    func deleteInsulin(at date: Date) {
+        guard let nightscout = nightscoutAPI, isUploadEnabled else {
+            pumpHistoryStorage.deleteInsulin(at: date)
+            return
+        }
+
+        nightscout.deleteInsulin(at: date)
+            .sink { completion in
+                switch completion {
+                case .finished:
+                    self.pumpHistoryStorage.deleteInsulin(at: date)
+                    debug(.nightscout, "Carbs deleted")
+                case let .failure(error):
+                    debug(.nightscout, error.localizedDescription)
+                }
+            } receiveValue: {}
+            .store(in: &lifetime)
+    }
+
     func uploadStatistics(dailystat: Statistics) {
         let stats = NightscoutStatistics(
             dailystats: dailystat