فهرست منبع

Allow bolusing with meals, option for fat/protein

Jonas Björkert 1 سال پیش
والد
کامیت
0a2b54b199

+ 9 - 0
LoopFollow/Remote/PushNotificationManager.swift

@@ -106,6 +106,7 @@ class PushNotificationManager {
         carbs: HKQuantity,
         protein: HKQuantity,
         fat: HKQuantity,
+        bolusAmount: HKQuantity,
         scheduledTime: Date?,
         completion: @escaping (Bool, String?) -> Void
     ) {
@@ -114,10 +115,17 @@ class PushNotificationManager {
             return valueInGrams > 0 ? Int(valueInGrams) : nil
         }
 
+        func convertToOptionalDecimal(_ quantity: HKQuantity?) -> Decimal? {
+            guard let quantity = quantity else { return nil }
+            let value = quantity.doubleValue(for: .internationalUnit())
+            return value > 0 ? Decimal(value) : nil
+        }
+
         let carbsValue = convertToOptionalInt(carbs)
         let proteinValue = convertToOptionalInt(protein)
         let fatValue = convertToOptionalInt(fat)
         let scheduledTimeInterval: TimeInterval? = scheduledTime?.timeIntervalSince1970
+        let bolusAmountValue = convertToOptionalDecimal(bolusAmount)
 
         guard carbsValue != nil || proteinValue != nil || fatValue != nil else {
             completion(false, "No nutrient data provided. At least one of carbs, fat, or protein must be greater than 0.")
@@ -127,6 +135,7 @@ class PushNotificationManager {
         let message = PushMessage(
             user: user,
             commandType: .meal,
+            bolusAmount: bolusAmountValue,
             carbs: carbsValue,
             protein: proteinValue,
             fat: fatValue,

+ 9 - 0
LoopFollow/Remote/Settings/RemoteSettingsView.swift

@@ -175,6 +175,15 @@ struct RemoteSettingsView: View {
                                 .foregroundColor(.secondary)
                         }
                     }
+
+                    // Meal Section
+                    Section(header: Text("Meal Settings")) {
+                        Toggle("Meal with Bolus", isOn: $viewModel.mealWithBolus)
+                            .toggleStyle(SwitchToggleStyle())
+
+                        Toggle("Meal with Fat/Protein", isOn: $viewModel.mealWithFatProtein)
+                            .toggleStyle(SwitchToggleStyle())
+                    }
                 }
             }
             .navigationBarTitle("Remote Settings", displayMode: .inline)

+ 12 - 0
LoopFollow/Remote/Settings/RemoteSettingsViewModel.swift

@@ -21,6 +21,8 @@ class RemoteSettingsViewModel: ObservableObject {
     @Published var maxCarbs: HKQuantity
     @Published var maxProtein: HKQuantity
     @Published var maxFat: HKQuantity
+    @Published var mealWithBolus: Bool
+    @Published var mealWithFatProtein: Bool
 
     private var storage = Storage.shared
     private var cancellables = Set<AnyCancellable>()
@@ -35,6 +37,8 @@ class RemoteSettingsViewModel: ObservableObject {
         self.maxCarbs = storage.maxCarbs.value
         self.maxProtein = storage.maxProtein.value
         self.maxFat = storage.maxFat.value
+        self.mealWithBolus = storage.mealWithBolus.value
+        self.mealWithFatProtein = storage.mealWithFatProtein.value
 
         setupBindings()
     }
@@ -75,5 +79,13 @@ class RemoteSettingsViewModel: ObservableObject {
         $maxFat
             .sink { [weak self] in self?.storage.maxFat.value = $0 }
             .store(in: &cancellables)
+
+        $mealWithBolus
+            .sink { [weak self] in self?.storage.mealWithBolus.value = $0 }
+            .store(in: &cancellables)
+
+        $mealWithFatProtein
+            .sink { [weak self] in self?.storage.mealWithFatProtein.value = $0 }
+            .store(in: &cancellables)
     }
 }

+ 54 - 24
LoopFollow/Remote/TRC/MealView.swift

@@ -14,16 +14,21 @@ struct MealView: View {
     @State private var carbs = HKQuantity(unit: .gram(), doubleValue: 0.0)
     @State private var protein = HKQuantity(unit: .gram(), doubleValue: 0.0)
     @State private var fat = HKQuantity(unit: .gram(), doubleValue: 0.0)
+    @State private var bolusAmount = HKQuantity(unit: .internationalUnit(), doubleValue: 0.0)
 
     private let pushNotificationManager = PushNotificationManager()
 
     @ObservedObject private var maxCarbs = Storage.shared.maxCarbs
     @ObservedObject private var maxProtein = Storage.shared.maxProtein
     @ObservedObject private var maxFat = Storage.shared.maxFat
+    @ObservedObject private var mealWithBolus = Storage.shared.mealWithBolus
+    @ObservedObject private var mealWithFatProtein = Storage.shared.mealWithFatProtein
+    @ObservedObject private var maxBolus = Storage.shared.maxBolus
 
     @FocusState private var carbsFieldIsFocused: Bool
     @FocusState private var proteinFieldIsFocused: Bool
     @FocusState private var fatFieldIsFocused: Bool
+    @FocusState private var bolusFieldIsFocused: Bool
 
     @State private var showAlert: Bool = false
     @State private var alertType: AlertType? = nil
@@ -58,31 +63,48 @@ struct MealView: View {
                             }
                         )
 
-                        HKQuantityInputView(
-                            label: "Protein",
-                            quantity: $protein,
-                            unit: .gram(),
-                            maxLength: 4,
-                            minValue: HKQuantity(unit: .gram(), doubleValue: 0),
-                            maxValue: maxProtein.value,
-                            isFocused: $proteinFieldIsFocused,
-                            onValidationError: { message in
-                                handleValidationError(message)
-                            }
-                        )
+                        if mealWithFatProtein.value {
+                            HKQuantityInputView(
+                                label: "Protein",
+                                quantity: $protein,
+                                unit: .gram(),
+                                maxLength: 4,
+                                minValue: HKQuantity(unit: .gram(), doubleValue: 0),
+                                maxValue: maxProtein.value,
+                                isFocused: $proteinFieldIsFocused,
+                                onValidationError: { message in
+                                    handleValidationError(message)
+                                }
+                            )
 
-                        HKQuantityInputView(
-                            label: "Fat",
-                            quantity: $fat,
-                            unit: .gram(),
-                            maxLength: 4,
-                            minValue: HKQuantity(unit: .gram(), doubleValue: 0),
-                            maxValue: maxFat.value,
-                            isFocused: $fatFieldIsFocused,
-                            onValidationError: { message in
-                                handleValidationError(message)
-                            }
-                        )
+                            HKQuantityInputView(
+                                label: "Fat",
+                                quantity: $fat,
+                                unit: .gram(),
+                                maxLength: 4,
+                                minValue: HKQuantity(unit: .gram(), doubleValue: 0),
+                                maxValue: maxFat.value,
+                                isFocused: $fatFieldIsFocused,
+                                onValidationError: { message in
+                                    handleValidationError(message)
+                                }
+                            )
+                        }
+
+                        if mealWithBolus.value {
+                            HKQuantityInputView(
+                                label: "Bolus Amount",
+                                quantity: $bolusAmount,
+                                unit: .internationalUnit(),
+                                maxLength: 4,
+                                minValue: HKQuantity(unit: .internationalUnit(), doubleValue: 0.05),
+                                maxValue: maxBolus.value,
+                                isFocused: $bolusFieldIsFocused,
+                                onValidationError: { message in
+                                    handleValidationError(message)
+                                }
+                            )
+                        }
                     }
 
                     Section(header: Text("Schedule")) {
@@ -136,8 +158,10 @@ struct MealView: View {
                     let carbsAmount = carbs.doubleValue(for: HKUnit.gram())
                     let proteinAmount = protein.doubleValue(for: HKUnit.gram())
                     let fatAmount = fat.doubleValue(for: HKUnit.gram())
+                    let bolusAmount = bolusAmount.doubleValue(for: .internationalUnit())
 
                     var message = "Are you sure you want to send the meal data"
+
                     if let selectedTime = selectedTime {
                         let timeFormatter = DateFormatter()
                         timeFormatter.timeStyle = .short
@@ -159,6 +183,10 @@ struct MealView: View {
                         message += String(format: "\nFat: %.0f g", fatAmount)
                     }
 
+                    if bolusAmount > 0 {
+                        message += String(format: "\nBolus: %.2f U", bolusAmount)
+                    }
+
                     return Alert(
                         title: Text("Confirm Meal"),
                         message: Text(message),
@@ -169,6 +197,7 @@ struct MealView: View {
                         }),
                         secondaryButton: .cancel()
                     )
+
                 case .statusSuccess:
                     return Alert(
                         title: Text("Status"),
@@ -218,6 +247,7 @@ struct MealView: View {
             carbs: carbs,
             protein: protein,
             fat: fat,
+            bolusAmount: bolusAmount,
             scheduledTime: scheduledDate
         ) { success, errorMessage in
             DispatchQueue.main.async {

+ 3 - 0
LoopFollow/Storage/Storage.swift

@@ -25,6 +25,9 @@ class Storage {
     var maxProtein = SecureStorageValue<HKQuantity>(key: "maxProtein", defaultValue: HKQuantity(unit: .gram(), doubleValue: 30.0))
     var maxFat = SecureStorageValue<HKQuantity>(key: "maxFat", defaultValue: HKQuantity(unit: .gram(), doubleValue: 30.0))
 
+    var mealWithBolus = StorageValue<Bool>(key: "mealWithBolus", defaultValue: false)
+    var mealWithFatProtein = StorageValue<Bool>(key: "mealWithFatProtein", defaultValue: false)
+
     var cachedJWT = StorageValue<String?>(key: "cachedJWT", defaultValue: nil)
     var jwtExpirationDate = StorageValue<Date?>(key: "jwtExpirationDate", defaultValue: nil)