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

Merge branch 'settings-update' of github.com:tmhastings/Trio-dev into settings-update

Deniz Cengiz 1 год назад
Родитель
Сommit
ef1ca44aed
21 измененных файлов с 277 добавлено и 67 удалено
  1. 78 0
      FreeAPS.xcodeproj/xcshareddata/xcschemes/FreeAPS.xcscheme
  2. 7 0
      FreeAPS/Sources/APS/APSManager.swift
  3. 4 0
      FreeAPS/Sources/Logger/Logger.swift
  4. 1 1
      FreeAPS/Sources/Models/DecimalPickerSettings.swift
  5. 1 1
      FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift
  6. 1 1
      FreeAPS/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift
  7. 44 2
      FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift
  8. 2 0
      FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift
  9. 86 0
      FreeAPS/Sources/Modules/Bolus/View/ForecastChart.swift
  10. 2 2
      FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift
  11. 2 1
      FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/CarbSetup.swift
  12. 2 1
      FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/GlucoseSetup.swift
  13. 2 1
      FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/PumpHistorySetup.swift
  14. 1 2
      FreeAPS/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift
  15. 3 2
      FreeAPS/Sources/Services/LiveActivity/Data/DataManager.swift
  16. 1 0
      FreeAPS/Sources/Services/LiveActivity/Data/DeterminationData.swift
  17. 1 1
      FreeAPS/Sources/Services/LiveActivity/LiveActitiyAttributes.swift
  18. 1 1
      FreeAPS/Sources/Services/LiveActivity/LiveActivityAttributes+Helper.swift
  19. 29 46
      FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift
  20. 8 4
      FreeAPS/Sources/Services/Network/NightscoutManager.swift
  21. 1 1
      LiveActivity/Views/WidgetItems/LiveActivityUpdatedLabelView.swift

+ 78 - 0
FreeAPS.xcodeproj/xcshareddata/xcschemes/FreeAPS.xcscheme

@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1600"
+   version = "1.7">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES"
+      buildArchitectures = "Automatic">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "388E595725AD948C0019842D"
+               BuildableName = "FreeAPS.app"
+               BlueprintName = "FreeAPS"
+               ReferencedContainer = "container:FreeAPS.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      shouldAutocreateTestPlan = "YES">
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "388E595725AD948C0019842D"
+            BuildableName = "FreeAPS.app"
+            BlueprintName = "FreeAPS"
+            ReferencedContainer = "container:FreeAPS.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "388E595725AD948C0019842D"
+            BuildableName = "FreeAPS.app"
+            BlueprintName = "FreeAPS"
+            ReferencedContainer = "container:FreeAPS.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

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

@@ -273,6 +273,13 @@ final class BaseAPSManager: APSManager, Injectable {
                 await loopCompleted(error: error, loopStatRecord: loopStatRecord)
                 await loopCompleted(error: error, loopStatRecord: loopStatRecord)
             }
             }
 
 
+            if let nightscoutManager = nightscout {
+                await nightscoutManager.uploadCarbs()
+                await nightscoutManager.uploadPumpHistory()
+                await nightscoutManager.uploadOverrides()
+                await nightscoutManager.uploadTempTargets()
+            }
+
             // End background task after all the operations are completed
             // End background task after all the operations are completed
             if let backgroundTask = self.backGroundTaskID {
             if let backgroundTask = self.backGroundTaskID {
                 await UIApplication.shared.endBackgroundTask(backgroundTask)
                 await UIApplication.shared.endBackgroundTask(backgroundTask)

+ 4 - 0
FreeAPS/Sources/Logger/Logger.swift

@@ -113,6 +113,7 @@ final class Logger {
     static let apsManager = Logger(category: .apsManager, reporter: baseReporter)
     static let apsManager = Logger(category: .apsManager, reporter: baseReporter)
     static let nightscout = Logger(category: .nightscout, reporter: baseReporter)
     static let nightscout = Logger(category: .nightscout, reporter: baseReporter)
     static let remoteControl = Logger(category: .remoteControl, reporter: baseReporter)
     static let remoteControl = Logger(category: .remoteControl, reporter: baseReporter)
+    static let bolusState = Logger(category: .bolusState, reporter: baseReporter)
 
 
     enum Category: String {
     enum Category: String {
         case `default`
         case `default`
@@ -123,6 +124,7 @@ final class Logger {
         case apsManager
         case apsManager
         case nightscout
         case nightscout
         case remoteControl
         case remoteControl
+        case bolusState
 
 
         var name: String {
         var name: String {
             rawValue.capitalizingFirstLetter()
             rawValue.capitalizingFirstLetter()
@@ -138,6 +140,7 @@ final class Logger {
             case .apsManager: return .apsManager
             case .apsManager: return .apsManager
             case .nightscout: return .nightscout
             case .nightscout: return .nightscout
             case .remoteControl: return .remoteControl
             case .remoteControl: return .remoteControl
+            case .bolusState: return .bolusState
             }
             }
         }
         }
 
 
@@ -146,6 +149,7 @@ final class Logger {
             switch self {
             switch self {
             case .default: return OSLog.default
             case .default: return OSLog.default
             case .apsManager,
             case .apsManager,
+                 .bolusState,
                  .businessLogic,
                  .businessLogic,
                  .deviceManager,
                  .deviceManager,
                  .nightscout,
                  .nightscout,

+ 1 - 1
FreeAPS/Sources/Models/DecimalPickerSettings.swift

@@ -77,7 +77,7 @@ struct DecimalPickerSettings {
         type: PickerSetting.PickerSettingType.glucose
         type: PickerSetting.PickerSettingType.glucose
     )
     )
     var maxCOB = PickerSetting(value: 120, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
     var maxCOB = PickerSetting(value: 120, step: 5, min: 0, max: 300, type: PickerSetting.PickerSettingType.gram)
-    var min5mCarbimpact = PickerSetting(value: 8, step: 1, min: 0, max: 20, type: PickerSetting.PickerSettingType.glucose)
+    var min5mCarbimpact = PickerSetting(value: 8, step: 1, min: 1, max: 20, type: PickerSetting.PickerSettingType.glucose)
     var autotuneISFAdjustmentFraction = PickerSetting(
     var autotuneISFAdjustmentFraction = PickerSetting(
         value: 1.0,
         value: 1.0,
         step: 0.05,
         step: 0.05,

+ 1 - 1
FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift

@@ -239,7 +239,7 @@ extension AlgorithmAdvancedSettings {
                         Text(
                         Text(
                             "This prevents lingering insulin effects when your pump is suspended, ensuring safer management of insulin on board."
                             "This prevents lingering insulin effects when your pump is suspended, ensuring safer management of insulin on board."
                         )
                         )
-                        Text("Note: Applies to only to pumps with on-pump suspend options")
+                        Text("Note: Applies to only to pumps with on-pump suspend options.")
                     }
                     }
                 )
                 )
 
 

+ 1 - 1
FreeAPS/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift

@@ -53,7 +53,7 @@ extension AutosensSettings {
                     VStack(alignment: .leading, spacing: 10) {
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: 120%").bold()
                         Text("Default: 120%").bold()
                         Text(
                         Text(
-                            "Autosens Max sets the maximum Autosens Ratio used by Autosens, Dynamic ISF, or Sigmoid Formula"
+                            "Autosens Max sets the maximum Autosens Ratio used by Autosens, Dynamic ISF, or Sigmoid Formula."
                         )
                         )
                         Text(
                         Text(
                             "The Autosens Ratio is used to calculate the amount of adjustment needed to basal rates, ISF, and CR."
                             "The Autosens Ratio is used to calculate the amount of adjustment needed to basal rates, ISF, and CR."

+ 44 - 2
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -119,12 +119,24 @@ extension Bolus {
         let glucoseFetchContext = CoreDataStack.shared.newTaskContext()
         let glucoseFetchContext = CoreDataStack.shared.newTaskContext()
         let determinationFetchContext = CoreDataStack.shared.newTaskContext()
         let determinationFetchContext = CoreDataStack.shared.newTaskContext()
 
 
+        var isActive: Bool = false
+
         private var coreDataPublisher: AnyPublisher<Set<NSManagedObject>, Never>?
         private var coreDataPublisher: AnyPublisher<Set<NSManagedObject>, Never>?
         private var subscriptions = Set<AnyCancellable>()
         private var subscriptions = Set<AnyCancellable>()
 
 
         typealias PumpEvent = PumpEventStored.EventType
         typealias PumpEvent = PumpEventStored.EventType
 
 
+        func unsubscribe() {
+            subscriptions.forEach { $0.cancel() }
+            subscriptions.removeAll()
+        }
+
         override func subscribe() {
         override func subscribe() {
+            guard isActive else {
+                return
+            }
+
+            debug(.bolusState, "subscribe fired")
             coreDataPublisher =
             coreDataPublisher =
                 changedObjectsOnManagedObjectContextDidSavePublisher()
                 changedObjectsOnManagedObjectContextDidSavePublisher()
                     .receive(on: DispatchQueue.global(qos: .background))
                     .receive(on: DispatchQueue.global(qos: .background))
@@ -135,7 +147,19 @@ extension Bolus {
             setupBolusStateConcurrently()
             setupBolusStateConcurrently()
         }
         }
 
 
+        deinit {
+            // Unregister from broadcaster
+            broadcaster.unregister(DeterminationObserver.self, observer: self)
+            broadcaster.unregister(BolusFailureObserver.self, observer: self)
+
+            // Cancel Combine subscriptions
+            unsubscribe()
+
+            debug(.bolusState, "Bolus.StateModel deinitialized")
+        }
+
         private func setupBolusStateConcurrently() {
         private func setupBolusStateConcurrently() {
+            debug(.bolusState, "setupBolusStateConcurrently fired")
             Task {
             Task {
                 await withTaskGroup(of: Void.self) { group in
                 await withTaskGroup(of: Void.self) { group in
                     group.addTask {
                     group.addTask {
@@ -329,6 +353,7 @@ extension Bolus {
 
 
         /// Calculate insulin recommendation
         /// Calculate insulin recommendation
         func calculateInsulin() -> Decimal {
         func calculateInsulin() -> Decimal {
+            debug(.bolusState, "calculateInsulin fired")
             let isfForCalculation = isf
             let isfForCalculation = isf
 
 
             // insulin needed for the current blood glucose
             // insulin needed for the current blood glucose
@@ -386,6 +411,7 @@ extension Bolus {
 
 
         func invokeTreatmentsTask() {
         func invokeTreatmentsTask() {
             Task {
             Task {
+                debug(.bolusState, "invokeTreatmentsTask fired")
                 await MainActor.run {
                 await MainActor.run {
                     self.addButtonPressed = true
                     self.addButtonPressed = true
                 }
                 }
@@ -425,6 +451,7 @@ extension Bolus {
         // MARK: - Insulin
         // MARK: - Insulin
 
 
         private func handleInsulin(isExternal: Bool) async throws {
         private func handleInsulin(isExternal: Bool) async throws {
+            debug(.bolusState, "handleInsulin fired")
             if !isExternal {
             if !isExternal {
                 await addPumpInsulin()
                 await addPumpInsulin()
             } else {
             } else {
@@ -573,7 +600,13 @@ extension Bolus {
 
 
 extension Bolus.StateModel: DeterminationObserver, BolusFailureObserver {
 extension Bolus.StateModel: DeterminationObserver, BolusFailureObserver {
     func determinationDidUpdate(_: Determination) {
     func determinationDidUpdate(_: Determination) {
+        guard isActive else {
+            debug(.bolusState, "skipping determinationDidUpdate; view not active")
+            return
+        }
+
         DispatchQueue.main.async {
         DispatchQueue.main.async {
+            debug(.bolusState, "determinationDidUpdate fired")
             self.waitForSuggestion = false
             self.waitForSuggestion = false
             if self.addButtonPressed {
             if self.addButtonPressed {
                 self.hideModal()
                 self.hideModal()
@@ -583,6 +616,7 @@ extension Bolus.StateModel: DeterminationObserver, BolusFailureObserver {
 
 
     func bolusDidFail() {
     func bolusDidFail() {
         DispatchQueue.main.async {
         DispatchQueue.main.async {
+            debug(.bolusState, "bolusDidFail fired")
             self.waitForSuggestion = false
             self.waitForSuggestion = false
             if self.addButtonPressed {
             if self.addButtonPressed {
                 self.hideModal()
                 self.hideModal()
@@ -597,7 +631,8 @@ extension Bolus.StateModel {
             guard let self = self else { return }
             guard let self = self else { return }
             Task {
             Task {
                 await self.setupDeterminationsArray()
                 await self.setupDeterminationsArray()
-                await self.updateForecasts()
+                let forecastData = await self.mapForecastsForChart()
+                await self.updateForecasts(with: forecastData)
             }
             }
         }.store(in: &subscriptions)
         }.store(in: &subscriptions)
 
 
@@ -749,11 +784,18 @@ extension Bolus.StateModel {
 
 
 extension Bolus.StateModel {
 extension Bolus.StateModel {
     @MainActor func updateForecasts(with forecastData: Determination? = nil) async {
     @MainActor func updateForecasts(with forecastData: Determination? = nil) async {
+        guard isActive else {
+            return
+                debug(.bolusState, "updateForecasts not fired")
+        }
+
+        debug(.bolusState, "updateForecasts fired")
         if let forecastData = forecastData {
         if let forecastData = forecastData {
             simulatedDetermination = forecastData
             simulatedDetermination = forecastData
         } else {
         } else {
             simulatedDetermination = await Task { [self] in
             simulatedDetermination = await Task { [self] in
-                await apsManager.simulateDetermineBasal(carbs: carbs, iob: amount)
+                debug(.bolusState, "calling simulateDetermineBasal to get forecast data")
+                return await apsManager.simulateDetermineBasal(carbs: carbs, iob: amount)
             }.value
             }.value
         }
         }
 
 

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

@@ -344,10 +344,12 @@ extension Bolus {
             })
             })
             .onAppear {
             .onAppear {
                 configureView {
                 configureView {
+                    state.isActive = true
                     state.insulinCalculated = state.calculateInsulin()
                     state.insulinCalculated = state.calculateInsulin()
                 }
                 }
             }
             }
             .onDisappear {
             .onDisappear {
+                state.isActive = false
                 state.addButtonPressed = false
                 state.addButtonPressed = false
             }
             }
             .sheet(isPresented: $state.showInfo) {
             .sheet(isPresented: $state.showInfo) {

+ 86 - 0
FreeAPS/Sources/Modules/Bolus/View/ForecastChart.swift

@@ -9,6 +9,8 @@ struct ForecastChart: View {
 
 
     @State private var startMarker = Date(timeIntervalSinceNow: -4 * 60 * 60)
     @State private var startMarker = Date(timeIntervalSinceNow: -4 * 60 * 60)
 
 
+    @State var selection: Date? = nil
+
     private var endMarker: Date {
     private var endMarker: Date {
         state
         state
             .forecastDisplayType == .lines ? Date(timeIntervalSinceNow: TimeInterval(hours: 3)) :
             .forecastDisplayType == .lines ? Date(timeIntervalSinceNow: TimeInterval(hours: 3)) :
@@ -32,6 +34,12 @@ struct ForecastChart: View {
         return formatter
         return formatter
     }
     }
 
 
+    private var selectedGlucose: GlucoseStored? {
+        guard let selection = selection else { return nil }
+        let range = selection.addingTimeInterval(-150) ... selection.addingTimeInterval(150)
+        return state.glucoseFromPersistence.first { $0.date.map(range.contains) ?? false }
+    }
+
     var body: some View {
     var body: some View {
         VStack {
         VStack {
             forecastChartLabels
             forecastChartLabels
@@ -114,7 +122,43 @@ struct ForecastChart: View {
             } else {
             } else {
                 drawForecastsCone()
                 drawForecastsCone()
             }
             }
+
+            if let selectedGlucose {
+                RuleMark(x: .value("Selection", selectedGlucose.date ?? Date.now, unit: .minute))
+                    .foregroundStyle(Color.tabBar)
+                    .lineStyle(.init(lineWidth: 2))
+                    .annotation(
+                        position: .top,
+                        overflowResolution: .init(x: .fit(to: .chart), y: .disabled)
+                    ) {
+                        selectionPopover
+                    }
+
+                PointMark(
+                    x: .value("Time", selectedGlucose.date ?? Date.now, unit: .minute),
+                    y: .value("Value", selectedGlucose.glucose)
+                )
+                .zIndex(-1)
+                .symbolSize(CGSize(width: 15, height: 15))
+                .foregroundStyle(
+                    Decimal(selectedGlucose.glucose) > state.highGlucose ? Color.orange
+                        .opacity(0.8) :
+                        (
+                            Decimal(selectedGlucose.glucose) < state.lowGlucose ? Color.red.opacity(0.8) : Color.green
+                                .opacity(0.8)
+                        )
+                )
+
+                PointMark(
+                    x: .value("Time", selectedGlucose.date ?? Date.now, unit: .minute),
+                    y: .value("Value", selectedGlucose.glucose)
+                )
+                .zIndex(-1)
+                .symbolSize(CGSize(width: 6, height: 6))
+                .foregroundStyle(Color.primary)
+            }
         }
         }
+        .chartXSelection(value: $selection)
         .chartXAxis { forecastChartXAxis }
         .chartXAxis { forecastChartXAxis }
         .chartXScale(domain: startMarker ... endMarker)
         .chartXScale(domain: startMarker ... endMarker)
         .chartYAxis { forecastChartYAxis }
         .chartYAxis { forecastChartYAxis }
@@ -122,6 +166,48 @@ struct ForecastChart: View {
         .backport.chartForegroundStyleScale(state: state)
         .backport.chartForegroundStyleScale(state: state)
     }
     }
 
 
+    @ViewBuilder var selectionPopover: some View {
+        if let sgv = selectedGlucose?.glucose {
+            VStack(alignment: .leading) {
+                HStack {
+                    Image(systemName: "clock")
+                    Text(selectedGlucose?.date?.formatted(.dateTime.hour().minute(.twoDigits)) ?? "")
+                        .font(.footnote).bold()
+                }.font(.footnote).padding(.bottom, 5)
+
+                // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
+                let hardCodedLow = Decimal(55)
+                let hardCodedHigh = Decimal(220)
+                let isDynamicColorScheme = state.glucoseColorScheme == .dynamicColor
+
+                let glucoseColor = FreeAPS.getDynamicGlucoseColor(
+                    glucoseValue: Decimal(sgv),
+                    highGlucoseColorValue: isDynamicColorScheme ? hardCodedHigh : state.highGlucose,
+                    lowGlucoseColorValue: isDynamicColorScheme ? hardCodedLow : state.lowGlucose,
+                    targetGlucose: state.currentBGTarget,
+                    glucoseColorScheme: state.glucoseColorScheme
+                )
+                HStack {
+                    Text(state.units == .mgdL ? Decimal(sgv).description : Decimal(sgv).formattedAsMmolL)
+                        .bold()
+                        + Text(" \(state.units.rawValue)")
+                }.foregroundStyle(
+                    Color(glucoseColor)
+                ).font(.footnote)
+            }
+            .padding(7)
+            .background {
+                RoundedRectangle(cornerRadius: 4)
+                    .fill(Color.chart.opacity(0.85))
+                    .shadow(color: Color.secondary, radius: 2)
+                    .overlay(
+                        RoundedRectangle(cornerRadius: 4)
+                            .stroke(Color.secondary, lineWidth: 2)
+                    )
+            }
+        }
+    }
+
     private func drawGlucose() -> some ChartContent {
     private func drawGlucose() -> some ChartContent {
         ForEach(state.glucoseFromPersistence) { item in
         ForEach(state.glucoseFromPersistence) { item in
             let glucoseToDisplay = state.units == .mgdL ? Decimal(item.glucose) : Decimal(item.glucose).asMmolL
             let glucoseToDisplay = state.units == .mgdL ? Decimal(item.glucose) : Decimal(item.glucose).asMmolL

+ 2 - 2
FreeAPS/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift

@@ -64,7 +64,7 @@ extension BolusCalculatorConfig {
                     label: "Display Meal Presets",
                     label: "Display Meal Presets",
                     miniHint: "Allows you to create and save preset meals.",
                     miniHint: "Allows you to create and save preset meals.",
                     verboseHint: VStack(alignment: .leading, spacing: 10) {
                     verboseHint: VStack(alignment: .leading, spacing: 10) {
-                        Text("Default: OFF").bold()
+                        Text("Default: ON").bold()
                         Text("Enabling this feature allows you to create and save preset meals.")
                         Text("Enabling this feature allows you to create and save preset meals.")
                     }
                     }
                 )
                 )
@@ -86,7 +86,7 @@ extension BolusCalculatorConfig {
                     miniHint: "Percentage of bolus used in bolus calculator.",
                     miniHint: "Percentage of bolus used in bolus calculator.",
                     verboseHint:
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                     VStack(alignment: .leading, spacing: 10) {
-                        Text("Default: 70%").bold()
+                        Text("Default: 80%").bold()
                         Text(
                         Text(
                             "Recommended Bolus Percentage is a safety feature built into Trio. Trio first calculates an insulin required value, which is the full dosage. That dosage is then multiplied by your Recommended Bolus Percentage to display your suggested insulin dose in the bolus calculator."
                             "Recommended Bolus Percentage is a safety feature built into Trio. Trio first calculates an insulin required value, which is the full dosage. That dosage is then multiplied by your Recommended Bolus Percentage to display your suggested insulin dose in the bolus calculator."
                         )
                         )

+ 2 - 1
FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/CarbSetup.swift

@@ -16,7 +16,8 @@ extension Home.StateModel {
             onContext: carbsFetchContext,
             onContext: carbsFetchContext,
             predicate: NSPredicate.carbsForChart,
             predicate: NSPredicate.carbsForChart,
             key: "date",
             key: "date",
-            ascending: false
+            ascending: false,
+            batchSize: 5
         )
         )
 
 
         return await carbsFetchContext.perform {
         return await carbsFetchContext.perform {

+ 2 - 1
FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/GlucoseSetup.swift

@@ -16,7 +16,8 @@ extension Home.StateModel {
             onContext: glucoseFetchContext,
             onContext: glucoseFetchContext,
             predicate: NSPredicate.glucose,
             predicate: NSPredicate.glucose,
             key: "date",
             key: "date",
-            ascending: true
+            ascending: true,
+            batchSize: 50
         )
         )
 
 
         return await glucoseFetchContext.perform {
         return await glucoseFetchContext.perform {

+ 2 - 1
FreeAPS/Sources/Modules/Home/HomeStateModel+Setup/PumpHistorySetup.swift

@@ -16,7 +16,8 @@ extension Home.StateModel {
             onContext: pumpHistoryFetchContext,
             onContext: pumpHistoryFetchContext,
             predicate: NSPredicate.pumpHistoryLast24h,
             predicate: NSPredicate.pumpHistoryLast24h,
             key: "timestamp",
             key: "timestamp",
-            ascending: true
+            ascending: true,
+            batchSize: 30
         )
         )
 
 
         return await pumpHistoryFetchContext.perform {
         return await pumpHistoryFetchContext.perform {

+ 1 - 2
FreeAPS/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift

@@ -300,7 +300,7 @@ extension SMBSettings {
                         }
                         }
                         VStack(alignment: .leading, spacing: 10) {
                         VStack(alignment: .leading, spacing: 10) {
                             Text(
                             Text(
-                                "TThis is a limit on the size of a single UAM SMB. One UAM SMB can only be as large as this many minutes of your current profile basal rate."
+                                "This is a limit on the size of a single UAM SMB. One UAM SMB can only be as large as this many minutes of your current profile basal rate."
                             )
                             )
                             Text(
                             Text(
                                 "To calculate the maximum UAM SMB allowed based on this setting, use the following formula"
                                 "To calculate the maximum UAM SMB allowed based on this setting, use the following formula"
@@ -395,7 +395,6 @@ extension SMBSettings {
                         Text(
                         Text(
                             "This is the minimum number of minutes since the last SMB or manual bolus before Trio will permit an automated SMB."
                             "This is the minimum number of minutes since the last SMB or manual bolus before Trio will permit an automated SMB."
                         )
                         )
-                        Text("Note: For Omnipod Dash, minimum value is 3 min. For Omnipod Eros, minimum value is 5 min.")
                     }
                     }
                 )
                 )
             }
             }

+ 3 - 2
FreeAPS/Sources/Services/LiveActivity/Data/DataManager.swift

@@ -33,7 +33,7 @@ extension LiveActivityBridge {
             key: "deliverAt",
             key: "deliverAt",
             ascending: false,
             ascending: false,
             fetchLimit: 1,
             fetchLimit: 1,
-            propertiesToFetch: ["iob", "cob", "currentTarget"]
+            propertiesToFetch: ["iob", "cob", "currentTarget", "deliverAt"]
         )
         )
 
 
         return await context.perform {
         return await context.perform {
@@ -45,7 +45,8 @@ extension LiveActivityBridge {
                 DeterminationData(
                 DeterminationData(
                     cob: ($0["cob"] as? Int) ?? 0,
                     cob: ($0["cob"] as? Int) ?? 0,
                     iob: ($0["iob"] as? NSDecimalNumber)?.decimalValue ?? 0,
                     iob: ($0["iob"] as? NSDecimalNumber)?.decimalValue ?? 0,
-                    target: ($0["currentTarget"] as? NSDecimalNumber)?.decimalValue ?? 0
+                    target: ($0["currentTarget"] as? NSDecimalNumber)?.decimalValue ?? 0,
+                    date: $0["deliverAt"] as? Date ?? nil
                 )
                 )
             }
             }
         }
         }

+ 1 - 0
FreeAPS/Sources/Services/LiveActivity/Data/DeterminationData.swift

@@ -4,4 +4,5 @@ struct DeterminationData {
     let cob: Int
     let cob: Int
     let iob: Decimal
     let iob: Decimal
     let target: Decimal
     let target: Decimal
+    let date: Date?
 }
 }

+ 1 - 1
FreeAPS/Sources/Services/LiveActivity/LiveActitiyAttributes.swift

@@ -16,7 +16,7 @@ struct LiveActivityAttributes: ActivityAttributes {
         let bg: String
         let bg: String
         let direction: String?
         let direction: String?
         let change: String
         let change: String
-        let date: Date
+        let date: Date?
         let highGlucose: Decimal
         let highGlucose: Decimal
         let lowGlucose: Decimal
         let lowGlucose: Decimal
         let target: Decimal
         let target: Decimal

+ 1 - 1
FreeAPS/Sources/Services/LiveActivity/LiveActivityAttributes+Helper.swift

@@ -126,7 +126,7 @@ extension LiveActivityAttributes.ContentState {
             bg: formattedBG,
             bg: formattedBG,
             direction: trendString,
             direction: trendString,
             change: change,
             change: change,
-            date: bg.date,
+            date: determination?.date ?? nil,
             highGlucose: settings.high,
             highGlucose: settings.high,
             lowGlucose: settings.low,
             lowGlucose: settings.low,
             target: determination?.target ?? 100 as Decimal,
             target: determination?.target ?? 100 as Decimal,

+ 29 - 46
FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift

@@ -15,18 +15,17 @@ import UIKit
              .ended,
              .ended,
              .stale:
              .stale:
             return true
             return true
-        case .active: break
+        case .active:
+            break
         @unknown default:
         @unknown default:
             return true
             return true
         }
         }
-
-        return -startDate.timeIntervalSinceNow >
-            TimeInterval(60 * 60)
+        return -startDate.timeIntervalSinceNow > TimeInterval(60 * 60)
     }
     }
 }
 }
 
 
-@available(iOS 16.2, *) final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver
-{
+@available(iOS 16.2, *)
+final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var storage: FileStorage!
     @Injected() private var storage: FileStorage!
@@ -90,8 +89,6 @@ import UIKit
         )
         )
     }
     }
 
 
-    // TODO: - use a delegate or a custom notification here instead
-
     func settingsDidChange(_: FreeAPSSettings) {
     func settingsDidChange(_: FreeAPSSettings) {
         Task {
         Task {
             await updateContentState(determination)
             await updateContentState(determination)
@@ -99,7 +96,6 @@ import UIKit
     }
     }
 
 
     private func registerHandler() {
     private func registerHandler() {
-        // Since we are only using this info to show if an Override is active or not in the Live Activity it is enough to observe only the 'OverrideStored' Entity
         coreDataPublisher?.filterByEntityName("OverrideStored").sink { [weak self] _ in
         coreDataPublisher?.filterByEntityName("OverrideStored").sink { [weak self] _ in
             guard let self = self else { return }
             guard let self = self else { return }
             self.overridesDidUpdate()
             self.overridesDidUpdate()
@@ -141,15 +137,16 @@ import UIKit
 
 
     @objc private func handleLiveActivityOrderChange() {
     @objc private func handleLiveActivityOrderChange() {
         Task {
         Task {
-            self.widgetItems = UserDefaults.standard
-                .loadLiveActivityOrderFromUserDefaults() ?? LiveActivityAttributes.LiveActivityItem.defaultItems
+            self.widgetItems = UserDefaults.standard.loadLiveActivityOrderFromUserDefaults() ?? LiveActivityAttributes
+                .LiveActivityItem.defaultItems
             await self.updateLiveActivityOrder()
             await self.updateLiveActivityOrder()
         }
         }
     }
     }
 
 
     @MainActor private func updateContentState<T>(_ update: T) async {
     @MainActor private func updateContentState<T>(_ update: T) async {
-        guard let latestGlucose = latestGlucose else { return }
-
+        guard let latestGlucose = latestGlucose else {
+            return
+        }
         var content: LiveActivityAttributes.ContentState?
         var content: LiveActivityAttributes.ContentState?
 
 
         if let determination = update as? DeterminationData {
         if let determination = update as? DeterminationData {
@@ -189,10 +186,7 @@ import UIKit
 
 
     private func setupGlucoseArray() {
     private func setupGlucoseArray() {
         Task { @MainActor in
         Task { @MainActor in
-            // Fetch and map glucose to GlucoseData struct
             self.glucoseFromPersistence = await fetchAndMapGlucose()
             self.glucoseFromPersistence = await fetchAndMapGlucose()
-
-            // Push the update to the Live Activity
             glucoseDidUpdate(glucoseFromPersistence ?? [])
             glucoseDidUpdate(glucoseFromPersistence ?? [])
         }
         }
     }
     }
@@ -209,14 +203,9 @@ import UIKit
         }
         }
     }
     }
 
 
-    /// creates and tries to present a new activity update from the current GlucoseStorage values if live activities are enabled in settings
-    /// Ends existing live activities if live activities are not enabled in settings
     @MainActor private func forceActivityUpdate() {
     @MainActor private func forceActivityUpdate() {
-        // just before app resigns active, show a new activity
-        // only do this if there is no current activity or the current activity is older than 1h
         if settings.useLiveActivity {
         if settings.useLiveActivity {
-            if currentActivity?.needsRecreation() ?? true
-            {
+            if currentActivity?.needsRecreation() ?? true {
                 glucoseDidUpdate(glucoseFromPersistence ?? [])
                 glucoseDidUpdate(glucoseFromPersistence ?? [])
             }
             }
         } else {
         } else {
@@ -226,9 +215,7 @@ import UIKit
         }
         }
     }
     }
 
 
-    /// attempts to present this live activity state, creating a new activity if none exists yet
     @MainActor private func pushUpdate(_ state: LiveActivityAttributes.ContentState) async {
     @MainActor private func pushUpdate(_ state: LiveActivityAttributes.ContentState) async {
-//        // End all activities that are not the current one
         for unknownActivity in Activity<LiveActivityAttributes>.activities
         for unknownActivity in Activity<LiveActivityAttributes>.activities
             .filter({ self.currentActivity?.activity.id != $0.id })
             .filter({ self.currentActivity?.activity.id != $0.id })
         {
         {
@@ -242,32 +229,29 @@ import UIKit
             } else {
             } else {
                 let content = ActivityContent(
                 let content = ActivityContent(
                     state: state,
                     state: state,
-                    staleDate: min(state.date, Date.now).addingTimeInterval(360) // 6 minutes in seconds
+                    staleDate: min(state.date ?? Date.now, Date.now).addingTimeInterval(360) // 6 minutes in seconds
                 )
                 )
                 await currentActivity.activity.update(content)
                 await currentActivity.activity.update(content)
             }
             }
         } else {
         } else {
             do {
             do {
-                // always push a non-stale content as the first update
-                // pushing a stale content as the frst content results in the activity not being shown at all
-                // apparently this initial state is also what is shown after the live activity expires (after 8h)
                 let expired = ActivityContent(
                 let expired = ActivityContent(
-                    state: LiveActivityAttributes.ContentState(
-                        bg: "--",
-                        direction: nil,
-                        change: "--",
-                        date: Date.now,
-                        highGlucose: settings.high,
-                        lowGlucose: settings.low,
-                        target: determination?.target ?? 100 as Decimal,
-                        glucoseColorScheme: settings.glucoseColorScheme.rawValue,
-                        detailedViewState: nil,
-                        isInitialState: true
-                    ),
+                    state: LiveActivityAttributes
+                        .ContentState(
+                            bg: "--",
+                            direction: nil,
+                            change: "--",
+                            date: Date.now,
+                            highGlucose: settings.high,
+                            lowGlucose: settings.low,
+                            target: determination?.target ?? 100 as Decimal,
+                            glucoseColorScheme: settings.glucoseColorScheme.rawValue,
+                            detailedViewState: nil,
+                            isInitialState: true
+                        ),
                     staleDate: Date.now.addingTimeInterval(60)
                     staleDate: Date.now.addingTimeInterval(60)
                 )
                 )
 
 
-                // Request a new activity
                 let activity = try Activity.request(
                 let activity = try Activity.request(
                     attributes: LiveActivityAttributes(startDate: Date.now),
                     attributes: LiveActivityAttributes(startDate: Date.now),
                     content: expired,
                     content: expired,
@@ -275,22 +259,22 @@ import UIKit
                 )
                 )
                 currentActivity = ActiveActivity(activity: activity, startDate: Date.now)
                 currentActivity = ActiveActivity(activity: activity, startDate: Date.now)
 
 
-                // then show the actual content
                 await pushUpdate(state)
                 await pushUpdate(state)
             } catch {
             } catch {
-                print("Activity creation error: \(error)")
+                debug(
+                    .default,
+                    "\(#file): Error creating new activity: \(error)"
+                )
             }
             }
         }
         }
     }
     }
 
 
-    /// ends all live activities immediateny
     private func endActivity() async {
     private func endActivity() async {
         if let currentActivity {
         if let currentActivity {
             await currentActivity.activity.end(nil, dismissalPolicy: .immediate)
             await currentActivity.activity.end(nil, dismissalPolicy: .immediate)
             self.currentActivity = nil
             self.currentActivity = nil
         }
         }
 
 
-        // end any other activities
         for unknownActivity in Activity<LiveActivityAttributes>.activities {
         for unknownActivity in Activity<LiveActivityAttributes>.activities {
             await unknownActivity.end(nil, dismissalPolicy: .immediate)
             await unknownActivity.end(nil, dismissalPolicy: .immediate)
         }
         }
@@ -309,7 +293,6 @@ extension LiveActivityBridge {
             return
             return
         }
         }
 
 
-        // backfill latest glucose if contained in this update
         if glucose.count > 1 {
         if glucose.count > 1 {
             latestGlucose = glucose.dropFirst().first
             latestGlucose = glucose.dropFirst().first
         }
         }

+ 8 - 4
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -15,6 +15,10 @@ protocol NightscoutManager: GlucoseSource {
     func deleteManualGlucose(withID id: String) async
     func deleteManualGlucose(withID id: String) async
     func uploadStatus() async
     func uploadStatus() async
     func uploadGlucose() async
     func uploadGlucose() async
+    func uploadCarbs() async
+    func uploadPumpHistory() async
+    func uploadOverrides() async
+    func uploadTempTargets() async
     func uploadManualGlucose() async
     func uploadManualGlucose() async
     func uploadProfiles() async
     func uploadProfiles() async
     func importSettings() async -> ScheduledNightscoutProfile?
     func importSettings() async -> ScheduledNightscoutProfile?
@@ -692,24 +696,24 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         await uploadManualGlucose(glucoseStorage.getManualGlucoseNotYetUploadedToNightscout())
         await uploadManualGlucose(glucoseStorage.getManualGlucoseNotYetUploadedToNightscout())
     }
     }
 
 
-    private func uploadPumpHistory() async {
+    func uploadPumpHistory() async {
         await uploadTreatments(
         await uploadTreatments(
             pumpHistoryStorage.getPumpHistoryNotYetUploadedToNightscout(),
             pumpHistoryStorage.getPumpHistoryNotYetUploadedToNightscout(),
             fileToSave: OpenAPS.Nightscout.uploadedPumphistory
             fileToSave: OpenAPS.Nightscout.uploadedPumphistory
         )
         )
     }
     }
 
 
-    private func uploadCarbs() async {
+    func uploadCarbs() async {
         await uploadCarbs(carbsStorage.getCarbsNotYetUploadedToNightscout())
         await uploadCarbs(carbsStorage.getCarbsNotYetUploadedToNightscout())
         await uploadCarbs(carbsStorage.getFPUsNotYetUploadedToNightscout())
         await uploadCarbs(carbsStorage.getFPUsNotYetUploadedToNightscout())
     }
     }
 
 
-    private func uploadOverrides() async {
+    func uploadOverrides() async {
         await uploadOverrides(overridesStorage.getOverridesNotYetUploadedToNightscout())
         await uploadOverrides(overridesStorage.getOverridesNotYetUploadedToNightscout())
         await uploadOverrideRuns(overridesStorage.getOverrideRunsNotYetUploadedToNightscout())
         await uploadOverrideRuns(overridesStorage.getOverrideRunsNotYetUploadedToNightscout())
     }
     }
 
 
-    private func uploadTempTargets() async {
+    func uploadTempTargets() async {
         await uploadTreatments(
         await uploadTreatments(
             tempTargetsStorage.nightscoutTreatmentsNotUploaded(),
             tempTargetsStorage.nightscoutTreatmentsNotUploaded(),
             fileToSave: OpenAPS.Nightscout.uploadedTempTargets
             fileToSave: OpenAPS.Nightscout.uploadedTempTargets

+ 1 - 1
LiveActivity/Views/WidgetItems/LiveActivityUpdatedLabelView.swift

@@ -20,7 +20,7 @@ struct LiveActivityUpdatedLabelView: View {
     }
     }
 
 
     var body: some View {
     var body: some View {
-        let dateText = Text("\(dateFormatter.string(from: context.state.date))")
+        let dateText = Text("\((context.state.date != nil) ? dateFormatter.string(from: context.state.date!) : "--")")
 
 
         if isDetailedLayout {
         if isDetailedLayout {
             VStack {
             VStack {