فهرست منبع

Release/0.1.9 (#18)

* Upload treatments in chunks

* Current time line

* Recommended Insulin Fraction

* Fix Nightscout data transfer

* Bolus recommendation

* Bump version
Ivan 5 سال پیش
والد
کامیت
be3f3b4356
27فایلهای تغییر یافته به همراه230 افزوده شده و 83 حذف شده
  1. 1 1
      FreeAPS/Resources/Config.xcconfig
  2. 2 1
      FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json
  3. 11 3
      FreeAPS/Sources/APS/APSManager.swift
  4. 8 2
      FreeAPS/Sources/APS/FetchTreatmentsManager.swift
  5. 2 2
      FreeAPS/Sources/APS/Storage/CarbsStorage.swift
  6. 3 3
      FreeAPS/Sources/APS/Storage/TempTargetsStorage.swift
  7. 1 0
      FreeAPS/Sources/Models/FreeAPSSettings.swift
  8. 2 2
      FreeAPS/Sources/Models/TempTarget.swift
  9. 6 2
      FreeAPS/Sources/Modules/AddCarbs/AddCarbsViewModel.swift
  10. 0 3
      FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetViewModel.swift
  11. 3 3
      FreeAPS/Sources/Modules/AddTempTarget/View/AddTempTargetRootView.swift
  12. 13 1
      FreeAPS/Sources/Modules/Bolus/BolusBuilder.swift
  13. 3 1
      FreeAPS/Sources/Modules/Bolus/BolusDataFlow.swift
  14. 5 1
      FreeAPS/Sources/Modules/Bolus/BolusProvider.swift
  15. 43 3
      FreeAPS/Sources/Modules/Bolus/BolusViewModel.swift
  16. 49 9
      FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift
  17. 1 1
      FreeAPS/Sources/Modules/DataTable/DataTableViewModel.swift
  18. 2 16
      FreeAPS/Sources/Modules/Home/HomeViewModel.swift
  19. 20 10
      FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift
  20. 12 7
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  21. 12 3
      FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorViewModel.swift
  22. 6 1
      FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift
  23. 5 0
      FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift
  24. 3 3
      FreeAPS/Sources/Router/Screen.swift
  25. 2 2
      FreeAPS/Sources/Services/Network/NightscoutAPI.swift
  26. 11 1
      FreeAPS/Sources/Services/Network/NightscoutManager.swift
  27. 4 2
      FreeAPS/Sources/Views/DecimalTextField.swift

+ 1 - 1
FreeAPS/Resources/Config.xcconfig

@@ -1 +1 @@
-BUILD_VERSION = 0.1.8
+BUILD_VERSION = 0.1.9

+ 2 - 1
FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json

@@ -6,5 +6,6 @@
     "isUploadEnabled": false,
     "useLocalGlucoseSource": false,
     "localGlucosePort": 8080,
-    "debugOptions": false
+    "debugOptions": false,
+    "insulinReqFraction": 0.7
 }

+ 11 - 3
FreeAPS/Sources/APS/APSManager.swift

@@ -8,7 +8,7 @@ import Swinject
 protocol APSManager {
     func heartbeat(date: Date, force: Bool)
     func autotune() -> AnyPublisher<Autotune?, Never>
-    func enactBolus(amount: Double)
+    func enactBolus(amount: Double, isSMB: Bool)
     var pumpManager: PumpManagerUI? { get set }
     var pumpDisplayState: CurrentValueSubject<PumpDisplayState?, Never> { get }
     var pumpName: CurrentValueSubject<String, Never> { get }
@@ -18,6 +18,7 @@ protocol APSManager {
     func enactTempBasal(rate: Double, duration: TimeInterval)
     func makeProfiles() -> AnyPublisher<Bool, Never>
     func determineBasal() -> AnyPublisher<Bool, Never>
+    func roundBolus(amount: Decimal) -> Decimal
 }
 
 final class BaseAPSManager: APSManager, Injectable {
@@ -218,7 +219,12 @@ final class BaseAPSManager: APSManager, Injectable {
             .eraseToAnyPublisher()
     }
 
-    func enactBolus(amount: Double) {
+    func roundBolus(amount: Decimal) -> Decimal {
+        guard let pump = pumpManager, verifyStatus() else { return amount }
+        return Decimal(pump.roundToSupportedBolusVolume(units: Double(amount)))
+    }
+
+    func enactBolus(amount: Double, isSMB: Bool) {
         guard let pump = pumpManager, verifyStatus() else { return }
 
         let roundedAmout = pump.roundToSupportedBolusVolume(units: amount)
@@ -226,7 +232,9 @@ final class BaseAPSManager: APSManager, Injectable {
             switch result {
             case .success:
                 debug(.apsManager, "Bolus succeeded")
-                _ = self.determineBasal()
+                if !isSMB {
+                    self.determineBasal().sink { _ in }.store(in: &self.lifetime)
+                }
             case let .failure(error):
                 debug(.apsManager, "Bolus failed with error: \(error.localizedDescription)")
             }

+ 8 - 2
FreeAPS/Sources/APS/FetchTreatmentsManager.swift

@@ -31,8 +31,14 @@ final class BaseFetchTreatmentsManager: FetchTreatmentsManager, Injectable {
                 ).eraseToAnyPublisher()
             }
             .sink { carbs, targets in
-                self.carbsStorage.storeCarbs(carbs)
-                self.tempTargetsStorage.storeTempTargets(targets)
+                let filteredCarbs = carbs.filter { !($0.enteredBy?.contains(CarbsEntry.manual) ?? false) }
+                if filteredCarbs.isNotEmpty {
+                    self.carbsStorage.storeCarbs(filteredCarbs)
+                }
+                let filteredTargets = targets.filter { !($0.enteredBy?.contains(TempTarget.manual) ?? false) }
+                if filteredTargets.isNotEmpty {
+                    self.tempTargetsStorage.storeTempTargets(filteredTargets)
+                }
             }
             .store(in: &lifetime)
         timer.resume()

+ 2 - 2
FreeAPS/Sources/APS/Storage/CarbsStorage.swift

@@ -42,11 +42,11 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
 
     func syncDate() -> Date {
         guard let events = storage.retrieve(OpenAPS.Monitor.carbHistory, as: [CarbsEntry].self),
-              let recent = events.filter({ $0.enteredBy != CarbsEntry.manual }).first
+              let recent = events.filter({ !($0.enteredBy?.contains(CarbsEntry.manual) ?? false) }).first
         else {
             return Date().addingTimeInterval(-1.days.timeInterval)
         }
-        return recent.createdAt.addingTimeInterval(-6.minutes.timeInterval)
+        return recent.createdAt
     }
 
     func recent() -> [CarbsEntry] {

+ 3 - 3
FreeAPS/Sources/APS/Storage/TempTargetsStorage.swift

@@ -51,11 +51,11 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
 
     func syncDate() -> Date {
         guard let events = storage.retrieve(OpenAPS.Settings.tempTargets, as: [TempTarget].self),
-              let recent = events.filter({ $0.enteredBy != TempTarget.manual }).first
+              let recent = events.filter({ !($0.enteredBy?.contains(TempTarget.manual) ?? false) }).first
         else {
             return Date().addingTimeInterval(-1.days.timeInterval)
         }
-        return recent.createdAt.addingTimeInterval(-6.minutes.timeInterval)
+        return recent.createdAt
     }
 
     func recent() -> [TempTarget] {
@@ -75,7 +75,7 @@ final class BaseTempTargetsStorage: TempTargetsStorage, Injectable {
     func nightscoutTretmentsNotUploaded() -> [NigtscoutTreatment] {
         let uploaded = storage.retrieve(OpenAPS.Nightscout.uploadedTempTargets, as: [NigtscoutTreatment].self) ?? []
 
-        let eventsManual = recent().filter { $0.enteredBy == CarbsEntry.manual }
+        let eventsManual = recent().filter { $0.enteredBy == TempTarget.manual }
         let treatments = eventsManual.map {
             NigtscoutTreatment(
                 duration: Int($0.duration),

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

@@ -9,4 +9,5 @@ struct FreeAPSSettings: JSON {
     var useLocalGlucoseSource: Bool?
     var localGlucosePort: Int?
     var debugOptions: Bool?
+    var insulinReqFraction: Decimal?
 }

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

@@ -4,8 +4,8 @@ struct TempTarget: JSON, Identifiable, Equatable {
     var id = UUID().uuidString
     let name: String?
     var createdAt: Date
-    let targetTop: Decimal
-    let targetBottom: Decimal
+    let targetTop: Decimal?
+    let targetBottom: Decimal?
     let duration: Decimal
     let enteredBy: String?
     let reason: String?

+ 6 - 2
FreeAPS/Sources/Modules/AddCarbs/AddCarbsViewModel.swift

@@ -10,11 +10,15 @@ extension AddCarbs {
         override func subscribe() {}
 
         func add() {
+            guard carbs > 0 else {
+                showModal(for: nil)
+                return
+            }
+
             carbsStorage.storeCarbs([
                 CarbsEntry(createdAt: date, carbs: carbs, enteredBy: CarbsEntry.manual)
             ])
-            apsManager.determineBasal().sink { _ in }.store(in: &lifetime)
-            showModal(for: nil)
+            showModal(for: .bolus(waitForDuggestion: true))
         }
     }
 }

+ 0 - 3
FreeAPS/Sources/Modules/AddTempTarget/AddTempTargetViewModel.swift

@@ -41,7 +41,6 @@ extension AddTempTarget {
                 reason: TempTarget.custom
             )
             storage.storeTempTargets([entry])
-            apsManager.determineBasal().sink { _ in }.store(in: &lifetime)
 
             showModal(for: nil)
         }
@@ -57,7 +56,6 @@ extension AddTempTarget {
                 reason: TempTarget.cancel
             )
             storage.storeTempTargets([entry])
-            apsManager.determineBasal().sink { _ in }.store(in: &lifetime)
 
             showModal(for: nil)
         }
@@ -91,7 +89,6 @@ extension AddTempTarget {
             if var preset = presets.first(where: { $0.id == id }) {
                 preset.createdAt = Date()
                 storage.storeTempTargets([preset])
-                apsManager.determineBasal().sink { _ in }.store(in: &lifetime)
                 showModal(for: nil)
             }
         }

+ 3 - 3
FreeAPS/Sources/Modules/AddTempTarget/View/AddTempTargetRootView.swift

@@ -78,8 +78,8 @@ extension AddTempTarget {
             var low = preset.targetBottom
             var high = preset.targetTop
             if viewModel.units == .mmolL {
-                low = low.asMmolL
-                high = high.asMmolL
+                low = low?.asMmolL
+                high = high?.asMmolL
             }
             return HStack {
                 VStack {
@@ -89,7 +89,7 @@ extension AddTempTarget {
                     }
                     HStack {
                         Text(
-                            "\(formatter.string(from: low as NSNumber)!) - \(formatter.string(from: high as NSNumber)!)"
+                            "\(formatter.string(from: (low ?? 0) as NSNumber)!) - \(formatter.string(from: (high ?? 0) as NSNumber)!)"
                         )
                         .foregroundColor(.secondary)
                         .font(.caption)

+ 13 - 1
FreeAPS/Sources/Modules/Bolus/BolusBuilder.swift

@@ -1,3 +1,15 @@
+import Swinject
+
 extension Bolus {
-    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
+    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {
+        private let waitForSuggestion: Bool
+        init(resolver: Resolver, waitForSuggestion: Bool) {
+            self.waitForSuggestion = waitForSuggestion
+            super.init(resolver: resolver)
+        }
+
+        override func buildViewModel() -> Bolus.ViewModel<Bolus.Provider> {
+            ViewModel(provider: Provider(resolver: resolver), resolver: resolver, waitForSuggestion: waitForSuggestion)
+        }
+    }
 }

+ 3 - 1
FreeAPS/Sources/Modules/Bolus/BolusDataFlow.swift

@@ -2,4 +2,6 @@ enum Bolus {
     enum Config {}
 }
 
-protocol BolusProvider: Provider {}
+protocol BolusProvider: Provider {
+    var suggestion: Suggestion? { get }
+}

+ 5 - 1
FreeAPS/Sources/Modules/Bolus/BolusProvider.swift

@@ -1,3 +1,7 @@
 extension Bolus {
-    final class Provider: BaseProvider, BolusProvider {}
+    final class Provider: BaseProvider, BolusProvider {
+        var suggestion: Suggestion? {
+            storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self)
+        }
+    }
 }

+ 43 - 3
FreeAPS/Sources/Modules/Bolus/BolusViewModel.swift

@@ -1,21 +1,61 @@
 import SwiftUI
+import Swinject
 
 extension Bolus {
     class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: BolusProvider {
         @Injected() var unlockmanager: UnlockManager!
         @Injected() var apsManager: APSManager!
+        @Injected() var broadcaster: Broadcaster!
+        @Injected() var settingsManager: SettingsManager!
         @Published var amount: Decimal = 0
+        @Published var inslinRecommended: Decimal = 0
+        @Published var inslinRequired: Decimal = 0
+        @Published var waitForSuggestion: Bool
+        let waitForSuggestionInitial: Bool
 
-        override func subscribe() {}
+        init(provider: Provider, resolver: Resolver, waitForSuggestion: Bool) {
+            self.waitForSuggestion = waitForSuggestion
+            waitForSuggestionInitial = waitForSuggestion
+            super.init(provider: provider, resolver: resolver)
+        }
+
+        required init(provider _: Provider, resolver _: Resolver) {
+            error(.default, "init(provider:resolver:) has not been implemented")
+        }
+
+        override func subscribe() {
+            setupInsulinRequired()
+            broadcaster.register(SuggestionObserver.self, observer: self)
+        }
 
         func add() {
-            guard amount > 0 else { return }
+            guard amount > 0 else {
+                showModal(for: nil)
+                return
+            }
             unlockmanager.unlock()
                 .sink { _ in } receiveValue: {
-                    self.apsManager.enactBolus(amount: Double(self.amount))
+                    self.apsManager.enactBolus(amount: Double(self.amount), isSMB: false)
                     self.showModal(for: nil)
                 }
                 .store(in: &lifetime)
         }
+
+        func setupInsulinRequired() {
+            DispatchQueue.main.async {
+                self.inslinRequired = self.provider.suggestion?.insulinReq ?? 0
+                self.inslinRecommended = self.apsManager
+                    .roundBolus(amount: max(self.inslinRequired * (self.settingsManager.settings.insulinReqFraction ?? 0.7), 0))
+            }
+        }
+    }
+}
+
+extension Bolus.ViewModel: SuggestionObserver {
+    func suggestionDidUpdate(_: Suggestion) {
+        DispatchQueue.main.async {
+            self.waitForSuggestion = false
+        }
+        setupInsulinRequired()
     }
 }

+ 49 - 9
FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift

@@ -13,18 +13,58 @@ extension Bolus {
 
         var body: some View {
             Form {
-                Section {
-                    HStack {
-                        Text("Amount")
-                        Spacer()
-                        DecimalTextField("0", value: $viewModel.amount, formatter: formatter, autofocus: true, cleanInput: true)
-                        Text("U").foregroundColor(.secondary)
+                Section(header: Text("Recommendation")) {
+                    if viewModel.waitForSuggestion {
+                        HStack {
+                            Text("Wait please").foregroundColor(.secondary)
+                            Spacer()
+                            ProgressView()
+                        }
+                    } else {
+                        HStack {
+                            Text("Insulin required").foregroundColor(.secondary)
+                            Spacer()
+                            Text(formatter.string(from: viewModel.inslinRequired as NSNumber)! + " U").foregroundColor(.secondary)
+                        }.contentShape(Rectangle())
+                            .onTapGesture {
+                                viewModel.amount = viewModel.inslinRecommended
+                            }
+                        HStack {
+                            Text("Insulin recommended")
+                            Spacer()
+                            Text(formatter.string(from: viewModel.inslinRecommended as NSNumber)! + " U")
+                        }.contentShape(Rectangle())
+                            .onTapGesture {
+                                viewModel.amount = viewModel.inslinRecommended
+                            }
                     }
                 }
 
-                Section {
-                    Button { viewModel.add() }
-                    label: { Text("Enact") }
+                if !viewModel.waitForSuggestion {
+                    Section(header: Text("Bolus")) {
+                        HStack {
+                            Text("Amount")
+                            Spacer()
+                            DecimalTextField(
+                                "0",
+                                value: $viewModel.amount,
+                                formatter: formatter,
+                                autofocus: true,
+                                cleanInput: true
+                            )
+                            Text("U").foregroundColor(.secondary)
+                        }
+                    }
+
+                    Section {
+                        Button { viewModel.add() }
+                        label: { Text("Enact bolus") }
+
+                        if viewModel.waitForSuggestionInitial {
+                            Button { viewModel.showModal(for: nil) }
+                            label: { Text("Continue without bolus") }
+                        }
+                    }
                 }
             }
             .navigationTitle("Enact Bolus")

+ 1 - 1
FreeAPS/Sources/Modules/DataTable/DataTableViewModel.swift

@@ -51,7 +51,7 @@ extension DataTable {
                             units: units,
                             type: .tempTarget,
                             date: $0.createdAt,
-                            amount: $0.targetBottom,
+                            amount: $0.targetBottom ?? 0,
                             secondAmount: $0.targetTop,
                             duration: $0.duration
                         )

+ 2 - 16
FreeAPS/Sources/Modules/Home/HomeViewModel.swift

@@ -115,22 +115,6 @@ extension Home {
             provider.heartbeatNow()
         }
 
-        func addTempTarget() {
-            showModal(for: .addTempTarget)
-        }
-
-        func manualTampBasal() {
-            showModal(for: .manualTempBasal)
-        }
-
-        func bolus() {
-            showModal(for: .bolus)
-        }
-
-        func settings() {
-            showModal(for: .settings)
-        }
-
         private func setupGlucose() {
             DispatchQueue.main.async {
                 self.glucose = self.provider.filteredGlucose(hours: self.filteredHours)
@@ -293,10 +277,12 @@ extension Home.ViewModel:
 
     func tempTargetsDidUpdate(_: [TempTarget]) {
         setupTempTargets()
+        apsManager.determineBasal().sink { _ in }.store(in: &lifetime)
     }
 
     func carbsDidUpdate(_: [CarbsEntry]) {
         setupCarbs()
+        apsManager.determineBasal().sink { _ in }.store(in: &lifetime)
     }
 
     func enactedSuggestionDidUpdate(_ suggestion: Suggestion) {

+ 20 - 10
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -42,6 +42,7 @@ struct MainChartView: View {
     @Binding var basalProfile: [BasalProfileEntry]
     @Binding var tempTargets: [TempTarget]
     @Binding var carbs: [CarbsEntry]
+    @Binding var timerDate: Date
     let units: GlucoseUnits
 
     @State var didAppearTrigger = false
@@ -196,16 +197,25 @@ struct MainChartView: View {
     }
 
     private func xGridView(fullSize: CGSize) -> some View {
-        Path { path in
-            for hour in 0 ..< hours + hours {
-                let x = firstHourPosition(viewWidth: fullSize.width) +
-                    oneSecondStep(viewWidth: fullSize.width) *
-                    CGFloat(hour) * CGFloat(1.hours.timeInterval)
+        ZStack {
+            Path { path in
+                for hour in 0 ..< hours + hours {
+                    let x = firstHourPosition(viewWidth: fullSize.width) +
+                        oneSecondStep(viewWidth: fullSize.width) *
+                        CGFloat(hour) * CGFloat(1.hours.timeInterval)
+                    path.move(to: CGPoint(x: x, y: 0))
+                    path.addLine(to: CGPoint(x: x, y: fullSize.height - 20))
+                }
+            }
+            .stroke(Color.secondary, lineWidth: 0.2)
+
+            Path { path in
+                let x = timeToXCoordinate(timerDate.timeIntervalSince1970, fullSize: fullSize)
                 path.move(to: CGPoint(x: x, y: 0))
                 path.addLine(to: CGPoint(x: x, y: fullSize.height - 20))
             }
+            .stroke(Color.secondary, style: StrokeStyle(lineWidth: 0.2, dash: [5]))
         }
-        .stroke(Color.secondary, lineWidth: 0.2)
     }
 
     private func timeLabelsView(fullSize: CGSize) -> some View {
@@ -505,12 +515,12 @@ extension MainChartView {
         calculationQueue.async {
             var rects = tempTargets.map { tempTarget -> CGRect in
                 let x0 = timeToXCoordinate(tempTarget.createdAt.timeIntervalSince1970, fullSize: fullSize)
-                let y0 = glucoseToYCoordinate(Int(tempTarget.targetTop), fullSize: fullSize)
+                let y0 = glucoseToYCoordinate(Int(tempTarget.targetTop ?? 0), fullSize: fullSize)
                 let x1 = timeToXCoordinate(
                     tempTarget.createdAt.timeIntervalSince1970 + Int(tempTarget.duration).minutes.timeInterval,
                     fullSize: fullSize
                 )
-                let y1 = glucoseToYCoordinate(Int(tempTarget.targetBottom), fullSize: fullSize)
+                let y1 = glucoseToYCoordinate(Int(tempTarget.targetBottom ?? 0), fullSize: fullSize)
                 return CGRect(
                     x: x0,
                     y: y0 - 3,
@@ -655,11 +665,11 @@ extension MainChartView {
     }
 
     private func maxTargetValue() -> Int? {
-        tempTargets.map(\.targetTop).filter { $0 > 0 }.max().map(Int.init)
+        tempTargets.map { $0.targetTop ?? 0 }.filter { $0 > 0 }.max().map(Int.init)
     }
 
     private func minTargetValue() -> Int? {
-        tempTargets.map(\.targetBottom).filter { $0 > 0 }.min().map(Int.init)
+        tempTargets.map { $0.targetBottom ?? 0 }.filter { $0 > 0 }.min().map(Int.init)
     }
 
     private func glucoseToCoordinate(_ glucoseEntry: BloodGlucose, fullSize: CGSize) -> CGPoint {

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

@@ -91,7 +91,7 @@ extension Home {
                     if viewModel.units == .mmolL {
                         Text(
                             targetFormatter
-                                .string(from: tempTarget.targetBottom.asMmolL as NSNumber)!
+                                .string(from: (tempTarget.targetBottom?.asMmolL ?? 0) as NSNumber)!
                         )
                         .font(.caption)
                         .foregroundColor(.secondary)
@@ -100,7 +100,8 @@ extension Home {
                                 .foregroundColor(.secondary)
                             Text(
                                 targetFormatter
-                                    .string(from: tempTarget.targetTop.asMmolL as NSNumber)! + " \(viewModel.units.rawValue)"
+                                    .string(from: (tempTarget.targetTop?.asMmolL ?? 0) as NSNumber)! +
+                                    " \(viewModel.units.rawValue)"
                             )
                             .font(.caption)
                             .foregroundColor(.secondary)
@@ -110,15 +111,18 @@ extension Home {
                         }
 
                     } else {
-                        Text(targetFormatter.string(from: tempTarget.targetBottom as NSNumber)!)
+                        Text(targetFormatter.string(from: (tempTarget.targetBottom ?? 0) as NSNumber)!)
                             .font(.caption)
                             .foregroundColor(.secondary)
                         if tempTarget.targetBottom != tempTarget.targetTop {
                             Text("-").font(.caption)
                                 .foregroundColor(.secondary)
-                            Text(targetFormatter.string(from: tempTarget.targetTop as NSNumber)! + " \(viewModel.units.rawValue)")
-                                .font(.caption)
-                                .foregroundColor(.secondary)
+                            Text(
+                                targetFormatter
+                                    .string(from: (tempTarget.targetTop ?? 0) as NSNumber)! + " \(viewModel.units.rawValue)"
+                            )
+                            .font(.caption)
+                            .foregroundColor(.secondary)
                         } else {
                             Text(viewModel.units.rawValue).font(.caption)
                                 .foregroundColor(.secondary)
@@ -175,6 +179,7 @@ extension Home {
                         basalProfile: $viewModel.basalProfile,
                         tempTargets: $viewModel.tempTargets,
                         carbs: $viewModel.carbs,
+                        timerDate: $viewModel.timerDate,
                         units: viewModel.units
                     )
                     .padding(.bottom)
@@ -202,7 +207,7 @@ extension Home {
                                     .frame(width: 24, height: 24)
                             }.foregroundColor(.loopYellow)
                             Spacer()
-                            Button { viewModel.showModal(for: .bolus) }
+                            Button { viewModel.showModal(for: .bolus(waitForDuggestion: false)) }
                             label: {
                                 Image("bolus")
                                     .renderingMode(.template)

+ 12 - 3
FreeAPS/Sources/Modules/PreferencesEditor/PreferencesEditorViewModel.swift

@@ -9,10 +9,11 @@ extension PreferencesEditor {
         private(set) var preferences = Preferences()
         @Published var unitsIndex = 1
         @Published var allowAnnouncements = false
+        @Published var insulinReqFraction: Decimal = 0.7
 
         @Published var decimalFields: [Field<Decimal>] = []
         @Published var boolFields: [Field<Bool>] = []
-        @Published var insulinCirveField = Field<InsulinCurve>(
+        @Published var insulinCurveField = Field<InsulinCurve>(
             displayName: "Insulin curve",
             keypath: \.curve,
             value: .rapidActing
@@ -22,8 +23,9 @@ extension PreferencesEditor {
             preferences = provider.preferences
             unitsIndex = settingsManager.settings.units == .mgdL ? 0 : 1
             allowAnnouncements = settingsManager.settings.allowAnnouncements
-            insulinCirveField.value = preferences.curve
-            insulinCirveField.settable = self
+            insulinCurveField.value = preferences.curve
+            insulinCurveField.settable = self
+            insulinReqFraction = settingsManager.settings.insulinReqFraction ?? 0.7
 
             $unitsIndex
                 .removeDuplicates()
@@ -39,6 +41,13 @@ extension PreferencesEditor {
                 }
                 .store(in: &lifetime)
 
+            $insulinReqFraction
+                .removeDuplicates()
+                .sink { [weak self] fraction in
+                    self?.settingsManager.settings.insulinReqFraction = fraction
+                }
+                .store(in: &lifetime)
+
             boolFields = [
                 Field(
                     displayName: "Rewind Resets Autosens",

+ 6 - 1
FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift

@@ -19,10 +19,15 @@ extension PreferencesEditor {
                     }
 
                     Toggle("Remote control", isOn: $viewModel.allowAnnouncements)
+
+                    HStack {
+                        Text("Recommended Insulin Fraction")
+                        DecimalTextField("", value: $viewModel.insulinReqFraction, formatter: formatter)
+                    }
                 }
 
                 Section(header: Text("OpenAPS")) {
-                    Picker(selection: $viewModel.insulinCirveField.value, label: Text(viewModel.insulinCirveField.displayName)) {
+                    Picker(selection: $viewModel.insulinCurveField.value, label: Text(viewModel.insulinCurveField.displayName)) {
                         ForEach(InsulinCurve.allCases) { v in
                             Text(v.rawValue).tag(v)
                         }

+ 5 - 0
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -75,6 +75,11 @@ extension Settings {
                             Text("Autotune").chevronCell()
                                 .navigationLink(to: .configEditor(file: OpenAPS.Settings.autotune), from: self)
                         }
+
+                        Group {
+                            Text("Target presets").chevronCell()
+                                .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.tempTargetsPresets), from: self)
+                        }
                     }
                 }
             }

+ 3 - 3
FreeAPS/Sources/Router/Screen.swift

@@ -19,7 +19,7 @@ enum Screen: Identifiable {
     case preferencesEditor
     case addCarbs
     case addTempTarget
-    case bolus
+    case bolus(waitForDuggestion: Bool)
     case manualTempBasal
     case autotuneConfig
     case dataTable
@@ -64,8 +64,8 @@ extension Screen {
             return AddCarbs.Builder(resolver: resolver).buildView()
         case .addTempTarget:
             return AddTempTarget.Builder(resolver: resolver).buildView()
-        case .bolus:
-            return Bolus.Builder(resolver: resolver).buildView()
+        case let .bolus(waitForSuggestion):
+            return Bolus.Builder(resolver: resolver, waitForSuggestion: waitForSuggestion).buildView()
         case .manualTempBasal:
             return ManualTempBasal.Builder(resolver: resolver).buildView()
         case .autotuneConfig:

+ 2 - 2
FreeAPS/Sources/Services/Network/NightscoutAPI.swift

@@ -107,7 +107,7 @@ extension NightscoutAPI {
         ]
         if let date = sinceDate {
             let dateItem = URLQueryItem(
-                name: "find[created_at][$gte]",
+                name: "find[created_at][$gt]",
                 value: Formatter.iso8601withFractionalSeconds.string(from: date)
             )
             components.queryItems?.append(dateItem)
@@ -176,7 +176,7 @@ extension NightscoutAPI {
         ]
         if let date = sinceDate {
             let dateItem = URLQueryItem(
-                name: "find[created_at][$gte]",
+                name: "find[created_at][$gt]",
                 value: Formatter.iso8601withFractionalSeconds.string(from: date)
             )
             components.queryItems?.append(dateItem)

+ 11 - 1
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -225,7 +225,17 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
         }
 
         processQueue.async {
-            nightscout.uploadTreatments(treatments)
+            treatments.chunks(ofCount: 100)
+                .map { chunk -> AnyPublisher<Void, Error> in
+                    nightscout.uploadTreatments(Array(chunk))
+                }
+                .reduce(
+                    Just(()).setFailureType(to: Error.self)
+                        .eraseToAnyPublisher()
+                ) { (result, next) -> AnyPublisher<Void, Error> in
+                    Publishers.Concatenate(prefix: result, suffix: next).eraseToAnyPublisher()
+                }
+                .dropFirst()
                 .sink { completion in
                     switch completion {
                     case .finished:

+ 4 - 2
FreeAPS/Sources/Views/DecimalTextField.swift

@@ -62,8 +62,10 @@ struct DecimalTextField: UIViewRepresentable {
         return textfield
     }
 
-    func updateUIView(_: UITextField, context _: Context) {
-//        textField.text = formatter.string(for: value)
+    func updateUIView(_ textField: UITextField, context _: Context) {
+        if value != 0 {
+            textField.text = formatter.string(for: value)
+        }
     }
 
     func makeCoordinator() -> Coordinator {