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,
         value: 0.5,
         step: 0.05,
         step: 0.05,
         min: 0.1,
         min: 0.1,
-        max: 2,
+        max: 1.2,
         type: PickerSetting.PickerSettingType.factor
         type: PickerSetting.PickerSettingType.factor
     )
     )
     var high = PickerSetting(value: 180, step: 1, min: 100, max: 500, type: PickerSetting.PickerSettingType.glucose)
     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 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 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 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 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)
     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 {
 extension BasalProfileEditor {
     @Observable final class StateModel: BaseStateModel<Provider> {
     @Observable final class StateModel: BaseStateModel<Provider> {
         @ObservationIgnored @Injected() private var nightscout: NightscoutManager!
         @ObservationIgnored @Injected() private var nightscout: NightscoutManager!
+        @ObservationIgnored @Injected() private var broadcaster: Broadcaster!
 
 
         var syncInProgress: Bool = false
         var syncInProgress: Bool = false
         var initialItems: [Item] = []
         var initialItems: [Item] = []
@@ -105,6 +106,12 @@ extension BasalProfileEditor {
                     print("We were successful")
                     print("We were successful")
                 }
                 }
                 .store(in: &lifetime)
                 .store(in: &lifetime)
+
+            DispatchQueue.main.async {
+                self.broadcaster.notify(BasalProfileObserver.self, on: .main) {
+                    $0.basalProfileDidChange(profile)
+                }
+            }
         }
         }
 
 
         @MainActor func validate() {
         @MainActor func validate() {

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

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

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

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

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

@@ -16,16 +16,6 @@ extension Home {
             apsManager.heartbeat(date: Date())
             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 {
         func pumpSettings() async -> PumpSettings {
             await storage.retrieveAsync(OpenAPS.Settings.settings, as: PumpSettings.self)
             await storage.retrieveAsync(OpenAPS.Settings.settings, as: PumpSettings.self)
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
                 ?? 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 basalProfile: [BasalProfileEntry] = []
         var bgTargets = BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
         var bgTargets = BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
             ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])
             ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])
-        var tempTargets: [TempTarget] = []
         var timerDate = Date()
         var timerDate = Date()
         var closedLoop = false
         var closedLoop = false
         var pumpSuspended = false
         var pumpSuspended = false
@@ -37,7 +36,6 @@ extension Home {
         var reservoir: Decimal?
         var reservoir: Decimal?
         var pumpName = ""
         var pumpName = ""
         var pumpExpiresAtDate: Date?
         var pumpExpiresAtDate: Date?
-        var tempTarget: TempTarget?
         var highTTraisesSens: Bool = false
         var highTTraisesSens: Bool = false
         var lowTTlowersSens: Bool = false
         var lowTTlowersSens: Bool = false
         var isExerciseModeActive: Bool = false
         var isExerciseModeActive: Bool = false
@@ -267,7 +265,6 @@ extension Home {
         }
         }
 
 
         private func registerObservers() {
         private func registerObservers() {
-            broadcaster.register(GlucoseObserver.self, observer: self)
             broadcaster.register(DeterminationObserver.self, observer: self)
             broadcaster.register(DeterminationObserver.self, observer: self)
             broadcaster.register(SettingsObserver.self, observer: self)
             broadcaster.register(SettingsObserver.self, observer: self)
             broadcaster.register(PreferencesObserver.self, observer: self)
             broadcaster.register(PreferencesObserver.self, observer: self)
@@ -554,7 +551,6 @@ extension Home {
 }
 }
 
 
 extension Home.StateModel:
 extension Home.StateModel:
-    GlucoseObserver,
     DeterminationObserver,
     DeterminationObserver,
     SettingsObserver,
     SettingsObserver,
     PreferencesObserver,
     PreferencesObserver,
@@ -565,11 +561,6 @@ extension Home.StateModel:
     PumpTimeZoneObserver,
     PumpTimeZoneObserver,
     PumpDeactivatedObserver
     PumpDeactivatedObserver
 {
 {
-    // TODO: still needed?
-    func glucoseDidUpdate(_: [BloodGlucose]) {
-//        setupGlucose()
-    }
-
     func determinationDidUpdate(_: Determination) {
     func determinationDidUpdate(_: Determination) {
         waitForSuggestion = false
         waitForSuggestion = false
     }
     }
@@ -606,11 +597,6 @@ extension Home.StateModel:
         lowTTlowersSens = settingsManager.preferences.lowTemptargetLowersSensitivity
         lowTTlowersSens = settingsManager.preferences.lowTemptargetLowersSensitivity
     }
     }
 
 
-    // TODO: is this ever really triggered? react to MOC changes?
-    func pumpHistoryDidUpdate(_: [PumpHistoryEvent]) {
-        displayPumpStatusHighlightMessage()
-    }
-
     func pumpSettingsDidChange(_: PumpSettings) {
     func pumpSettingsDidChange(_: PumpSettings) {
         Task {
         Task {
             await setupPumpSettings()
             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.
      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.
      - 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:
      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.
      - 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.
      - Handles conversions between mg/dL and mmol/L as per user settings.
      - Ensures targets span across midnight to avoid data cutoff.
      - 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] {
     private func processFetchedTargets(_ rawTargets: BGTargets) -> [TargetProfile] {
         var targetProfiles: [TargetProfile] = []
         var targetProfiles: [TargetProfile] = []
@@ -74,9 +69,9 @@ struct GlucoseTargetsView: ChartContent {
         // Base date is the start of the day for the startMarker
         // Base date is the start of the day for the startMarker
         let baseDate = Calendar.current.startOfDay(for: 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 dayOffset = index / targets.count
             let targetIndex = 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 safeAreaSize: CGFloat
     var units: GlucoseUnits
     var units: GlucoseUnits
     var hours: Int
     var hours: Int
-    var tempTargets: [TempTarget]
     var highGlucose: Decimal
     var highGlucose: Decimal
     var lowGlucose: Decimal
     var lowGlucose: Decimal
     var currentGlucoseTarget: Decimal
     var currentGlucoseTarget: Decimal

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

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

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

@@ -45,24 +45,44 @@ struct PumpView: View {
                     HStack {
                     HStack {
                         Image(systemName: "cross.vial.fill")
                         Image(systemName: "cross.vial.fill")
                             .font(.callout)
                             .font(.callout)
-                            .foregroundColor(reservoirColor)
+
                         if reservoir == 0xDEAD_BEEF {
                         if reservoir == 0xDEAD_BEEF {
                             Text("50+ " + NSLocalizedString("U", comment: "Insulin unit"))
                             Text("50+ " + NSLocalizedString("U", comment: "Insulin unit"))
-                                .font(.callout).fontWeight(.bold).fontDesign(.rounded)
+                                .font(.callout)
+                                .fontWeight(.bold)
+                                .fontDesign(.rounded)
                         } else {
                         } else {
                             Text(
                             Text(
                                 Formatter.integerFormatter
                                 Formatter.integerFormatter
                                     .string(from: reservoir as NSNumber)! + NSLocalizedString(" U", comment: "Insulin unit")
                                     .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() {
                     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 {
                     HStack {
                         Image(systemName: "battery.100")
                         Image(systemName: "battery.100")
                             .font(.callout)
                             .font(.callout)
-                            .foregroundColor(batteryColor)
+                            .foregroundStyle(batteryColor)
                         Text("\(Int(battery.first?.percent ?? 100)) %")
                         Text("\(Int(battery.first?.percent ?? 100)) %")
                             .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                             .font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
                     }
@@ -80,13 +100,15 @@ struct PumpView: View {
                     HStack {
                     HStack {
                         Image(systemName: "stopwatch.fill")
                         Image(systemName: "stopwatch.fill")
                             .font(.callout)
                             .font(.callout)
-                            .foregroundColor(timerColor)
+                            .foregroundStyle(timerColor)
 
 
                         Text(remainingTimeString(time: date.timeIntervalSince(timerDate)))
                         Text(remainingTimeString(time: date.timeIntervalSince(timerDate)))
                             .font(!(date.timeIntervalSince(timerDate) > 0) ? .subheadline : .callout)
                             .font(!(date.timeIntervalSince(timerDate) > 0) ? .subheadline : .callout)
                             .fontWeight(.bold)
                             .fontWeight(.bold)
                             .fontDesign(.rounded)
                             .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 {
         switch battery.percent {
         case ...10:
         case ...10:
-            return .red
+            return Color.loopRed
         case ...20:
         case ...20:
-            return .yellow
+            return Color.orange
         default:
         default:
-            return .green
+            return Color.loopGreen
         }
         }
     }
     }
 
 
@@ -138,11 +160,11 @@ struct PumpView: View {
 
 
         switch reservoir {
         switch reservoir {
         case ...10:
         case ...10:
-            return .red
+            return Color.loopRed
         case ...30:
         case ...30:
-            return .yellow
+            return Color.orange
         default:
         default:
-            return .blue
+            return Color.insulin
         }
         }
     }
     }
 
 
@@ -155,11 +177,11 @@ struct PumpView: View {
 
 
         switch time {
         switch time {
         case ...8.hours.timeInterval:
         case ...8.hours.timeInterval:
-            return .red
+            return Color.loopRed
         case ...1.days.timeInterval:
         case ...1.days.timeInterval:
-            return .yellow
+            return Color.orange
         default:
         default:
-            return .green
+            return Color.loopGreen
         }
         }
     }
     }
 }
 }

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

@@ -5,11 +5,9 @@ import SwiftUI
 import Swinject
 import Swinject
 
 
 struct TimePicker: Identifiable {
 struct TimePicker: Identifiable {
-    let label: String
-    let number: String
     var active: Bool
     var active: Bool
     let hours: Int16
     let hours: Int16
-    var id: String { label }
+    var id: String { hours.description }
 }
 }
 
 
 extension Home {
 extension Home {
@@ -36,15 +34,12 @@ extension Home {
         @State var showPumpSelection: Bool = false
         @State var showPumpSelection: Bool = false
         @State var notificationsDisabled = false
         @State var notificationsDisabled = false
         @State var timeButtons: [TimePicker] = [
         @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(
         @FetchRequest(fetchRequest: OverrideStored.fetch(
             NSPredicate.lastActiveOverride,
             NSPredicate.lastActiveOverride,
             ascending: false,
             ascending: false,
@@ -116,7 +111,8 @@ extension Home {
                 timeZone: state.timeZone,
                 timeZone: state.timeZone,
                 pumpStatusHighlightMessage: state.pumpStatusHighlightMessage,
                 pumpStatusHighlightMessage: state.pumpStatusHighlightMessage,
                 battery: state.batteryFromPersistence
                 battery: state.batteryFromPersistence
-            ).onTapGesture {
+            )
+            .onTapGesture {
                 if state.pumpDisplayState == nil {
                 if state.pumpDisplayState == nil {
                     // shows user confirmation dialog with pump model choices, then proceeds to setup
                     // shows user confirmation dialog with pump model choices, then proceeds to setup
                     showPumpSelection.toggle()
                     showPumpSelection.toggle()
@@ -245,45 +241,73 @@ extension Home {
             return components.isEmpty ? nil : components.joined(separator: ", ")
             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
                 ForEach(timeButtons) { button in
-                    Text(button.active ? NSLocalizedString(button.label, comment: "") : button.number).onTapGesture {
+                    Button(action: {
                         state.hours = button.hours
                         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 {
         @ViewBuilder func mainChart(geo: GeometryProxy) -> some View {
@@ -293,7 +317,6 @@ extension Home {
                     safeAreaSize: notificationsDisabled == true ? safeAreaSize : 0,
                     safeAreaSize: notificationsDisabled == true ? safeAreaSize : 0,
                     units: state.units,
                     units: state.units,
                     hours: state.filteredHours,
                     hours: state.filteredHours,
-                    tempTargets: state.tempTargets,
                     highGlucose: state.highGlucose,
                     highGlucose: state.highGlucose,
                     lowGlucose: state.lowGlucose,
                     lowGlucose: state.lowGlucose,
                     currentGlucoseTarget: state.currentGlucoseTarget,
                     currentGlucoseTarget: state.currentGlucoseTarget,
@@ -324,9 +347,11 @@ extension Home {
                     lastLoopDate: state.lastLoopDate,
                     lastLoopDate: state.lastLoopDate,
                     manualTempBasal: state.manualTempBasal,
                     manualTempBasal: state.manualTempBasal,
                     determination: state.determinationsFromPersistence
                     determination: state.determinationsFromPersistence
-                ).onTapGesture {
+                )
+                .onTapGesture {
                     state.isLoopStatusPresented = true
                     state.isLoopStatusPresented = true
-                }.onLongPressGesture {
+                }
+                .onLongPressGesture {
                     let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
                     let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
                     impactHeavy.impactOccurred()
                     impactHeavy.impactOccurred()
                     state.runLoop()
                     state.runLoop()
@@ -347,6 +372,8 @@ extension Home {
                             )!
                             )!
                         ).font(.callout).fontWeight(.bold).fontDesign(.rounded)
                         ).font(.callout).fontWeight(.bold).fontDesign(.rounded)
                     }
                     }
+                    // aligns the evBG icon exactly with the first pixel of loop status icon
+                    .padding(.leading, 12)
                 } else {
                 } else {
                     HStack {
                     HStack {
                         Image(systemName: "arrow.right.circle")
                         Image(systemName: "arrow.right.circle")
@@ -402,8 +429,17 @@ extension Home {
                         Image(systemName: "drop.circle")
                         Image(systemName: "drop.circle")
                             .font(.callout)
                             .font(.callout)
                             .foregroundColor(.insulinTintColor)
                             .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 {
                     } else {
                         Image(systemName: "drop.circle")
                         Image(systemName: "drop.circle")
                             .font(.callout)
                             .font(.callout)
@@ -815,8 +851,28 @@ extension Home {
 
 
                 mainChart(geo: geo)
                 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 {
                 if let progress = state.bolusProgress {
                     bolusView(geo: geo, progress)
                     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 maxCarbs: Decimal = 250
         @Published var maxFat: Decimal = 250
         @Published var maxFat: Decimal = 250
         @Published var maxProtein: 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() {
         override func subscribe() {
             units = settingsManager.settings.units
             units = settingsManager.settings.units
@@ -38,8 +38,7 @@ extension MealSettings {
             })
             })
 
 
             subscribeSetting(\.individualAdjustmentFactor, on: $individualAdjustmentFactor, initial: {
             subscribeSetting(\.individualAdjustmentFactor, on: $individualAdjustmentFactor, initial: {
-                let value = max(min($0, 1.2), 0.1)
-                individualAdjustmentFactor = value
+                individualAdjustmentFactor = $0
             }, map: {
             }, map: {
                 $0
                 $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("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("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("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("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("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("(Fat × 45%) + (Protein × 20%)")
                                     Text("100% is full effect:").bold()
                                     Text("100% is full effect:").bold()
                                     Text("(Fat × 90%) + (Protein × 40%)")
                                     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(
                                 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%."
                                     "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 {
 extension TargetsEditor {
     final class StateModel: BaseStateModel<Provider> {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() private var nightscout: NightscoutManager!
         @Injected() private var nightscout: NightscoutManager!
+        @Injected() private var broadcaster: Broadcaster!
 
 
         @Published var items: [Item] = []
         @Published var items: [Item] = []
         @Published var initialItems: [Item] = []
         @Published var initialItems: [Item] = []
@@ -75,6 +76,12 @@ extension TargetsEditor {
             provider.saveProfile(profile)
             provider.saveProfile(profile)
             initialItems = items.map { Item(lowIndex: $0.lowIndex, highIndex: $0.highIndex, timeIndex: $0.timeIndex) }
             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) {
             Task.detached(priority: .low) {
                 debug(.nightscout, "Attempting to upload targets to Nightscout")
                 debug(.nightscout, "Attempting to upload targets to Nightscout")
                 await self.nightscout.uploadProfiles()
                 await self.nightscout.uploadProfiles()