Browse Source

Fat & Protein on Watch

* Add Fat and Protein from Watch app.
Meal presets will be included in another future PR.
Jon B Mårtensson 2 years ago
parent
commit
4efe56b60f

+ 34 - 0
FreeAPS/Resources/Assets.xcassets/Colors/darkGray.colorset/Contents.json

@@ -0,0 +1,34 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "extended-gray",
+        "components" : {
+          "alpha" : "1.000",
+          "white" : "0.145"
+        }
+      },
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "color" : {
+        "color-space" : "extended-gray",
+        "components" : {
+          "alpha" : "1.000",
+          "white" : "0.145"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 83 - 11
FreeAPS/Sources/APS/Storage/CarbsStorage.swift

@@ -19,6 +19,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
     private let processQueue = DispatchQueue(label: "BaseCarbsStorage.processQueue")
     @Injected() private var storage: FileStorage!
     @Injected() private var broadcaster: Broadcaster!
+    @Injected() private var settings: SettingsManager!
 
     let coredataContext = CoreDataStack.shared.persistentContainer.newBackgroundContext()
 
@@ -26,25 +27,97 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
         injectServices(resolver)
     }
 
-    func storeCarbs(_ carbs: [CarbsEntry]) {
+    func storeCarbs(_ entries: [CarbsEntry]) {
         processQueue.sync {
             let file = OpenAPS.Monitor.carbHistory
             var uniqEvents: [CarbsEntry] = []
-            self.storage.transaction { storage in
-                storage.append(carbs, to: file, uniqBy: \.createdAt)
-                uniqEvents = storage.retrieve(file, as: [CarbsEntry].self)?
-                    .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
-                    .sorted { $0.createdAt > $1.createdAt } ?? []
-                storage.save(Array(uniqEvents), as: file)
+
+            let fat = entries.last?.fat ?? 0
+            let protein = entries.last?.protein ?? 0
+
+            if fat > 0 || protein > 0 {
+                // -------------------------- FPU--------------------------------------
+                let interval = settings.settings.minuteInterval // Interval betwwen carbs
+                let timeCap = settings.settings.timeCap // Max Duration
+                let adjustment = settings.settings.individualAdjustmentFactor
+                let delay = settings.settings.delay // Tme before first future carb entry
+                let kcal = protein * 4 + fat * 9
+                let carbEquivalents = (kcal / 10) * adjustment
+                let fpus = carbEquivalents / 10
+                // Duration in hours used for extended boluses with Warsaw Method. Here used for total duration of the computed carbquivalents instead, excluding the configurable delay.
+                var computedDuration = 0
+                switch fpus {
+                case ..<2:
+                    computedDuration = 3
+                case 2 ..< 3:
+                    computedDuration = 4
+                case 3 ..< 4:
+                    computedDuration = 5
+                default:
+                    computedDuration = timeCap
+                }
+                // Size of each created carb equivalent if 60 minutes interval
+                var equivalent: Decimal = carbEquivalents / Decimal(computedDuration)
+                // Adjust for interval setting other than 60 minutes
+                equivalent /= Decimal(60 / interval)
+                // Round to 1 fraction digit
+                // equivalent = Decimal(round(Double(equivalent * 10) / 10))
+                let roundedEquivalent: Double = round(Double(equivalent * 10)) / 10
+                equivalent = Decimal(roundedEquivalent)
+                // Number of equivalents
+                var numberOfEquivalents = carbEquivalents / equivalent
+                // Only use delay in first loop
+                var firstIndex = true
+                // New date for each carb equivalent
+                var useDate = entries.last?.createdAt ?? Date()
+                // Group and Identify all FPUs together
+                let fpuID = UUID().uuidString
+                // Create an array of all future carb equivalents.
+                var futureCarbArray = [CarbsEntry]()
+                while carbEquivalents > 0, numberOfEquivalents > 0 {
+                    if firstIndex {
+                        useDate = useDate.addingTimeInterval(delay.minutes.timeInterval)
+                        firstIndex = false
+                    } else { useDate = useDate.addingTimeInterval(interval.minutes.timeInterval) }
+
+                    let eachCarbEntry = CarbsEntry(
+                        id: UUID().uuidString, createdAt: useDate, carbs: equivalent, fat: 0, protein: 0,
+                        enteredBy: CarbsEntry.manual, isFPU: true,
+                        fpuID: fpuID
+                    )
+                    futureCarbArray.append(eachCarbEntry)
+                    numberOfEquivalents -= 1
+                }
+                // Save the array
+                if carbEquivalents > 0 {
+                    self.storage.transaction { storage in
+                        storage.append(futureCarbArray, to: file, uniqBy: \.createdAt)
+                        uniqEvents = storage.retrieve(file, as: [CarbsEntry].self)?
+                            .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
+                            .sorted { $0.createdAt > $1.createdAt } ?? []
+                        storage.save(Array(uniqEvents), as: file)
+                    }
+                }
+            } // ------------------------- END OF TPU ----------------------------------------
+            // Store the actual (normal) carbs
+            if entries.last?.carbs ?? 0 > 0 {
+                uniqEvents = []
+                self.storage.transaction { storage in
+                    storage.append(entries, to: file, uniqBy: \.createdAt)
+                    uniqEvents = storage.retrieve(file, as: [CarbsEntry].self)?
+                        .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
+                        .sorted { $0.createdAt > $1.createdAt } ?? []
+                    storage.save(Array(uniqEvents), as: file)
+                }
             }
 
             // MARK: Save to CoreData. TEST
 
             var cbs: Decimal = 0
             var carbDate = Date()
-            if carbs.isNotEmpty {
-                cbs = carbs[0].carbs
-                carbDate = carbs[0].createdAt
+            if entries.isNotEmpty {
+                cbs = entries[0].carbs
+                carbDate = entries[0].createdAt
             }
             if cbs != 0 {
                 self.coredataContext.perform {
@@ -56,7 +129,6 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                     try? self.coredataContext.save()
                 }
             }
-
             broadcaster.notify(CarbsObserver.self, on: processQueue) {
                 $0.carbsDidUpdate(uniqEvents)
             }

+ 1 - 0
FreeAPS/Sources/Helpers/Color+Extensions.swift

@@ -62,4 +62,5 @@ extension Color {
     static let loopPink = Color("LoopPink")
     static let lemon = Color("Lemon")
     static let minus = Color("minus")
+    static let darkGray = Color("darkGray")
 }

+ 4 - 0
FreeAPS/Sources/Models/CarbsEntry.swift

@@ -4,6 +4,8 @@ struct CarbsEntry: JSON, Equatable, Hashable {
     let id: String?
     let createdAt: Date
     let carbs: Decimal
+    let fat: Decimal
+    let protein: Decimal
     let enteredBy: String?
     let isFPU: Bool?
     let fpuID: String?
@@ -25,6 +27,8 @@ extension CarbsEntry {
         case id = "_id"
         case createdAt = "created_at"
         case carbs
+        case fat
+        case protein
         case enteredBy
         case isFPU
         case fpuID

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

@@ -41,6 +41,7 @@ struct FreeAPSSettings: JSON, Equatable {
     var oneDimensionalGraph: Bool = false
     var rulerMarks: Bool = false
     var maxCarbs: Decimal = 1000
+    var displayFatAndProteinOnWatch: Bool = false
 }
 
 extension FreeAPSSettings: Decodable {
@@ -214,6 +215,10 @@ extension FreeAPSSettings: Decodable {
             settings.maxCarbs = maxCarbs
         }
 
+        if let displayFatAndProteinOnWatch = try? container.decode(Bool.self, forKey: .displayFatAndProteinOnWatch) {
+            settings.displayFatAndProteinOnWatch = displayFatAndProteinOnWatch
+        }
+
         self = settings
     }
 }

+ 11 - 73
FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift

@@ -33,79 +33,17 @@ extension AddCarbs {
             }
             carbs = min(carbs, maxCarbs)
 
-            if useFPUconversion {
-                // -------------------------- FPU--------------------------------------
-                let interval = settings.settings.minuteInterval // Interval betwwen carbs
-                let timeCap = settings.settings.timeCap // Max Duration
-                let adjustment = settings.settings.individualAdjustmentFactor
-                let delay = settings.settings.delay // Tme before first future carb entry
-
-                let kcal = protein * 4 + fat * 9
-                let carbEquivalents = (kcal / 10) * adjustment
-                let fpus = carbEquivalents / 10
-
-                // Duration in hours used for extended boluses with Warsaw Method. Here used for total duration of the computed carbquivalents instead, excluding the configurable delay.
-                var computedDuration = 0
-                switch fpus {
-                case ..<2:
-                    computedDuration = 3
-                case 2 ..< 3:
-                    computedDuration = 4
-                case 3 ..< 4:
-                    computedDuration = 5
-                default:
-                    computedDuration = timeCap
-                }
-
-                // Size of each created carb equivalent if 60 minutes interval
-                var equivalent: Decimal = carbEquivalents / Decimal(computedDuration)
-                // Adjust for interval setting other than 60 minutes
-                equivalent /= Decimal(60 / interval)
-                // Round to 1 fraction digit
-                // equivalent = Decimal(round(Double(equivalent * 10) / 10))
-                let roundedEquivalent: Double = round(Double(equivalent * 10)) / 10
-                equivalent = Decimal(roundedEquivalent)
-                // Number of equivalents
-                var numberOfEquivalents = carbEquivalents / equivalent
-                // Only use delay in first loop
-                var firstIndex = true
-                // New date for each carb equivalent
-                var useDate = date
-                // Group and Identify all FPUs together
-                let fpuID = UUID().uuidString
-
-                // Create an array of all future carb equivalents.
-                var futureCarbArray = [CarbsEntry]()
-                while carbEquivalents > 0, numberOfEquivalents > 0 {
-                    if firstIndex {
-                        useDate = useDate.addingTimeInterval(delay.minutes.timeInterval)
-                        firstIndex = false
-                    } else { useDate = useDate.addingTimeInterval(interval.minutes.timeInterval) }
-
-                    let eachCarbEntry = CarbsEntry(
-                        id: UUID().uuidString, createdAt: useDate, carbs: equivalent, enteredBy: CarbsEntry.manual, isFPU: true,
-                        fpuID: fpuID
-                    )
-                    futureCarbArray.append(eachCarbEntry)
-                    numberOfEquivalents -= 1
-                }
-                // Save the array
-                if carbEquivalents > 0 {
-                    carbsStorage.storeCarbs(futureCarbArray)
-                }
-            } // ------------------------- END OF TPU ----------------------------------------
-
-            // Store the real carbs
-            if carbs > 0 {
-                carbsStorage
-                    .storeCarbs([CarbsEntry(
-                        id: UUID().uuidString,
-                        createdAt: date,
-                        carbs: carbs,
-                        enteredBy: CarbsEntry.manual,
-                        isFPU: false, fpuID: nil
-                    )])
-            }
+            carbsStorage.storeCarbs(
+                [CarbsEntry(
+                    id: UUID().uuidString,
+                    createdAt: date,
+                    carbs: carbs,
+                    fat: fat,
+                    protein: protein,
+                    enteredBy: CarbsEntry.manual,
+                    isFPU: false, fpuID: nil
+                )]
+            )
 
             if settingsManager.settings.skipBolusScreenAfterCarbs {
                 apsManager.determineBasalSync()

+ 3 - 0
FreeAPS/Sources/Modules/WatchConfig/View/WatchConfigRootView.swift

@@ -18,6 +18,9 @@ extension WatchConfig {
                         }
                     }
                 }
+
+                Toggle("Display Protein & Fat", isOn: $state.displayFatAndProteinOnWatch)
+
                 Section(header: Text("Garmin Watch")) {
                     List {
                         ForEach(state.devices, id: \.uuid) { device in

+ 2 - 0
FreeAPS/Sources/Modules/WatchConfig/WatchConfigStateModel.swift

@@ -30,12 +30,14 @@ extension WatchConfig {
         @Injected() private var garmin: GarminManager!
         @Published var devices: [IQDevice] = []
         @Published var selectedAwConfig: AwConfig = .HR
+        @Published var displayFatAndProteinOnWatch = false
 
         private(set) var preferences = Preferences()
 
         override func subscribe() {
             preferences = provider.preferences
 
+            subscribeSetting(\.displayFatAndProteinOnWatch, on: $displayFatAndProteinOnWatch) { displayFatAndProteinOnWatch = $0 }
             subscribeSetting(\.displayOnWatch, on: $selectedAwConfig) { selectedAwConfig = $0 }
             didSet: { [weak self] value in
                 // for compatibility with old displayHR

+ 12 - 6
FreeAPS/Sources/Services/WatchManager/WatchManager.swift

@@ -102,8 +102,8 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable {
                     )
                 }
             self.state.bolusAfterCarbs = !self.settingsManager.settings.skipBolusScreenAfterCarbs
-
             self.state.displayOnWatch = self.settingsManager.settings.displayOnWatch
+            self.state.displayFatAndProteinOnWatch = self.settingsManager.settings.displayFatAndProteinOnWatch
 
             let eBG = self.evetualBGStraing()
             self.state.eventualBG = eBG.map { "⇢ " + $0 }
@@ -263,16 +263,22 @@ extension BaseWatchManager: WCSessionDelegate {
     func session(_: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) {
         debug(.service, "WCSession got message with reply handler: \(message)")
 
-        if let carbs = message["carbs"] as? Double, carbs > 0 {
-            carbsStorage.storeCarbs([
-                CarbsEntry(
+        if let carbs = message["carbs"] as? Double,
+           let fat = message["fat"] as? Double,
+           let protein = message["protein"] as? Double,
+           carbs > 0 || fat > 0 || protein > 0
+        {
+            carbsStorage.storeCarbs(
+                [CarbsEntry(
                     id: UUID().uuidString,
                     createdAt: Date(),
                     carbs: Decimal(carbs),
+                    fat: Decimal(fat),
+                    protein: Decimal(protein),
                     enteredBy: CarbsEntry.manual,
                     isFPU: false, fpuID: nil
-                )
-            ])
+                )]
+            )
 
             if settingsManager.settings.skipBolusScreenAfterCarbs {
                 apsManager.determineBasalSync()

+ 1 - 0
FreeAPSWatch WatchKit Extension/DataFlow.swift

@@ -21,6 +21,7 @@ struct WatchState: Codable {
     var eventualBG: String?
     var eventualBGRaw: String?
     var displayOnWatch: AwConfig?
+    var displayFatAndProteinOnWatch: Bool?
     var isf: Decimal?
     var override: String?
 }

+ 197 - 52
FreeAPSWatch WatchKit Extension/Views/CarbsView.swift

@@ -3,7 +3,20 @@ import SwiftUI
 struct CarbsView: View {
     @EnvironmentObject var state: WatchStateModel
 
-    @State var amount = 0.0
+    // Selected nutrient
+    enum Selection: String {
+        case none
+        case carbs
+        case protein
+        case fat
+    }
+
+    @State var selection: Selection = .carbs
+    @State var carbAmount = 0.0
+    @State var fatAmount = 0.0
+    @State var proteinAmount = 0.0
+    @State var colorOfselection: Color = .darkGray
+    // @State var displayPresets: Bool = false
 
     var numberFormatter: NumberFormatter {
         let formatter = NumberFormatter()
@@ -16,62 +29,194 @@ struct CarbsView: View {
     }
 
     var body: some View {
-        GeometryReader { geo in
-            VStack(spacing: 16) {
-                HStack {
-                    Button {
-                        WKInterfaceDevice.current().play(.click)
-                        let newValue = amount - 5
-                        amount = max(newValue, 0)
-                    } label: {
-                        Image(systemName: "minus")
-                    }
-                    .frame(width: geo.size.width / 4)
-                    Spacer()
-                    Text(numberFormatter.string(from: amount as NSNumber)! + " g")
-                        .font(.title2)
-                        .focusable(true)
-                        .digitalCrownRotation(
-                            $amount,
-                            from: 0,
-                            through: Double(state.maxCOB ?? 120),
-                            by: 1,
-                            sensitivity: .medium,
-                            isContinuous: false,
-                            isHapticFeedbackEnabled: true
-                        )
-                    Spacer()
-                    Button {
-                        WKInterfaceDevice.current().play(.click)
-                        let newValue = amount + 5
-                        amount = min(newValue, Double(state.maxCOB ?? 120))
-                    } label: { Image(systemName: "plus") }
-                        .frame(width: geo.size.width / 4)
-                }
+        VStack {
+            // nutrient
+            carbs
+            if state.displayFatAndProteinOnWatch {
+                Spacer()
+                protein
+                Spacer()
+                fat
+            }
+            buttonStack
+        }
+        .onAppear { carbAmount = Double(state.carbsRequired ?? 0) }
+    }
+
+    var nutrient: some View {
+        HStack {
+            switch selection {
+            case .protein:
+                Text("Protein")
+            case .fat:
+                Text("Fat")
+            default:
+                Text("Carbs")
+            }
+        }.font(.footnote).frame(maxWidth: .infinity, alignment: .center)
+    }
+
+    var carbs: some View {
+        HStack {
+            if selection == .carbs {
                 Button {
                     WKInterfaceDevice.current().play(.click)
-                    // Get amount from displayed string
-                    let amount = Int(numberFormatter.string(from: amount as NSNumber)!) ?? Int(amount.rounded())
-                    state.addCarbs(amount)
-                }
-                label: {
-                    HStack {
-                        Image("carbs", bundle: nil)
-                            .renderingMode(.template)
-                            .resizable()
-                            .frame(width: 24, height: 24)
-                            .foregroundColor(.loopYellow)
-                        Text("Add Carbs ")
-                    }
-                }
-                .disabled(amount <= 0)
-            }.frame(maxHeight: .infinity)
+                    let newValue = carbAmount - 5
+                    carbAmount = max(newValue, 0)
+                } label: { Image(systemName: "minus") }
+                    .buttonStyle(.borderless).padding(.leading, 5)
+                    .tint(selection == .carbs ? .blue : .none)
+            }
+            Spacer()
+            Text("🥨")
+            Spacer()
+            Text(numberFormatter.string(from: carbAmount as NSNumber)! + " g")
+                .font(selection == .carbs ? .title : .title3)
+                .focusable(selection == .carbs)
+                .digitalCrownRotation(
+                    $carbAmount,
+                    from: 0,
+                    through: Double(state.maxCOB ?? 120),
+                    by: 1,
+                    sensitivity: .medium,
+                    isContinuous: false,
+                    isHapticFeedbackEnabled: true
+                )
+            Spacer()
+            if selection == .carbs {
+                Button {
+                    WKInterfaceDevice.current().play(.click)
+                    let newValue = carbAmount + 5
+                    carbAmount = min(newValue, Double(state.maxCOB ?? 120))
+                } label: { Image(systemName: "plus") }
+                    .buttonStyle(.borderless).padding(.trailing, 5)
+                    .tint(selection == .carbs ? .blue : .none)
+            }
+        }
+        .onTapGesture {
+            select(entry: .carbs)
         }
-        .navigationTitle("Add Carbs ")
+        .background(selection == .carbs && state.displayFatAndProteinOnWatch ? colorOfselection : .black)
+        .padding(.top)
+    }
 
-        .onAppear {
-            amount = Double(state.carbsRequired ?? 0)
+    var protein: some View {
+        HStack {
+            if selection == .protein {
+                Button {
+                    WKInterfaceDevice.current().play(.click)
+                    let newValue = proteinAmount - 5
+                    proteinAmount = max(newValue, 0)
+                } label: { Image(systemName: "minus") }
+                    .buttonStyle(.borderless).padding(.leading, 5)
+                    .tint(selection == .protein ? .blue : .none)
+            }
+            Spacer()
+            Text("🍗")
+            Spacer()
+            Text(numberFormatter.string(from: proteinAmount as NSNumber)! + " g")
+                .font(selection == .protein ? .title : .title3)
+                .foregroundStyle(.red)
+                .focusable(selection == .protein)
+                .digitalCrownRotation(
+                    $proteinAmount,
+                    from: 0,
+                    through: Double(240),
+                    by: 1,
+                    sensitivity: .medium,
+                    isContinuous: false,
+                    isHapticFeedbackEnabled: true
+                )
+            Spacer()
+            if selection == .protein {
+                Button {
+                    WKInterfaceDevice.current().play(.click)
+                    let newValue = proteinAmount + 5
+                    proteinAmount = min(newValue, Double(240))
+                } label: { Image(systemName: "plus") }.buttonStyle(.borderless).padding(.trailing, 5)
+                    .tint(selection == .protein ? .blue : .none)
+            }
         }
+        .onTapGesture {
+            select(entry: .protein)
+        }
+        .background(selection == .protein ? colorOfselection : .black)
+    }
+
+    var fat: some View {
+        HStack {
+            if selection == .fat {
+                Button {
+                    WKInterfaceDevice.current().play(.click)
+                    let newValue = fatAmount - 5
+                    fatAmount = max(newValue, 0)
+                } label: { Image(systemName: "minus") }
+                    .buttonStyle(.borderless).padding(.leading, 5)
+                    .tint(selection == .fat ? .blue : .none)
+            }
+            Spacer()
+            Text("🧀")
+            Spacer()
+            Text(numberFormatter.string(from: fatAmount as NSNumber)! + " g")
+                .font(selection == .fat ? .title : .title3)
+                .foregroundColor(.loopYellow)
+                .focusable(selection == .fat)
+                .digitalCrownRotation(
+                    $fatAmount,
+                    from: 0,
+                    through: Double(240),
+                    by: 1,
+                    sensitivity: .medium,
+                    isContinuous: false,
+                    isHapticFeedbackEnabled: true
+                )
+            Spacer()
+            if selection == .fat {
+                Button {
+                    WKInterfaceDevice.current().play(.click)
+                    let newValue = fatAmount + 5
+                    fatAmount = min(newValue, Double(240))
+                } label: { Image(systemName: "plus") }
+                    .buttonStyle(.borderless).padding(.trailing, 5)
+                    .tint(selection == .fat ? .blue : .none)
+            }
+        }
+        .onTapGesture {
+            select(entry: .fat)
+        }
+        .background(selection == .fat ? colorOfselection : .black)
+    }
+
+    var buttonStack: some View {
+        HStack(spacing: 25) {
+            /* To do: display the actual meal presets
+             Button {
+                 displayPresets.toggle()
+             }
+             label: { Image(systemName: "menucard.fill") }
+                 .buttonStyle(.borderless)
+             */
+            Button {
+                WKInterfaceDevice.current().play(.click)
+                // Get amount from displayed string
+                let amountCarbs = Int(numberFormatter.string(from: carbAmount as NSNumber)!) ?? Int(carbAmount.rounded())
+                let amountFat = Int(numberFormatter.string(from: fatAmount as NSNumber)!) ?? Int(fatAmount.rounded())
+                let amountProtein = Int(numberFormatter.string(from: proteinAmount as NSNumber)!) ??
+                    Int(proteinAmount.rounded())
+                state.addMeal(amountCarbs, fat: amountFat, protein: amountProtein)
+            }
+            label: { Text("Save") }
+                .buttonStyle(.borderless)
+                .font(.callout)
+                .foregroundColor(carbAmount > 0 || fatAmount > 0 || proteinAmount > 0 ? .blue : .secondary)
+                .disabled(carbAmount <= 0 && fatAmount <= 0 && proteinAmount <= 0)
+        }
+        .frame(maxHeight: .infinity, alignment: .bottom)
+        .padding(.top)
+    }
+
+    private func select(entry: Selection) {
+        selection = entry
     }
 }
 

+ 4 - 3
FreeAPSWatch WatchKit Extension/WatchStateModel.swift

@@ -33,6 +33,7 @@ class WatchStateModel: NSObject, ObservableObject {
     @Published var isTempTargetViewActive = false
     @Published var isBolusViewActive = false
     @Published var displayOnWatch: AwConfig = .BGTarget
+    @Published var displayFatAndProteinOnWatch = false
     @Published var eventualBG = ""
     @Published var isConfirmationViewActive = false {
         didSet {
@@ -53,7 +54,6 @@ class WatchStateModel: NSObject, ObservableObject {
     @Published var lastUpdate: Date = .distantPast
     @Published var timerDate = Date()
     @Published var pendingBolus: Double?
-
     @Published var isf: Decimal?
     @Published var override: String?
 
@@ -69,11 +69,11 @@ class WatchStateModel: NSObject, ObservableObject {
         session.activate()
     }
 
-    func addCarbs(_ carbs: Int) {
+    func addMeal(_ carbs: Int, fat: Int, protein: Int) {
         confirmationSuccess = nil
         isConfirmationViewActive = true
         isCarbsViewActive = false
-        session.sendMessage(["carbs": carbs], replyHandler: { reply in
+        session.sendMessage(["carbs": carbs, "fat": fat, "protein": protein], replyHandler: { reply in
             self.completionHandler(reply)
             if let ok = reply["confirmation"] as? Bool, ok, self.bolusAfterCarbs {
                 DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
@@ -173,6 +173,7 @@ class WatchStateModel: NSObject, ObservableObject {
         lastUpdate = Date()
         eventualBG = state.eventualBG ?? ""
         displayOnWatch = state.displayOnWatch ?? .BGTarget
+        displayFatAndProteinOnWatch = state.displayFatAndProteinOnWatch ?? false
         isf = state.isf
         override = state.override
     }