Sfoglia il codice sorgente

Merge branch 'dev' into meal_settings

Mike Plante 2 anni fa
parent
commit
ca55d2e379

+ 3 - 4
FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json

@@ -32,15 +32,14 @@
   "smoothGlucose" : false,
   "displayOnWatch" : "BGTarget",
   "overrideHbA1cUnit" : false,
-  "high" : 145,
+  "high" : 180,
   "low" : 70,
-  "uploadStats" : true,
   "hours" : 6,
   "xGridLines" : true,
   "yGridLines" : true,
   "oneDimensionalGraph" : false,
-  "rulerMarks" : false,
-  "maxCarbs": 1000,
+  "rulerMarks" : true,
+  "maxCarbs": 250,
   "displayFatAndProteinOnWatch": false,
   "lockScreenView": "simple"
 }

+ 2 - 2
FreeAPS/Resources/json/defaults/preferences.json

@@ -8,8 +8,8 @@
   "rewind_resets_autosens" : true,
   "high_temptarget_raises_sensitivity" : false,
   "low_temptarget_lowers_sensitivity" : false,
-  "sensitivity_raises_target" : true,
-  "resistanceLowersTarget" : false,
+  "sensitivity_raises_target" : false,
+  "resistance_lowers_target" : false,
   "adv_target_adjustments" : false,
   "exercise_mode" : false,
   "half_basal_exercise_target" : 160,

+ 0 - 302
FreeAPS/Sources/APS/APSManager.swift

@@ -721,7 +721,6 @@ final class BaseAPSManager: APSManager, Injectable {
                 }
             }
             nightscout.uploadStatus()
-            statistics()
         }
     }
 
@@ -879,307 +878,6 @@ final class BaseAPSManager: APSManager, Injectable {
         return output
     }
 
-    // Add to statistics.JSON for upload to NS.
-    private func statistics() {
-        let now = Date()
-        if settingsManager.settings.uploadStats {
-            let hour = Calendar.current.component(.hour, from: now)
-            guard hour > 20 else {
-                return
-            }
-            coredataContext.performAndWait { [self] in
-                var stats = [StatsData]()
-                let requestStats = StatsData.fetchRequest() as NSFetchRequest<StatsData>
-                let sortStats = NSSortDescriptor(key: "lastrun", ascending: false)
-                requestStats.sortDescriptors = [sortStats]
-                requestStats.fetchLimit = 1
-                try? stats = coredataContext.fetch(requestStats)
-                // Only save and upload once per day
-                guard (-1 * (stats.first?.lastrun ?? .distantPast).timeIntervalSinceNow.hours) > 22 else { return }
-
-                let units = self.settingsManager.settings.units
-                let preferences = settingsManager.preferences
-
-                // Carbs
-                var carbs = [Carbohydrates]()
-                var carbTotal: Decimal = 0
-                let requestCarbs = Carbohydrates.fetchRequest() as NSFetchRequest<Carbohydrates>
-                let daysAgo = Date().addingTimeInterval(-1.days.timeInterval)
-                requestCarbs.predicate = NSPredicate(format: "carbs > 0 AND date > %@", daysAgo as NSDate)
-                let sortCarbs = NSSortDescriptor(key: "date", ascending: true)
-                requestCarbs.sortDescriptors = [sortCarbs]
-                try? carbs = coredataContext.fetch(requestCarbs)
-                carbTotal = carbs.map({ carbs in carbs.carbs as? Decimal ?? 0 }).reduce(0, +)
-
-                // TDD
-                var tdds = [TDD]()
-                var currentTDD: Decimal = 0
-                var tddTotalAverage: Decimal = 0
-                let requestTDD = TDD.fetchRequest() as NSFetchRequest<TDD>
-                let sort = NSSortDescriptor(key: "timestamp", ascending: false)
-                let daysOf14Ago = Date().addingTimeInterval(-14.days.timeInterval)
-                requestTDD.predicate = NSPredicate(format: "timestamp > %@", daysOf14Ago as NSDate)
-                requestTDD.sortDescriptors = [sort]
-                try? tdds = coredataContext.fetch(requestTDD)
-
-                if !tdds.isEmpty {
-                    currentTDD = tdds[0].tdd?.decimalValue ?? 0
-                    let tddArray = tdds.compactMap({ insulin in insulin.tdd as? Decimal ?? 0 })
-                    tddTotalAverage = tddArray.reduce(0, +) / Decimal(tddArray.count)
-                }
-
-                var algo_ = "Oref0"
-
-                if preferences.sigmoid, preferences.enableDynamicCR {
-                    algo_ = "Dynamic ISF + CR: Sigmoid"
-                } else if preferences.sigmoid, !preferences.enableDynamicCR {
-                    algo_ = "Dynamic ISF: Sigmoid"
-                } else if preferences.useNewFormula, preferences.enableDynamicCR {
-                    algo_ = "Dynamic ISF + CR: Logarithmic"
-                } else if preferences.useNewFormula, !preferences.sigmoid,!preferences.enableDynamicCR {
-                    algo_ = "Dynamic ISF: Logarithmic"
-                }
-                let af = preferences.sigmoid ? preferences.adjustmentFactorSigmoid : preferences.adjustmentFactor
-                let insulin_type = preferences.curve
-                let buildDate = BuildDetails.default.buildDate() ?? Date()
-                let version = Bundle.main.releaseVersionNumber
-                let build = Bundle.main.buildVersionNumber
-
-                let branch = BuildDetails.default.branchAndSha
-
-                let copyrightNotice_ = Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String ?? ""
-                let pump_ = pumpManager?.localizedTitle ?? ""
-                let cgm = settingsManager.settings.cgm
-                let file = OpenAPS.Monitor.statistics
-                var iPa: Decimal = 75
-                if preferences.useCustomPeakTime {
-                    iPa = preferences.insulinPeakTime
-                } else if preferences.curve.rawValue == "rapid-acting" {
-                    iPa = 65
-                } else if preferences.curve.rawValue == "ultra-rapid" {
-                    iPa = 50
-                }
-                // CGM Readings
-                var glucose_24 = [Readings]() // Day
-                var glucose_7 = [Readings]() // Week
-                var glucose_30 = [Readings]() // Month
-                var glucose = [Readings]() // Total
-                let filter = DateFilter()
-                // 24h
-                let requestGFS_24 = Readings.fetchRequest() as NSFetchRequest<Readings>
-                let sortGlucose_24 = NSSortDescriptor(key: "date", ascending: false)
-                requestGFS_24.predicate = NSPredicate(format: "glucose > 0 AND date > %@", filter.day)
-                requestGFS_24.sortDescriptors = [sortGlucose_24]
-                try? glucose_24 = coredataContext.fetch(requestGFS_24)
-                // Week
-                let requestGFS_7 = Readings.fetchRequest() as NSFetchRequest<Readings>
-                let sortGlucose_7 = NSSortDescriptor(key: "date", ascending: false)
-                requestGFS_7.predicate = NSPredicate(format: "glucose > 0 AND date > %@", filter.week)
-                requestGFS_7.sortDescriptors = [sortGlucose_7]
-                try? glucose_7 = coredataContext.fetch(requestGFS_7)
-                // Month
-                let requestGFS_30 = Readings.fetchRequest() as NSFetchRequest<Readings>
-                let sortGlucose_30 = NSSortDescriptor(key: "date", ascending: false)
-                requestGFS_30.predicate = NSPredicate(format: "glucose > 0 AND date > %@", filter.month)
-                requestGFS_30.sortDescriptors = [sortGlucose_30]
-                try? glucose_30 = coredataContext.fetch(requestGFS_30)
-                // Total
-                let requestGFS = Readings.fetchRequest() as NSFetchRequest<Readings>
-                let sortGlucose = NSSortDescriptor(key: "date", ascending: false)
-                requestGFS.predicate = NSPredicate(format: "glucose > 0 AND date > %@", filter.total)
-                requestGFS.sortDescriptors = [sortGlucose]
-                try? glucose = coredataContext.fetch(requestGFS)
-
-                // First date
-                let previous = glucose.last?.date ?? Date()
-                // Last date (recent)
-                let current = glucose.first?.date ?? Date()
-                // Total time in days
-                let numberOfDays = (current - previous).timeInterval / 8.64E4
-
-                // Get glucose computations for every case
-                let oneDayGlucose = glucoseStats(glucose_24)
-                let sevenDaysGlucose = glucoseStats(glucose_7)
-                let thirtyDaysGlucose = glucoseStats(glucose_30)
-                let totalDaysGlucose = glucoseStats(glucose)
-
-                let median = Durations(
-                    day: roundDecimal(Decimal(oneDayGlucose.median), 1),
-                    week: roundDecimal(Decimal(sevenDaysGlucose.median), 1),
-                    month: roundDecimal(Decimal(thirtyDaysGlucose.median), 1),
-                    total: roundDecimal(Decimal(totalDaysGlucose.median), 1)
-                )
-
-                let overrideHbA1cUnit = settingsManager.settings.overrideHbA1cUnit
-
-                let hbs = Durations(
-                    day: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
-                        roundDecimal(Decimal(oneDayGlucose.ifcc), 1) : roundDecimal(Decimal(oneDayGlucose.ngsp), 1),
-                    week: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
-                        roundDecimal(Decimal(sevenDaysGlucose.ifcc), 1) : roundDecimal(Decimal(sevenDaysGlucose.ngsp), 1),
-                    month: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
-                        roundDecimal(Decimal(thirtyDaysGlucose.ifcc), 1) : roundDecimal(Decimal(thirtyDaysGlucose.ngsp), 1),
-                    total: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
-                        roundDecimal(Decimal(totalDaysGlucose.ifcc), 1) : roundDecimal(Decimal(totalDaysGlucose.ngsp), 1)
-                )
-
-                var oneDay_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
-                var sevenDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
-                var thirtyDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
-                var totalDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
-                // Get TIR computations for every case
-                oneDay_ = tir(glucose_24)
-                sevenDays_ = tir(glucose_7)
-                thirtyDays_ = tir(glucose_30)
-                totalDays_ = tir(glucose)
-
-                let tir = Durations(
-                    day: roundDecimal(Decimal(oneDay_.TIR), 1),
-                    week: roundDecimal(Decimal(sevenDays_.TIR), 1),
-                    month: roundDecimal(Decimal(thirtyDays_.TIR), 1),
-                    total: roundDecimal(Decimal(totalDays_.TIR), 1)
-                )
-                let hypo = Durations(
-                    day: Decimal(oneDay_.hypos),
-                    week: Decimal(sevenDays_.hypos),
-                    month: Decimal(thirtyDays_.hypos),
-                    total: Decimal(totalDays_.hypos)
-                )
-                let hyper = Durations(
-                    day: Decimal(oneDay_.hypers),
-                    week: Decimal(sevenDays_.hypers),
-                    month: Decimal(thirtyDays_.hypers),
-                    total: Decimal(totalDays_.hypers)
-                )
-                let normal = Durations(
-                    day: Decimal(oneDay_.normal_),
-                    week: Decimal(sevenDays_.normal_),
-                    month: Decimal(thirtyDays_.normal_),
-                    total: Decimal(totalDays_.normal_)
-                )
-                let range = Threshold(
-                    low: units == .mmolL ? roundDecimal(settingsManager.settings.low.asMmolL, 1) :
-                        roundDecimal(settingsManager.settings.low, 0),
-                    high: units == .mmolL ? roundDecimal(settingsManager.settings.high.asMmolL, 1) :
-                        roundDecimal(settingsManager.settings.high, 0)
-                )
-                let TimeInRange = TIRs(
-                    TIR: tir,
-                    Hypos: hypo,
-                    Hypers: hyper,
-                    Threshold: range,
-                    Euglycemic: normal
-                )
-                let avgs = Durations(
-                    day: roundDecimal(Decimal(oneDayGlucose.average), 1),
-                    week: roundDecimal(Decimal(sevenDaysGlucose.average), 1),
-                    month: roundDecimal(Decimal(thirtyDaysGlucose.average), 1),
-                    total: roundDecimal(Decimal(totalDaysGlucose.average), 1)
-                )
-                let avg = Averages(Average: avgs, Median: median)
-                // Standard Deviations
-                let standardDeviations = Durations(
-                    day: roundDecimal(Decimal(oneDayGlucose.sd), 1),
-                    week: roundDecimal(Decimal(sevenDaysGlucose.sd), 1),
-                    month: roundDecimal(Decimal(thirtyDaysGlucose.sd), 1),
-                    total: roundDecimal(Decimal(totalDaysGlucose.sd), 1)
-                )
-                // CV = standard deviation / sample mean x 100
-                let cvs = Durations(
-                    day: roundDecimal(Decimal(oneDayGlucose.cv), 1),
-                    week: roundDecimal(Decimal(sevenDaysGlucose.cv), 1),
-                    month: roundDecimal(Decimal(thirtyDaysGlucose.cv), 1),
-                    total: roundDecimal(Decimal(totalDaysGlucose.cv), 1)
-                )
-                let variance = Variance(SD: standardDeviations, CV: cvs)
-
-                // Loops
-                var lsr = [LoopStatRecord]()
-                let requestLSR = LoopStatRecord.fetchRequest() as NSFetchRequest<LoopStatRecord>
-                requestLSR.predicate = NSPredicate(
-                    format: "interval > 0 AND start > %@",
-                    Date().addingTimeInterval(-24.hours.timeInterval) as NSDate
-                )
-                let sortLSR = NSSortDescriptor(key: "start", ascending: false)
-                requestLSR.sortDescriptors = [sortLSR]
-                try? lsr = coredataContext.fetch(requestLSR)
-                // Compute LoopStats for 24 hours
-                let oneDayLoops = loops(lsr)
-                let loopstat = LoopCycles(
-                    loops: oneDayLoops.loops,
-                    errors: oneDayLoops.errors,
-                    readings: Int(oneDayGlucose.readings),
-                    success_rate: oneDayLoops.success_rate,
-                    avg_interval: oneDayLoops.avg_interval,
-                    median_interval: oneDayLoops.median_interval,
-                    min_interval: oneDayLoops.min_interval,
-                    max_interval: oneDayLoops.max_interval,
-                    avg_duration: oneDayLoops.avg_duration,
-                    median_duration: oneDayLoops.median_duration,
-                    min_duration: oneDayLoops.max_duration,
-                    max_duration: oneDayLoops.max_duration
-                )
-
-                // Insulin
-                var insulinDistribution = [InsulinDistribution]()
-                var insulin = Ins(
-                    TDD: 0,
-                    bolus: 0,
-                    temp_basal: 0,
-                    scheduled_basal: 0,
-                    total_average: 0
-                )
-                let requestInsulinDistribution = InsulinDistribution.fetchRequest() as NSFetchRequest<InsulinDistribution>
-                let sortInsulin = NSSortDescriptor(key: "date", ascending: false)
-                requestInsulinDistribution.sortDescriptors = [sortInsulin]
-                try? insulinDistribution = coredataContext.fetch(requestInsulinDistribution)
-                insulin = Ins(
-                    TDD: roundDecimal(currentTDD, 2),
-                    bolus: insulinDistribution.first != nil ? ((insulinDistribution.first?.bolus ?? 0) as Decimal) : 0,
-                    temp_basal: insulinDistribution.first != nil ? ((insulinDistribution.first?.tempBasal ?? 0) as Decimal) : 0,
-                    scheduled_basal: insulinDistribution
-                        .first != nil ? ((insulinDistribution.first?.scheduledBasal ?? 0) as Decimal) : 0,
-                    total_average: roundDecimal(tddTotalAverage, 1)
-                )
-
-                let hbA1cUnit = !overrideHbA1cUnit ? (units == .mmolL ? "mmol/mol" : "%") : (units == .mmolL ? "%" : "mmol/mol")
-
-                let dailystat = Statistics(
-                    created_at: Date(),
-                    iPhone: UIDevice.current.getDeviceId,
-                    iOS: UIDevice.current.getOSInfo,
-                    Build_Version: version ?? "",
-                    Build_Number: build ?? "1",
-                    Branch: branch,
-                    CopyRightNotice: String(copyrightNotice_.prefix(32)),
-                    Build_Date: buildDate,
-                    Algorithm: algo_,
-                    AdjustmentFactor: af,
-                    Pump: pump_,
-                    CGM: cgm.rawValue,
-                    insulinType: insulin_type.rawValue,
-                    peakActivityTime: iPa,
-                    Carbs_24h: carbTotal,
-                    GlucoseStorage_Days: Decimal(roundDouble(numberOfDays, 1)),
-                    Statistics: Stats(
-                        Distribution: TimeInRange,
-                        Glucose: avg,
-                        HbA1c: hbs, Units: Units(Glucose: units.rawValue, HbA1c: hbA1cUnit),
-                        LoopCycles: loopstat,
-                        Insulin: insulin,
-                        Variance: variance
-                    )
-                )
-                storage.save(dailystat, as: file)
-                nightscout.uploadStatistics(dailystat: dailystat)
-
-                let saveStatsCoreData = StatsData(context: self.coredataContext)
-                saveStatsCoreData.lastrun = Date()
-                try? self.coredataContext.save()
-            }
-        }
-    }
-
     private func loopStats(loopStatRecord: LoopStats) {
         coredataContext.perform {
             let nLS = LoopStatRecord(context: self.coredataContext)

+ 3 - 8
FreeAPS/Sources/Models/FreeAPSSettings.swift

@@ -33,15 +33,14 @@ struct FreeAPSSettings: JSON, Equatable {
     var smoothGlucose: Bool = false
     var displayOnWatch: AwConfig = .BGTarget
     var overrideHbA1cUnit: Bool = false
-    var high: Decimal = 145
+    var high: Decimal = 180
     var low: Decimal = 70
-    var uploadStats: Bool = true
     var hours: Int = 6
     var xGridLines: Bool = true
     var yGridLines: Bool = true
     var oneDimensionalGraph: Bool = false
-    var rulerMarks: Bool = false
-    var maxCarbs: Decimal = 1000
+    var rulerMarks: Bool = true
+    var maxCarbs: Decimal = 250
     var displayFatAndProteinOnWatch: Bool = false
     var onlyAutotuneBasals: Bool = false
     var useLiveActivity: Bool = false
@@ -191,10 +190,6 @@ extension FreeAPSSettings: Decodable {
             settings.high = high
         }
 
-        if let uploadStats = try? container.decode(Bool.self, forKey: .uploadStats) {
-            settings.uploadStats = uploadStats
-        }
-
         if let hours = try? container.decode(Int.self, forKey: .hours) {
             settings.hours = hours
         }

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

@@ -3,6 +3,8 @@ import Foundation
 func determineBolusEventType(for event: PumpHistoryEvent) -> EventType {
     if event.isExternalInsulin ?? false {
         return .nsExternalInsulin
+    } else if event.isSMB ?? false {
+        return .smb
     }
     return event.type
 }

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

@@ -10,7 +10,7 @@ struct Preferences: JSON {
     var rewindResetsAutosens: Bool = true
     var highTemptargetRaisesSensitivity: Bool = false
     var lowTemptargetLowersSensitivity: Bool = false
-    var sensitivityRaisesTarget: Bool = true
+    var sensitivityRaisesTarget: Bool = false
     var resistanceLowersTarget: Bool = false
     var advTargetAdjustments: Bool = false
     var exerciseMode: Bool = false
@@ -68,7 +68,7 @@ extension Preferences {
         case highTemptargetRaisesSensitivity = "high_temptarget_raises_sensitivity"
         case lowTemptargetLowersSensitivity = "low_temptarget_lowers_sensitivity"
         case sensitivityRaisesTarget = "sensitivity_raises_target"
-        case resistanceLowersTarget
+        case resistanceLowersTarget = "resistance_lowers_target"
         case advTargetAdjustments = "adv_target_adjustments"
         case exerciseMode = "exercise_mode"
         case halfBasalExerciseTarget = "half_basal_exercise_target"

+ 1 - 0
FreeAPS/Sources/Models/PumpHistoryEvent.swift

@@ -46,6 +46,7 @@ struct PumpHistoryEvent: JSON, Equatable {
 
 enum EventType: String, JSON {
     case bolus = "Bolus"
+    case smb = "SMB"
     case mealBolus = "Meal Bolus"
     case correctionBolus = "Correction Bolus"
     case snackBolus = "Snack Bolus"

+ 2 - 1
FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift

@@ -80,10 +80,11 @@ extension Bolus {
                         Button { state.add() }
                         label: {
                             Text(
-                                state.amount < state.maxBolus ? NSLocalizedString("Enact bolus", comment: "") :
+                                state.amount <= state.maxBolus ? NSLocalizedString("Enact bolus", comment: "") :
                                     NSLocalizedString("Max Bolus exceeded!", comment: "")
                                     + " (>"
                                     + formatter.string(from: state.maxBolus as NSNumber)!
+                                    + NSLocalizedString("U", comment: "Insulin unit")
                                     + ")"
                             ) }
                             .disabled(

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

@@ -141,8 +141,6 @@ enum DataTable {
 
                 if isExternal ?? false {
                     bolusText += " " + NSLocalizedString("External", comment: "External Insulin")
-                } else if isSMB ?? false {
-                    bolusText += " " + NSLocalizedString("SMB", comment: "SMB")
                 }
 
                 return numberFormatter

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

@@ -13,7 +13,7 @@ extension DataTable {
         @Published var mode: Mode = .treatments
         @Published var treatments: [Treatment] = []
         @Published var glucose: [Glucose] = []
-        @Published var manualGlcuose: Decimal = 0
+        @Published var manualGlucose: Decimal = 0
         @Published var maxBolus: Decimal = 0
         @Published var externalInsulinAmount: Decimal = 0
         @Published var externalInsulinDate = Date()
@@ -156,8 +156,8 @@ 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>
@@ -181,8 +181,8 @@ extension DataTable {
             // try? coredataContext.save()
         }
 
-        func addManualGlucose() {
-            let glucose = units == .mmolL ? manualGlcuose.asMgdL : manualGlcuose
+        func logManualGlucose() {
+            let glucose = units == .mmolL ? manualGlucose.asMgdL : manualGlucose
             let now = Date()
             let id = UUID().uuidString
 
@@ -201,7 +201,7 @@ extension DataTable {
             debug(.default, "Manual Glucose saved to glucose.json")
         }
 
-        func addExternalInsulin() {
+        func logExternalInsulin() {
             guard externalInsulinAmount > 0 else {
                 showModal(for: nil)
                 return

+ 190 - 144
FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -7,11 +7,12 @@ extension DataTable {
         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?
-        @State private var newGlucose = false
+        @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 showManualGlucose = false
         @State private var showExternalInsulin = false
         @State private var isAmountUnconfirmed = true
 
@@ -64,7 +65,7 @@ extension DataTable {
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarItems(
                 leading: Button("Close", action: state.hideModal),
-                trailing: state.mode == .glucose ? EditButton().asAny() : EmptyView().asAny()
+                trailing: state.mode == .glucose ? logGlucoseButton.asAny() : logInsulinButton.asAny()
             )
             .sheet(isPresented: $showExternalInsulin, onDismiss: {
                 if isAmountUnconfirmed {
@@ -72,57 +73,40 @@ extension DataTable {
                     state.externalInsulinDate = Date()
                 }
             }) {
-                addExternalInsulinView
+                logExternalInsulinView
             }
-            .popup(isPresented: newGlucose, alignment: .top, direction: .bottom) {
-                VStack(spacing: 20) {
-                    HStack {
-                        Text("New Glucose")
-                        DecimalTextField(" ... ", value: $state.manualGlcuose, formatter: glucoseFormatter)
-                        Text(state.units.rawValue)
-                    }.padding(.horizontal, 20)
-                    HStack {
-                        let limitLow: Decimal = state.units == .mmolL ? 2.2 : 40
-                        let limitHigh: Decimal = state.units == .mmolL ? 21 : 380
-                        Button { newGlucose = false }
-                        label: { Text("Cancel") }.frame(maxWidth: .infinity, alignment: .leading)
+            .sheet(isPresented: $showManualGlucose) {
+                logGlucoseView
+            }
+        }
 
-                        Button {
-                            state.addManualGlucose()
-                            newGlucose = false
-                        }
-                        label: { Text("Save") }
-                            .frame(maxWidth: .infinity, alignment: .trailing)
-                        // .disabled(state.manualGlcuose < limitLow || state.manualGlcuose > limitHigh)
+        private var logInsulinButton: some View {
+            Button(action: { showExternalInsulin = true
+                state.externalInsulinDate = Date() }, label: {
+                Text("Log Insulin")
+                    .foregroundColor(Color.accentColor)
+                Image(systemName: "plus")
+                    .foregroundColor(Color.accentColor)
+            }).buttonStyle(.borderless)
+        }
 
-                    }.padding(20)
+        private var logGlucoseButton: some View {
+            Button(
+                action: {
+                    showManualGlucose = true
+                    state.manualGlucose = 0
+                },
+                label: {
+                    Text("Log Glucose")
+                        .foregroundColor(Color.accentColor)
+                    Image(systemName: "plus")
+                        .foregroundColor(Color.accentColor)
                 }
-                .frame(maxHeight: 140)
-                .background(
-                    RoundedRectangle(cornerRadius: 8, style: .continuous)
-                        .fill(Color(colorScheme == .dark ? UIColor.systemGray2 : UIColor.systemGray6))
-                )
-            }
+            ).buttonStyle(.borderless)
         }
 
         private var treatmentsList: some View {
             List {
-                HStack {
-                    Spacer()
-                    Button(action: { showExternalInsulin = true
-                        state.externalInsulinDate = Date() }, label: {
-                        HStack {
-                            Text("Add")
-                                .foregroundColor(Color.secondary)
-                                .font(.caption)
-
-                            Image(systemName: "syringe")
-                                .foregroundColor(Color.accentColor)
-                        }.frame(maxWidth: .infinity, alignment: .trailing)
-
-                    }).buttonStyle(.borderless)
-                }
-
                 if !state.treatments.isEmpty {
                     ForEach(state.treatments) { item in
                         treatmentView(item)
@@ -137,100 +121,131 @@ extension DataTable {
 
         private var glucoseList: some View {
             List {
-                Button { newGlucose = true }
-                label: { Text("Add") }.frame(maxWidth: .infinity, alignment: .trailing)
-                    .padding(.trailing, 20)
+                if !state.glucose.isEmpty {
+                    ForEach(state.glucose) { item in
+                        glucoseView(item)
+                    }
+                } else {
+                    HStack {
+                        Text(NSLocalizedString("No data.", comment: "No data text when no entries in history list"))
+                    }
+                }
+            }
+        }
 
-                ForEach(state.glucose) { item in
-                    glucoseView(item)
-                }.onDelete(perform: deleteGlucose)
+        private var logGlucoseView: some View {
+            NavigationView {
+                VStack {
+                    Form {
+                        Section {
+                            HStack {
+                                Text("New Glucose")
+                                DecimalTextField(
+                                    " ... ",
+                                    value: $state.manualGlucose,
+                                    formatter: glucoseFormatter,
+                                    autofocus: true,
+                                    cleanInput: true
+                                )
+                                Text(state.units.rawValue).foregroundStyle(.secondary)
+                            }
+                        }
+
+                        Section {
+                            HStack {
+                                let limitLow: Decimal = state.units == .mmolL ? 0.8 : 40
+                                let limitHigh: Decimal = state.units == .mmolL ? 14 : 720
+
+                                Button {
+                                    state.logManualGlucose()
+                                    isAmountUnconfirmed = false
+                                    showManualGlucose = false
+                                }
+                                label: { Text("Save") }
+                                    .frame(maxWidth: .infinity, alignment: .center)
+                                    .disabled(state.manualGlucose < limitLow || state.manualGlucose > limitHigh)
+                            }
+                        }
+                    }
+                }
+                .onAppear(perform: configureView)
+                .navigationTitle("Log Glucose")
+                .navigationBarTitleDisplayMode(.automatic)
+                .navigationBarItems(leading: Button("Close", action: { showManualGlucose = false }))
             }
         }
 
         @ViewBuilder private func treatmentView(_ item: Treatment) -> some View {
             HStack {
-                Image(systemName: "circle.fill").foregroundColor(item.color)
-                Text(dateFormatter.string(from: item.date))
-                    .moveDisabled(true)
-                Text(item.type.name)
+                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 {
+                // Only allow swipe to delete if a carb, fpu, or bolus entry.
+                if item.type == .carbs || item.type == .fpus || item.type == .bolus {
+                    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"
+                                }
+                            }
+
+                            isRemoveHistoryItemAlertPresented = true
                         }
+                    ).tint(.red)
                 }
+            }
+            .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 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!
-                        }
+                    if treatmentToDelete.type == .carbs || treatmentToDelete.type == .fpus {
+                        state.deleteCarbs(treatmentToDelete)
+                    } else {
+                        state.deleteInsulin(treatmentToDelete)
+                    }
                 }
+            } message: {
+                Text("\n" + NSLocalizedString(alertMessage, comment: ""))
             }
         }
 
-        var addExternalInsulinView: some View {
+        var logExternalInsulinView: some View {
             NavigationView {
                 VStack {
                     Form {
@@ -259,12 +274,12 @@ extension DataTable {
                         Section {
                             HStack {
                                 Button {
-                                    state.addExternalInsulin()
+                                    state.logExternalInsulin()
                                     isAmountUnconfirmed = false
                                     showExternalInsulin = false
                                 }
                                 label: {
-                                    Text("Log external insulin")
+                                    Text("Save")
                                 }
                                 .foregroundColor(amountWarningCondition ? Color.white : Color.accentColor)
                                 .frame(maxWidth: .infinity, alignment: .center)
@@ -287,32 +302,63 @@ extension DataTable {
                     }
                 }
                 .onAppear(perform: configureView)
-                .navigationTitle("External Insulin")
-                .navigationBarTitleDisplayMode(.inline)
+                .navigationTitle("Log External Insulin")
+                .navigationBarTitleDisplayMode(.automatic)
                 .navigationBarItems(leading: Button("Close", action: { showExternalInsulin = false
                     state.externalInsulinAmount = 0 }))
             }
         }
 
         @ViewBuilder private func glucoseView(_ item: Glucose) -> some View {
-            VStack(alignment: .leading, spacing: 4) {
-                HStack {
-                    Text(dateFormatter.string(from: item.glucose.dateString))
-                    Spacer()
-                    Text(item.glucose.glucose.map {
-                        glucoseFormatter.string(from: Double(
-                            state.units == .mmolL ? $0.asMmolL : Decimal($0)
-                        ) as NSNumber)!
-                    } ?? "--")
-                    Text(state.units.rawValue)
-                    Text(item.glucose.direction?.symbol ?? "--")
-                }
-                Text("ID: " + item.glucose.id).font(.caption2).foregroundColor(.secondary)
+            HStack {
+                Text(item.glucose.glucose.map {
+                    glucoseFormatter.string(from: Double(
+                        state.units == .mmolL ? $0.asMmolL : Decimal($0)
+                    ) as NSNumber)!
+                } ?? "--")
+                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
 
-        private func deleteGlucose(at offsets: IndexSet) {
-            state.deleteGlucose(at: offsets[offsets.startIndex])
+                        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
+                    }
+
+                    state.deleteGlucose(glucoseToDelete)
+                }
+            } message: {
+                Text("\n" + NSLocalizedString(alertMessage, comment: ""))
+            }
         }
     }
 }

+ 0 - 3
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -14,7 +14,6 @@ extension Home {
         private(set) var filteredHours = 24
         @Published var glucose: [BloodGlucose] = []
         @Published var suggestion: Suggestion?
-        @Published var uploadStats = false
         @Published var enactedSuggestion: Suggestion?
         @Published var recentGlucose: BloodGlucose?
         @Published var glucoseDelta: Int?
@@ -76,7 +75,6 @@ extension Home {
             setupReservoir()
 
             suggestion = provider.suggestion
-            uploadStats = settingsManager.settings.uploadStats
             enactedSuggestion = provider.enactedSuggestion
             units = settingsManager.settings.units
             allowManualTemp = !settingsManager.settings.closedLoop
@@ -392,7 +390,6 @@ extension Home.StateModel:
 
     func settingsDidChange(_ settings: FreeAPSSettings) {
         allowManualTemp = !settings.closedLoop
-        uploadStats = settingsManager.settings.uploadStats
         closedLoop = settingsManager.settings.closedLoop
         units = settingsManager.settings.units
         animatedBackground = settingsManager.settings.animatedBackground

+ 3 - 5
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -22,7 +22,6 @@ extension NightscoutConfig {
         @Published var connecting = false
         @Published var backfilling = false
         @Published var isUploadEnabled = false // Allow uploads
-        @Published var uploadStats = false // Upload Statistics
         @Published var uploadGlucose = true // Upload Glucose
         @Published var changeUploadGlucose = true // if plugin, need to be change in CGM configuration
         @Published var useLocalSource = false
@@ -46,7 +45,6 @@ extension NightscoutConfig {
             subscribeSetting(\.isUploadEnabled, on: $isUploadEnabled) { isUploadEnabled = $0 }
             subscribeSetting(\.useLocalGlucoseSource, on: $useLocalSource) { useLocalSource = $0 }
             subscribeSetting(\.localGlucosePort, on: $localPort.map(Int.init)) { localPort = Decimal($0) }
-            subscribeSetting(\.uploadStats, on: $uploadStats) { uploadStats = $0 }
             subscribeSetting(\.uploadGlucose, on: $uploadGlucose, initial: { uploadGlucose = $0 })
         }
 
@@ -198,7 +196,7 @@ extension NightscoutConfig {
 
                         let sensitivities = fetchedProfile.sens.map { sensitivity -> InsulinSensitivityEntry in
                             InsulinSensitivityEntry(
-                                sensitivity: self.units == .mmolL ? sensitivity.value : sensitivity.value.asMgdL,
+                                sensitivity: sensitivity.value,
                                 offset: self.offset(sensitivity.time) / 60,
                                 start: sensitivity.time
                             )
@@ -219,8 +217,8 @@ extension NightscoutConfig {
                         let targets = fetchedProfile.target_low
                             .map { target -> BGTargetEntry in
                                 BGTargetEntry(
-                                    low: self.units == .mmolL ? target.value : target.value.asMgdL,
-                                    high: self.units == .mmolL ? target.value : target.value.asMgdL,
+                                    low: target.value,
+                                    high: target.value,
                                     start: target.time,
                                     offset: self.offset(target.time) / 60
                                 ) }

+ 0 - 1
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift

@@ -65,7 +65,6 @@ extension NightscoutConfig {
                 Section {
                     Toggle("Upload", isOn: $state.isUploadEnabled)
                     if state.isUploadEnabled {
-                        Toggle("Statistics", isOn: $state.uploadStats)
                         Toggle("Glucose", isOn: $state.uploadGlucose).disabled(!state.changeUploadGlucose)
                     }
                 } header: {

+ 1 - 22
FreeAPS/Sources/Modules/NotificationsConfig/View/NotificationsConfigRootView.swift

@@ -34,27 +34,6 @@ extension NotificationsConfig {
             return formatter
         }
 
-        @Environment(\.colorScheme) var colorScheme
-
-        var color: LinearGradient {
-            colorScheme == .dark ? LinearGradient(
-                gradient: Gradient(colors: [
-                    Color("Background_1"),
-                    Color("Background_1"),
-                    Color("Background_2")
-                    // Color("Background_1")
-                ]),
-                startPoint: .top,
-                endPoint: .bottom
-            )
-                :
-                LinearGradient(
-                    gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
-                    startPoint: .top,
-                    endPoint: .bottom
-                )
-        }
-
         @ViewBuilder private func liveActivitySection() -> some View {
             if #available(iOS 16.2, *) {
                 Section(
@@ -132,7 +111,7 @@ extension NotificationsConfig {
                 }
 
                 liveActivitySection()
-            }.scrollContentBackground(.hidden).background(color)
+            }.scrollContentBackground(.hidden)
                 .onAppear(perform: configureView)
                 .navigationBarTitle("Notifications")
                 .navigationBarTitleDisplayMode(.automatic)

+ 3 - 3
FreeAPS/Sources/Modules/StatConfig/StatConfigStateModel.swift

@@ -3,13 +3,13 @@ import SwiftUI
 extension StatConfig {
     final class StateModel: BaseStateModel<Provider> {
         @Published var overrideHbA1cUnit = false
-        @Published var low: Decimal = 4 / 0.0555
-        @Published var high: Decimal = 10 / 0.0555
+        @Published var low: Decimal = 70
+        @Published var high: Decimal = 180
         @Published var hours: Decimal = 6
         @Published var xGridLines = false
         @Published var yGridLines: Bool = false
         @Published var oneDimensionalGraph = false
-        @Published var rulerMarks: Bool = false
+        @Published var rulerMarks: Bool = true
 
         var units: GlucoseUnits = .mmolL
 

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

@@ -326,30 +326,6 @@ extension NightscoutAPI {
             .eraseToAnyPublisher()
     }
 
-    func uploadStats(_ stats: NightscoutStatistics) -> AnyPublisher<Void, Swift.Error> {
-        var components = URLComponents()
-        components.scheme = url.scheme
-        components.host = url.host
-        components.port = url.port
-        components.path = Config.statusPath
-
-        var request = URLRequest(url: components.url!)
-        request.allowsConstrainedNetworkAccess = false
-        request.timeoutInterval = Config.timeout
-        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
-
-        if let secret = secret {
-            request.addValue(secret.sha1(), forHTTPHeaderField: "api-secret")
-        }
-        request.httpBody = try! JSONCoding.encoder.encode(stats)
-        request.httpMethod = "POST"
-
-        return service.run(request)
-            .retry(Config.retryCount)
-            .map { _ in () }
-            .eraseToAnyPublisher()
-    }
-
     func uploadStatus(_ status: NightscoutStatus) -> AnyPublisher<Void, Swift.Error> {
         var components = URLComponents()
         components.scheme = url.scheme

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

@@ -13,7 +13,6 @@ protocol NightscoutManager: GlucoseSource {
     func deleteInsulin(at date: Date)
     func uploadStatus()
     func uploadGlucose()
-    func uploadStatistics(dailystat: Statistics)
     func uploadPreferences(_ preferences: Preferences)
     func uploadProfileAndSettings(_: Bool)
     var cgmURL: URL? { get }
@@ -249,29 +248,6 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
             .store(in: &lifetime)
     }
 
-    func uploadStatistics(dailystat: Statistics) {
-        let stats = NightscoutStatistics(
-            dailystats: dailystat
-        )
-
-        guard let nightscout = nightscoutAPI, isUploadEnabled else {
-            return
-        }
-
-        processQueue.async {
-            nightscout.uploadStats(stats)
-                .sink { completion in
-                    switch completion {
-                    case .finished:
-                        debug(.nightscout, "Statistics uploaded")
-                    case let .failure(error):
-                        debug(.nightscout, error.localizedDescription)
-                    }
-                } receiveValue: {}
-                .store(in: &self.lifetime)
-        }
-    }
-
     func uploadPreferences(_ preferences: Preferences) {
         let prefs = NightscoutPreferences(
             preferences: settingsManager.preferences

+ 0 - 1
FreeAPS/Sources/Services/Network/TidepoolManager.swift

@@ -14,7 +14,6 @@ protocol TidePoolManager {
 //    func uploadStatus()
     func uploadGlucose(device: HKDevice?)
     func forceUploadData(device: HKDevice?)
-//    func uploadStatistics(dailystat: Statistics)
 //    func uploadPreferences(_ preferences: Preferences)
 //    func uploadProfileAndSettings(_: Bool)
 }