Ivan Valkou 5 лет назад
Родитель
Сommit
b14376fde7

+ 36 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -7,9 +7,13 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		041D1E995A6AE92E9289DC49 /* BolusDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */; };
+		0CEA2EA070AB041AF3E3745B /* BolusRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10A0C32B0DAB52726EF9B6D9 /* BolusRootView.swift */; };
 		17A9D0899046B45E87834820 /* CREditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C8D5F457B5AFF763F8CF3DF /* CREditorProvider.swift */; };
+		19434C14DF3F4816F4E4BF2E /* BolusBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77FAEF7B34EEC71B3A7B800C /* BolusBuilder.swift */; };
 		1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505E09DC17A0C3D0AF4B66FE /* ISFEditorViewModel.swift */; };
 		1D086541F369D339A74893AC /* BasalProfileEditorBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA56D2DCAB9E0A8AF24D984 /* BasalProfileEditorBuilder.swift */; };
+		23888883D4EA091C88480FF2 /* BolusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19984D62EFC0035A9E9644D /* BolusProvider.swift */; };
 		25548F1F0AA8E42FF5F96DBA /* PumpSettingsEditorBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10CAE3534904CDCA0F367017 /* PumpSettingsEditorBuilder.swift */; };
 		28089E07169488CF6DCC2A31 /* AddCarbsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86FC1CFD647CF34508AF9A3B /* AddCarbsRootView.swift */; };
 		2BE9A6FA20875F6F4F9CD461 /* PumpSettingsEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D97F14812C1AFED3621165A5 /* PumpSettingsEditorProvider.swift */; };
@@ -188,6 +192,7 @@
 		63E890B4D951EAA91C071D5C /* BasalProfileEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFF91130F2FCCC7EBBA11AD /* BasalProfileEditorViewModel.swift */; };
 		642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8782B44544F38F2B2D82C38E /* NightscoutConfigRootView.swift */; };
 		6632A0DC746872439A858B44 /* ISFEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */; };
+		69A31254F2451C20361D172F /* BolusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223EC0494F55A91E3EA69EF4 /* BolusViewModel.swift */; };
 		69B9A368029F7EB39F525422 /* CREditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA5E04A2761F6EEA6568E1 /* CREditorViewModel.swift */; };
 		6B9625766B697D1C98E455A2 /* PumpSettingsEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72778B68C3004F71F6E79BDC /* PumpSettingsEditorViewModel.swift */; };
 		72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44080E4709E3AE4B73054563 /* ConfigEditorProvider.swift */; };
@@ -602,9 +607,11 @@
 		0274EE6439B1C3ED70730D41 /* PumpSettingsEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorDataFlow.swift; sourceTree = "<group>"; };
 		073A69402BBB977D7E902997 /* AddTempTargetBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddTempTargetBuilder.swift; sourceTree = "<group>"; };
 		0CA3E609094E064C99A4752C /* PreferencesEditorViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorViewModel.swift; sourceTree = "<group>"; };
+		10A0C32B0DAB52726EF9B6D9 /* BolusRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusRootView.swift; sourceTree = "<group>"; };
 		10CAE3534904CDCA0F367017 /* PumpSettingsEditorBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorBuilder.swift; sourceTree = "<group>"; };
 		111579A6E3AC6BFA79C4DD43 /* NightscoutConfigBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigBuilder.swift; sourceTree = "<group>"; };
 		12204445D7632AF09264A979 /* PreferencesEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorDataFlow.swift; sourceTree = "<group>"; };
+		223EC0494F55A91E3EA69EF4 /* BolusViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusViewModel.swift; sourceTree = "<group>"; };
 		2AD22C985B79A2F0D2EA3D9D /* PumpConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigRootView.swift; sourceTree = "<group>"; };
 		2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigDataFlow.swift; sourceTree = "<group>"; };
 		3239D795F77DEB5676F9427A /* ISFEditorBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorBuilder.swift; sourceTree = "<group>"; };
@@ -760,6 +767,7 @@
 		6BA56D2DCAB9E0A8AF24D984 /* BasalProfileEditorBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorBuilder.swift; sourceTree = "<group>"; };
 		6F8BA8533F56BC55748CA877 /* PreferencesEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PreferencesEditorProvider.swift; sourceTree = "<group>"; };
 		72778B68C3004F71F6E79BDC /* PumpSettingsEditorViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorViewModel.swift; sourceTree = "<group>"; };
+		77FAEF7B34EEC71B3A7B800C /* BolusBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusBuilder.swift; sourceTree = "<group>"; };
 		79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorDataFlow.swift; sourceTree = "<group>"; };
 		7E22146D3DF4853786C78132 /* CREditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CREditorDataFlow.swift; sourceTree = "<group>"; };
 		86FC1CFD647CF34508AF9A3B /* AddCarbsRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddCarbsRootView.swift; sourceTree = "<group>"; };
@@ -777,6 +785,8 @@
 		B8C7F882606FF83A21BE00D8 /* PumpSettingsEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorRootView.swift; sourceTree = "<group>"; };
 		BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorDataFlow.swift; sourceTree = "<group>"; };
 		BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorRootView.swift; sourceTree = "<group>"; };
+		C19984D62EFC0035A9E9644D /* BolusProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusProvider.swift; sourceTree = "<group>"; };
+		C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusDataFlow.swift; sourceTree = "<group>"; };
 		D0BDC6993C1087310EDFC428 /* CREditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CREditorRootView.swift; sourceTree = "<group>"; };
 		D97F14812C1AFED3621165A5 /* PumpSettingsEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpSettingsEditorProvider.swift; sourceTree = "<group>"; };
 		E01C416A0792696C6911C1D7 /* PumpConfigBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigBuilder.swift; sourceTree = "<group>"; };
@@ -862,6 +872,7 @@
 				3811DE4525C9D4B800A708ED /* AuthotizedRoot */,
 				A42F1FEDFFD0DDE00AAD54D3 /* BasalProfileEditor */,
 				3811DE0425C9D32E00A708ED /* Base */,
+				C2C98283C436DB934D7E7994 /* Bolus */,
 				0610F7D6D2EC00E3BA1569F0 /* ConfigEditor */,
 				E42231DBF0DBE2B4B92D1B15 /* CREditor */,
 				3811DE2725C9D49500A708ED /* Home */,
@@ -1557,6 +1568,26 @@
 			path = AddTempTarget;
 			sourceTree = "<group>";
 		};
+		B9488883C59C31550E0B4CEC /* View */ = {
+			isa = PBXGroup;
+			children = (
+				10A0C32B0DAB52726EF9B6D9 /* BolusRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		C2C98283C436DB934D7E7994 /* Bolus */ = {
+			isa = PBXGroup;
+			children = (
+				77FAEF7B34EEC71B3A7B800C /* BolusBuilder.swift */,
+				C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */,
+				C19984D62EFC0035A9E9644D /* BolusProvider.swift */,
+				223EC0494F55A91E3EA69EF4 /* BolusViewModel.swift */,
+				B9488883C59C31550E0B4CEC /* View */,
+			);
+			path = Bolus;
+			sourceTree = "<group>";
+		};
 		D533BF261CDC1C3F871E7BFD /* NightscoutConfig */ = {
 			isa = PBXGroup;
 			children = (
@@ -2174,6 +2205,11 @@
 				5BFA1C2208114643B77F8CEB /* AddTempTargetProvider.swift in Sources */,
 				919DBD08F13BAFB180DF6F47 /* AddTempTargetViewModel.swift in Sources */,
 				8BC2F5A29AD1ED08AC0EE013 /* AddTempTargetRootView.swift in Sources */,
+				19434C14DF3F4816F4E4BF2E /* BolusBuilder.swift in Sources */,
+				041D1E995A6AE92E9289DC49 /* BolusDataFlow.swift in Sources */,
+				23888883D4EA091C88480FF2 /* BolusProvider.swift in Sources */,
+				69A31254F2451C20361D172F /* BolusViewModel.swift in Sources */,
+				0CEA2EA070AB041AF3E3745B /* BolusRootView.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

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

@@ -8,6 +8,7 @@ protocol APSManager {
     func fetchAndLoop()
     func autosense()
     func autotune()
+    func enactBolus(amount: Double)
     var pumpManager: PumpManagerUI? { get set }
     var pumpDisplayState: CurrentValueSubject<PumpDisplayState?, Never> { get }
 }
@@ -106,6 +107,20 @@ final class BaseAPSManager: APSManager, Injectable {
             .eraseToAnyPublisher()
     }
 
+    func enactBolus(amount: Double) {
+        guard let pump = pumpManager else { return }
+
+        let roundedAmout = pump.roundToSupportedBolusVolume(units: amount)
+        pump.enactBolus(units: roundedAmout, automatic: false) { result in
+            switch result {
+            case .success:
+                print("Bolus succeeded")
+            case let .failure(error):
+                print("Bolus failed with error: \(error.localizedDescription)")
+            }
+        }
+    }
+
     func autosense() {
         _ = openAPS.autosense()
     }

+ 3 - 0
FreeAPS/Sources/Modules/Bolus/BolusBuilder.swift

@@ -0,0 +1,3 @@
+extension Bolus {
+    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
+}

+ 5 - 0
FreeAPS/Sources/Modules/Bolus/BolusDataFlow.swift

@@ -0,0 +1,5 @@
+enum Bolus {
+    enum Config {}
+}
+
+protocol BolusProvider: Provider {}

+ 3 - 0
FreeAPS/Sources/Modules/Bolus/BolusProvider.swift

@@ -0,0 +1,3 @@
+extension Bolus {
+    final class Provider: BaseProvider, BolusProvider {}
+}

+ 20 - 0
FreeAPS/Sources/Modules/Bolus/BolusViewModel.swift

@@ -0,0 +1,20 @@
+import SwiftUI
+
+extension Bolus {
+    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: BolusProvider {
+        @Injected() var unlockmanager: UnlockManager!
+        @Injected() var apsManager: APSManager!
+        @Published var amount: Decimal = 0
+
+        override func subscribe() {}
+
+        func add() {
+            unlockmanager.unlock()
+                .sink { _ in } receiveValue: {
+                    self.apsManager.enactBolus(amount: Double(self.amount))
+                    self.showModal(for: nil)
+                }
+                .store(in: &lifetime)
+        }
+    }
+}

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

@@ -0,0 +1,35 @@
+import SwiftUI
+
+extension Bolus {
+    struct RootView: BaseView {
+        @EnvironmentObject var viewModel: ViewModel<Provider>
+
+        private var formatter: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.numberStyle = .decimal
+            formatter.maximumFractionDigits = 2
+            return formatter
+        }
+
+        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 {
+                    Button { viewModel.add() }
+                    label: { Text("Enact") }
+                }
+            }
+            .navigationTitle("Bolus")
+            .navigationBarTitleDisplayMode(.automatic)
+            .navigationBarItems(leading: Button("Close", action: viewModel.hideModal))
+        }
+    }
+}

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

@@ -17,5 +17,9 @@ extension Home {
         func addTempTarget() {
             showModal(for: .addTempTarget)
         }
+
+        func bolus() {
+            showModal(for: .bolus)
+        }
     }
 }

+ 6 - 0
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -19,6 +19,12 @@ extension Home {
                         .foregroundColor(.white)
                         .buttonBackground()
                 }
+                Button(action: viewModel.bolus) {
+                    Text("Bolus")
+                        .frame(maxWidth: .infinity)
+                        .foregroundColor(.white)
+                        .buttonBackground()
+                }
                 Button(action: viewModel.runLoop) {
                     Text("Run loop")
                         .frame(maxWidth: .infinity)

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

@@ -19,6 +19,7 @@ enum Screen: Identifiable {
     case preferencesEditor
     case addCarbs
     case addTempTarget
+    case bolus
 
     var id: Int { String(reflecting: self).hashValue }
 }
@@ -60,6 +61,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()
         }
     }