Explorar el Código

Merge branch 'core-data-sync-trio' of github.com:dnzxy/Trio-dev into watch

Deniz Cengiz hace 1 año
padre
commit
f414b315fb

+ 3 - 3
Trio/Sources/Models/DecimalPickerSettings.swift

@@ -40,7 +40,7 @@ struct DecimalPickerSettings {
         value: 0.5,
         step: 0.05,
         min: 0.1,
-        max: 2,
+        max: 1.2,
         type: PickerSetting.PickerSettingType.factor
     )
     var high = PickerSetting(value: 180, step: 1, min: 100, max: 500, type: PickerSetting.PickerSettingType.glucose)
@@ -130,8 +130,8 @@ struct DecimalPickerSettings {
     )
     var threshold_setting = PickerSetting(value: 60, step: 1, min: 60, max: 120, type: PickerSetting.PickerSettingType.glucose)
     var updateInterval = PickerSetting(value: 20, step: 5, min: 1, max: 60, type: PickerSetting.PickerSettingType.minute)
-    var delay = PickerSetting(value: 60, step: 15, min: 30, max: 120, type: PickerSetting.PickerSettingType.minute)
-    var minuteInterval = PickerSetting(value: 20, step: 5, min: 5, max: 60, type: PickerSetting.PickerSettingType.minute)
+    var delay = PickerSetting(value: 60, step: 10, min: 60, max: 120, type: PickerSetting.PickerSettingType.minute)
+    var minuteInterval = PickerSetting(value: 30, step: 5, min: 10, max: 60, type: PickerSetting.PickerSettingType.minute)
     var timeCap = PickerSetting(value: 8, step: 1, min: 5, max: 12, type: PickerSetting.PickerSettingType.hour)
     var hours = PickerSetting(value: 6, step: 0.5, min: 2, max: 24, type: PickerSetting.PickerSettingType.hour)
     var dia = PickerSetting(value: 10, step: 0.5, min: 5, max: 10, type: PickerSetting.PickerSettingType.hour)

+ 7 - 0
Trio/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift

@@ -4,6 +4,7 @@ import SwiftUI
 extension BasalProfileEditor {
     @Observable final class StateModel: BaseStateModel<Provider> {
         @ObservationIgnored @Injected() private var nightscout: NightscoutManager!
+        @ObservationIgnored @Injected() private var broadcaster: Broadcaster!
 
         var syncInProgress: Bool = false
         var initialItems: [Item] = []
@@ -105,6 +106,12 @@ extension BasalProfileEditor {
                     print("We were successful")
                 }
                 .store(in: &lifetime)
+
+            DispatchQueue.main.async {
+                self.broadcaster.notify(BasalProfileObserver.self, on: .main) {
+                    $0.basalProfileDidChange(profile)
+                }
+            }
         }
 
         @MainActor func validate() {

+ 0 - 12
Trio/Sources/Modules/DataTable/View/DataTableRootView.swift

@@ -110,18 +110,6 @@ extension DataTable {
                 .navigationTitle("History")
                 .navigationBarTitleDisplayMode(.large)
                 .toolbar {
-                    ToolbarItem(placement: .topBarLeading, content: {
-                        Button(
-                            action: { state.showModal(for: .statistics) },
-                            label: {
-                                HStack {
-                                    Text("Statistics")
-                                }
-                            }
-                        )
-                    })
-                }
-                .toolbar {
                     ToolbarItem(placement: .topBarTrailing, content: {
                         addButton({
                             showManualGlucose = true

+ 0 - 2
Trio/Sources/Modules/Home/HomeDataFlow.swift

@@ -9,8 +9,6 @@ protocol HomeProvider: Provider {
     func heartbeatNow()
     func pumpSettings() async -> PumpSettings
     func getBasalProfile() async -> [BasalProfileEntry]
-    func tempTargets(hours: Int) -> [TempTarget]
     func pumpReservoir() async -> Decimal?
-    func tempTarget() -> TempTarget?
     func getBGTargets() async -> BGTargets
 }

+ 0 - 10
Trio/Sources/Modules/Home/HomeProvider.swift

@@ -16,16 +16,6 @@ extension Home {
             apsManager.heartbeat(date: Date())
         }
 
-        func tempTargets(hours: Int) -> [TempTarget] {
-            tempTargetsStorage.recent().filter {
-                $0.createdAt.addingTimeInterval(hours.hours.timeInterval) > Date()
-            }
-        }
-
-        func tempTarget() -> TempTarget? {
-            tempTargetsStorage.current()
-        }
-
         func pumpSettings() async -> PumpSettings {
             await storage.retrieveAsync(OpenAPS.Settings.settings, as: PumpSettings.self)
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))

+ 0 - 14
Trio/Sources/Modules/Home/HomeStateModel.swift

@@ -26,7 +26,6 @@ extension Home {
         var basalProfile: [BasalProfileEntry] = []
         var bgTargets = BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
             ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])
-        var tempTargets: [TempTarget] = []
         var timerDate = Date()
         var closedLoop = false
         var pumpSuspended = false
@@ -37,7 +36,6 @@ extension Home {
         var reservoir: Decimal?
         var pumpName = ""
         var pumpExpiresAtDate: Date?
-        var tempTarget: TempTarget?
         var highTTraisesSens: Bool = false
         var lowTTlowersSens: Bool = false
         var isExerciseModeActive: Bool = false
@@ -267,7 +265,6 @@ extension Home {
         }
 
         private func registerObservers() {
-            broadcaster.register(GlucoseObserver.self, observer: self)
             broadcaster.register(DeterminationObserver.self, observer: self)
             broadcaster.register(SettingsObserver.self, observer: self)
             broadcaster.register(PreferencesObserver.self, observer: self)
@@ -554,7 +551,6 @@ extension Home {
 }
 
 extension Home.StateModel:
-    GlucoseObserver,
     DeterminationObserver,
     SettingsObserver,
     PreferencesObserver,
@@ -565,11 +561,6 @@ extension Home.StateModel:
     PumpTimeZoneObserver,
     PumpDeactivatedObserver
 {
-    // TODO: still needed?
-    func glucoseDidUpdate(_: [BloodGlucose]) {
-//        setupGlucose()
-    }
-
     func determinationDidUpdate(_: Determination) {
         waitForSuggestion = false
     }
@@ -606,11 +597,6 @@ extension Home.StateModel:
         lowTTlowersSens = settingsManager.preferences.lowTemptargetLowersSensitivity
     }
 
-    // TODO: is this ever really triggered? react to MOC changes?
-    func pumpHistoryDidUpdate(_: [PumpHistoryEvent]) {
-        displayPumpStatusHighlightMessage()
-    }
-
     func pumpSettingsDidChange(_: PumpSettings) {
         Task {
             await setupPumpSettings()

+ 5 - 10
Trio/Sources/Modules/Home/View/Chart/ChartElements/GlucoseTargetsView.swift

@@ -47,18 +47,13 @@ struct GlucoseTargetsView: ChartContent {
      Processes raw glucose target data into a list of target profiles for visualization.
 
      - Parameter rawTargets: The raw glucose target data containing offset and glucose values.
-     - Returns: An array of `TargetProfile` objects, each representing a glucose target range for today and tomorrow.
+     - Returns: An array of `TargetProfile` objects, each representing a glucose target range starting from the day of the startMarker and ending two days later.
 
      The function:
-     - Converts glucose targets into profiles covering two consecutive days (today and tomorrow).
+     - Converts glucose targets into profiles covering three consecutive days (day of startMarker, day after startMarker and day after that).
      - Calculates start and end times for each target based on the offsets provided.
      - Handles conversions between mg/dL and mmol/L as per user settings.
      - Ensures targets span across midnight to avoid data cutoff.
-
-     Example:
-     For a target at offset 0 (midnight) with low glucose value 70 mg/dL, the function generates two profiles:
-     - One for today from midnight to the next target offset or end of the day.
-     - Another for tomorrow covering the same time range.
      */
     private func processFetchedTargets(_ rawTargets: BGTargets) -> [TargetProfile] {
         var targetProfiles: [TargetProfile] = []
@@ -74,9 +69,9 @@ struct GlucoseTargetsView: ChartContent {
         // Base date is the start of the day for the startMarker
         let baseDate = Calendar.current.startOfDay(for: startMarker)
 
-        // Process each target twice: once for today and once for tomorrow
-        for index in 0 ..< (targets.count * 2) {
-            // Calculate the day offset (0 for today, 1 for tomorrow)
+        // Process each target three times
+        for index in 0 ..< (targets.count * 3) {
+            // Calculate the day offset (0 for today, 1 for tomorrow, 2 for day after)
             let dayOffset = index / targets.count
             let targetIndex = index % targets.count
 

+ 0 - 1
Trio/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -10,7 +10,6 @@ struct MainChartView: View {
     var safeAreaSize: CGFloat
     var units: GlucoseUnits
     var hours: Int
-    var tempTargets: [TempTarget]
     var highGlucose: Decimal
     var lowGlucose: Decimal
     var currentGlucoseTarget: Decimal

+ 13 - 11
Trio/Sources/Modules/Home/View/Header/LoopView.swift

@@ -4,6 +4,8 @@ import SwiftUI
 import UIKit
 
 struct LoopView: View {
+    @Environment(\.colorScheme) var colorScheme
+
     private enum Config {
         static let lag: TimeInterval = 30
     }
@@ -19,10 +21,19 @@ struct LoopView: View {
     private let rect = CGRect(x: 0, y: 0, width: 18, height: 18)
 
     var body: some View {
+        loopStatusWithMinutes
+            .padding(.vertical, 5)
+            .padding(.horizontal, 10)
+            .overlay(
+                Capsule()
+                    .stroke(color.opacity(0.4), lineWidth: 2)
+            )
+    }
+
+    private var loopStatusWithMinutes: some View {
         HStack(alignment: .center) {
             ZStack {
-                Image(systemName: "circle")
-                    .mask(mask(in: rect).fill(style: FillStyle(eoFill: true)))
+                Image(systemName: (!closedLoop || manualTempBasal) ? "circle.and.line.horizontal" : "circle")
                 if isLooping {
                     ProgressView()
                 }
@@ -41,7 +52,6 @@ struct LoopView: View {
                 Text("--")
             }
         }
-        .strikethrough(!closedLoop || manualTempBasal, pattern: .solid, color: color)
         .font(.callout).fontWeight(.bold).fontDesign(.rounded)
         .foregroundColor(color)
     }
@@ -80,14 +90,6 @@ struct LoopView: View {
             return .loopRed
         }
     }
-
-    func mask(in rect: CGRect) -> Path {
-        var path = Rectangle().path(in: rect)
-        if !closedLoop || manualTempBasal {
-            path.addPath(Rectangle().path(in: CGRect(x: rect.minX, y: rect.midY - 4, width: rect.width, height: 8)))
-        }
-        return path
-    }
 }
 
 extension View {

+ 40 - 18
Trio/Sources/Modules/Home/View/Header/PumpView.swift

@@ -45,24 +45,44 @@ struct PumpView: View {
                     HStack {
                         Image(systemName: "cross.vial.fill")
                             .font(.callout)
-                            .foregroundColor(reservoirColor)
+
                         if reservoir == 0xDEAD_BEEF {
                             Text("50+ " + NSLocalizedString("U", comment: "Insulin unit"))
-                                .font(.callout).fontWeight(.bold).fontDesign(.rounded)
+                                .font(.callout)
+                                .fontWeight(.bold)
+                                .fontDesign(.rounded)
                         } else {
                             Text(
                                 Formatter.integerFormatter
                                     .string(from: reservoir as NSNumber)! + NSLocalizedString(" U", comment: "Insulin unit")
                             )
-                            .font(.callout).fontWeight(.bold).fontDesign(.rounded)
+                            .font(.callout)
+                            .fontWeight(.bold)
+                            .fontDesign(.rounded)
                         }
                     }
+                    .padding(.vertical, 5)
+                    .padding(.horizontal, 10)
+                    .foregroundStyle(reservoirColor)
+                    .overlay(
+                        Capsule()
+                            .stroke(reservoirColor.opacity(0.4), lineWidth: 2)
+                    )
 
                     if let timeZone = timeZone, timeZone.secondsFromGMT() != TimeZone.current.secondsFromGMT() {
-                        Image(systemName: "clock.badge.exclamationmark.fill")
-                            .font(.callout)
-                            .symbolRenderingMode(.palette)
-                            .foregroundStyle(.red, Color(.warning))
+                        HStack {
+                            Image(systemName: "clock.badge.exclamationmark.fill")
+                                .font(.callout)
+                                .symbolRenderingMode(.palette)
+                                .foregroundStyle(.red, Color(.warning))
+
+                            Text("Timezone")
+                                .font(.callout)
+                                .fontWeight(.bold)
+                                .fontDesign(.rounded)
+                                .foregroundStyle(.red)
+                        }
+                        .padding(.leading, 12)
                     }
                 }
 
@@ -70,7 +90,7 @@ struct PumpView: View {
                     HStack {
                         Image(systemName: "battery.100")
                             .font(.callout)
-                            .foregroundColor(batteryColor)
+                            .foregroundStyle(batteryColor)
                         Text("\(Int(battery.first?.percent ?? 100)) %")
                             .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
@@ -80,13 +100,15 @@ struct PumpView: View {
                     HStack {
                         Image(systemName: "stopwatch.fill")
                             .font(.callout)
-                            .foregroundColor(timerColor)
+                            .foregroundStyle(timerColor)
 
                         Text(remainingTimeString(time: date.timeIntervalSince(timerDate)))
                             .font(!(date.timeIntervalSince(timerDate) > 0) ? .subheadline : .callout)
                             .fontWeight(.bold)
                             .fontDesign(.rounded)
                     }
+                    // aligns the stopwatch icon exactly with the first pixel of the reservoir icon
+                    .padding(.leading, 12)
                 }
             }
         }
@@ -123,11 +145,11 @@ struct PumpView: View {
 
         switch battery.percent {
         case ...10:
-            return .red
+            return Color.loopRed
         case ...20:
-            return .yellow
+            return Color.orange
         default:
-            return .green
+            return Color.loopGreen
         }
     }
 
@@ -138,11 +160,11 @@ struct PumpView: View {
 
         switch reservoir {
         case ...10:
-            return .red
+            return Color.loopRed
         case ...30:
-            return .yellow
+            return Color.orange
         default:
-            return .blue
+            return Color.insulin
         }
     }
 
@@ -155,11 +177,11 @@ struct PumpView: View {
 
         switch time {
         case ...8.hours.timeInterval:
-            return .red
+            return Color.loopRed
         case ...1.days.timeInterval:
-            return .yellow
+            return Color.orange
         default:
-            return .green
+            return Color.loopGreen
         }
     }
 }

+ 107 - 51
Trio/Sources/Modules/Home/View/HomeRootView.swift

@@ -5,11 +5,9 @@ import SwiftUI
 import Swinject
 
 struct TimePicker: Identifiable {
-    let label: String
-    let number: String
     var active: Bool
     let hours: Int16
-    var id: String { label }
+    var id: String { hours.description }
 }
 
 extension Home {
@@ -36,15 +34,12 @@ extension Home {
         @State var showPumpSelection: Bool = false
         @State var notificationsDisabled = false
         @State var timeButtons: [TimePicker] = [
-            TimePicker(label: "2 hours", number: "2", active: false, hours: 2),
-            TimePicker(label: "4 hours", number: "4", active: false, hours: 4),
-            TimePicker(label: "6 hours", number: "6", active: false, hours: 6),
-            TimePicker(label: "12 hours", number: "12", active: false, hours: 12),
-            TimePicker(label: "24 hours", number: "24", active: false, hours: 24)
+            TimePicker(active: false, hours: 4),
+            TimePicker(active: false, hours: 6),
+            TimePicker(active: false, hours: 12),
+            TimePicker(active: false, hours: 24)
         ]
 
-        let buttonFont = Font.custom("TimeButtonFont", size: 14)
-
         @FetchRequest(fetchRequest: OverrideStored.fetch(
             NSPredicate.lastActiveOverride,
             ascending: false,
@@ -116,7 +111,8 @@ extension Home {
                 timeZone: state.timeZone,
                 pumpStatusHighlightMessage: state.pumpStatusHighlightMessage,
                 battery: state.batteryFromPersistence
-            ).onTapGesture {
+            )
+            .onTapGesture {
                 if state.pumpDisplayState == nil {
                     // shows user confirmation dialog with pump model choices, then proceeds to setup
                     showPumpSelection.toggle()
@@ -245,45 +241,73 @@ extension Home {
             return components.isEmpty ? nil : components.joined(separator: ", ")
         }
 
-        var timeInterval: some View {
-            HStack(alignment: .center) {
+        var timeIntervalButtons: some View {
+            let buttonColor = (colorScheme == .dark ? Color.white : Color.black).opacity(0.8)
+
+            return HStack(alignment: .center) {
                 ForEach(timeButtons) { button in
-                    Text(button.active ? NSLocalizedString(button.label, comment: "") : button.number).onTapGesture {
+                    Button(action: {
                         state.hours = button.hours
+                    }) {
+                        Group {
+                            if button.active {
+                                Text(
+                                    NSLocalizedString(button.hours.description, comment: "") + " " +
+                                        NSLocalizedString("h", comment: "h")
+                                )
+                            } else {
+                                Text(NSLocalizedString(button.hours.description, comment: ""))
+                            }
+                        }
+                        .font(.footnote)
+                        .fontWeight(button.active ? .semibold : .regular)
+                        .padding(.vertical, 5)
+                        .padding(.horizontal, 10)
+                        .foregroundColor(
+                            button
+                                .active ? (colorScheme == .dark ? Color.bgDarkerDarkBlue : Color.white) : buttonColor
+                        )
+                        .background(button.active ? buttonColor.opacity(colorScheme == .dark ? 1 : 0.8) : Color.clear)
+                        .clipShape(Capsule())
+                        .overlay(
+                            Capsule()
+                                .stroke(button.active ? buttonColor.opacity(0.4) : Color.clear, lineWidth: 2)
+                        )
                     }
-                    .foregroundStyle(button.active ? (colorScheme == .dark ? Color.white : Color.black).opacity(0.9) : .secondary)
-                    .frame(maxHeight: 30).padding(.horizontal, 8)
-                    .background(
-                        button.active ?
-                            // RGB(30, 60, 95)
-                            (
-                                colorScheme == .dark ? Color(red: 0.1176470588, green: 0.2352941176, blue: 0.3725490196) :
-                                    Color.white
-                            ) :
-                            Color
-                            .clear
-                    )
-                    .cornerRadius(20)
                 }
-                Button(action: {
-                    state.isLegendPresented.toggle()
-                }) {
-                    Image(systemName: "info")
-                        .foregroundColor(colorScheme == .dark ? Color.white : Color.black).opacity(0.9)
-                        .frame(width: 20, height: 20)
-                        .background(
-                            colorScheme == .dark ? Color(red: 0.1176470588, green: 0.2352941176, blue: 0.3725490196) :
-                                Color.white
-                        )
-                        .clipShape(Circle())
+            }
+        }
+
+        var statsIconString: String {
+            if #available(iOS 18, *) {
+                return "chart.line.text.clipboard"
+            } else {
+                return "list.clipboard"
+            }
+        }
+
+        @ViewBuilder private func tappableButton(
+            buttonColor: Color,
+            label: String,
+            iconString: String,
+            action: @escaping () -> Void
+        ) -> some View {
+            Button(action: {
+                action()
+            }) {
+                HStack {
+                    Image(systemName: iconString)
+                    Text(label)
                 }
-                .padding([.top, .bottom])
+                .font(.footnote)
+                .padding(.vertical, 5)
+                .padding(.horizontal, 10)
+                .foregroundStyle(buttonColor)
+                .overlay(
+                    Capsule()
+                        .stroke(buttonColor.opacity(0.4), lineWidth: 2)
+                )
             }
-            .shadow(
-                color: Color.black.opacity(colorScheme == .dark ? 0.75 : 0.33),
-                radius: colorScheme == .dark ? 5 : 3
-            )
-            .font(buttonFont)
         }
 
         @ViewBuilder func mainChart(geo: GeometryProxy) -> some View {
@@ -293,7 +317,6 @@ extension Home {
                     safeAreaSize: notificationsDisabled == true ? safeAreaSize : 0,
                     units: state.units,
                     hours: state.filteredHours,
-                    tempTargets: state.tempTargets,
                     highGlucose: state.highGlucose,
                     lowGlucose: state.lowGlucose,
                     currentGlucoseTarget: state.currentGlucoseTarget,
@@ -324,9 +347,11 @@ extension Home {
                     lastLoopDate: state.lastLoopDate,
                     manualTempBasal: state.manualTempBasal,
                     determination: state.determinationsFromPersistence
-                ).onTapGesture {
+                )
+                .onTapGesture {
                     state.isLoopStatusPresented = true
-                }.onLongPressGesture {
+                }
+                .onLongPressGesture {
                     let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
                     impactHeavy.impactOccurred()
                     state.runLoop()
@@ -347,6 +372,8 @@ extension Home {
                             )!
                         ).font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
+                    // aligns the evBG icon exactly with the first pixel of loop status icon
+                    .padding(.leading, 12)
                 } else {
                     HStack {
                         Image(systemName: "arrow.right.circle")
@@ -402,8 +429,17 @@ extension Home {
                         Image(systemName: "drop.circle")
                             .font(.callout)
                             .foregroundColor(.insulinTintColor)
-                        Text(tempBasalString)
-                            .font(.callout).fontWeight(.bold).fontDesign(.rounded)
+                        if tempBasalString.count > 5 {
+                            Text(tempBasalString)
+                                .font(.callout).fontWeight(.bold).fontDesign(.rounded)
+                                .lineLimit(1)
+                                .minimumScaleFactor(0.85)
+                                .truncationMode(.tail)
+                                .allowsTightening(true)
+                        } else {
+                            // Short strings can just display normally
+                            Text(tempBasalString).font(.callout).fontWeight(.bold).fontDesign(.rounded)
+                        }
                     } else {
                         Image(systemName: "drop.circle")
                             .font(.callout)
@@ -815,8 +851,28 @@ extension Home {
 
                 mainChart(geo: geo)
 
-                timeInterval.padding(.top, UIDevice.adjustPadding(min: 0, max: 12))
-                    .padding(.bottom, UIDevice.adjustPadding(min: 0, max: 12))
+                HStack {
+                    tappableButton(
+                        buttonColor: (colorScheme == .dark ? Color.white : Color.black).opacity(0.8),
+                        label: "Stats",
+                        iconString: statsIconString,
+                        action: { state.showModal(for: .statistics) }
+                    )
+
+                    Spacer()
+
+                    timeIntervalButtons.padding(.top, UIDevice.adjustPadding(min: 0, max: 10))
+                        .padding(.bottom, UIDevice.adjustPadding(min: 0, max: 10))
+
+                    Spacer()
+
+                    tappableButton(
+                        buttonColor: (colorScheme == .dark ? Color.white : Color.black).opacity(0.8),
+                        label: "Info",
+                        iconString: "info",
+                        action: { state.isLegendPresented.toggle() }
+                    )
+                }.padding([.horizontal, .top, .bottom])
 
                 if let progress = state.bolusProgress {
                     bolusView(geo: geo, progress)

+ 5 - 6
Trio/Sources/Modules/MealSettings/MealSettingsStateModel.swift

@@ -7,10 +7,10 @@ extension MealSettings {
         @Published var maxCarbs: Decimal = 250
         @Published var maxFat: Decimal = 250
         @Published var maxProtein: Decimal = 250
-        @Published var individualAdjustmentFactor: Decimal = 0
-        @Published var timeCap: Decimal = 0
-        @Published var minuteInterval: Decimal = 0
-        @Published var delay: Decimal = 0
+        @Published var individualAdjustmentFactor: Decimal = 0.5
+        @Published var timeCap: Decimal = 8
+        @Published var minuteInterval: Decimal = 30
+        @Published var delay: Decimal = 60
 
         override func subscribe() {
             units = settingsManager.settings.units
@@ -38,8 +38,7 @@ extension MealSettings {
             })
 
             subscribeSetting(\.individualAdjustmentFactor, on: $individualAdjustmentFactor, initial: {
-                let value = max(min($0, 1.2), 0.1)
-                individualAdjustmentFactor = value
+                individualAdjustmentFactor = $0
             }, map: {
                 $0
             })

+ 4 - 4
Trio/Sources/Modules/MealSettings/View/MealSettingsRootView.swift

@@ -288,7 +288,6 @@ extension MealSettings {
                             )
                             Text("Increasing this setting may result in more FPU entries with smaller carb values.")
                             Text("Decreasing this setting may result in fewer FPU entries with larger carb values.")
-                            Text("Note: Accepted range for this setting is 5 - 12 hours.")
                         }
                     )
 
@@ -316,7 +315,6 @@ extension MealSettings {
                             Text("The shorter the interval, the smoother the correlating dosing result.")
                             Text("Increasing this setting may result in fewer FPU entries with larger carb values.")
                             Text("Decreasing this setting may result in more FPU entries with smaller carb values.")
-                            Text("Accepted range for this setting is 5 - 60 minutes.")
                         }
                     )
 
@@ -344,9 +342,11 @@ extension MealSettings {
                                     Text("(Fat × 45%) + (Protein × 20%)")
                                     Text("100% is full effect:").bold()
                                     Text("(Fat × 90%) + (Protein × 40%)")
-                                    Text("200% is double effect:").bold()
-                                    Text("(Fat × 180%) + (Protein x 80%)")
+                                    Text("110% makes fat-to-carbs ratio essentially equal:").bold()
+                                    Text("(Fat × 99%) + (Protein x 44%)")
                                 }
+                                .multilineTextAlignment(.center)
+                                .fixedSize(horizontal: false, vertical: true)
                                 Text(
                                     "Tip: You may find that your normal carb ratio needs to increase to a larger number when you begin adding fat and protein entries. For this reason, it is best to start with a factor of about 50%."
                                 )

+ 7 - 0
Trio/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift

@@ -3,6 +3,7 @@ import SwiftUI
 extension TargetsEditor {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() private var nightscout: NightscoutManager!
+        @Injected() private var broadcaster: Broadcaster!
 
         @Published var items: [Item] = []
         @Published var initialItems: [Item] = []
@@ -75,6 +76,12 @@ extension TargetsEditor {
             provider.saveProfile(profile)
             initialItems = items.map { Item(lowIndex: $0.lowIndex, highIndex: $0.highIndex, timeIndex: $0.timeIndex) }
 
+            DispatchQueue.main.async {
+                self.broadcaster.notify(BGTargetsObserver.self, on: .main) {
+                    $0.bgTargetsDidChange(profile)
+                }
+            }
+
             Task.detached(priority: .low) {
                 debug(.nightscout, "Attempting to upload targets to Nightscout")
                 await self.nightscout.uploadProfiles()