Procházet zdrojové kódy

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

Deniz Cengiz před 1 rokem
rodič
revize
7fa12f457f
32 změnil soubory, kde provedl 357 přidání a 85 odebrání
  1. 1 0
      LiveActivity/LiveActivity.swift
  2. 0 1
      LiveActivity/Views/LiveActivityGlucoseDeltaLabelView.swift
  3. 13 6
      LiveActivity/Views/LiveActivityView.swift
  4. 32 0
      LiveActivity/Views/WidgetItems/LiveActivityBGLabelLargeView.swift
  5. 35 0
      LiveActivity/Views/WidgetItems/LiveActivityTotalDailyDoseView.swift
  6. 1 1
      Trio/Sources/APS/Storage/DeterminationStorage.swift
  7. 1 0
      Trio/Sources/Helpers/Formatters.swift
  8. 1 0
      Trio/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+TempTargets.swift
  9. 0 2
      Trio/Sources/Modules/Adjustments/View/TempTargets/AddTempTargetForm.swift
  10. 1 2
      Trio/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift
  11. 1 1
      Trio/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift
  12. 21 1
      Trio/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift
  13. 20 1
      Trio/Sources/Modules/CarbRatioEditor/View/CarbRatioEditorRootView.swift
  14. 1 1
      Trio/Sources/Modules/DynamicSettings/DynamicSettingsStateModel.swift
  15. 1 1
      Trio/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift
  16. 66 28
      Trio/Sources/Modules/Home/View/Header/LoopStatusView.swift
  17. 12 2
      Trio/Sources/Modules/Home/View/Header/PumpView.swift
  18. 20 1
      Trio/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift
  19. 35 4
      Trio/Sources/Modules/LiveActivitySettings/View/LiveActivityWidgetConfiguration.swift
  20. 14 14
      Trio/Sources/Modules/MealSettings/View/MealSettingsRootView.swift
  21. 1 1
      Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift
  22. 15 2
      Trio/Sources/Modules/Settings/SettingItems.swift
  23. 20 1
      Trio/Sources/Modules/TargetsEditor/View/TargetsEditorRootView.swift
  24. 9 4
      Trio/Sources/Modules/Treatments/View/MealPreset/AddMealPresetView.swift
  25. 13 4
      Trio/Sources/Modules/Treatments/View/MealPreset/MealPresetView.swift
  26. 3 3
      Trio/Sources/Modules/UserInterfaceSettings/View/UserInterfaceSettingsRootView.swift
  27. 8 0
      Trio/Sources/Services/Calendar/CalendarManager.swift
  28. 4 2
      Trio/Sources/Services/ContactImage/ContactImageManager.swift
  29. 2 1
      Trio/Sources/Services/LiveActivity/Data/DataManager.swift
  30. 1 0
      Trio/Sources/Services/LiveActivity/Data/DeterminationData.swift
  31. 4 1
      Trio/Sources/Services/LiveActivity/LiveActitiyAttributes.swift
  32. 1 0
      Trio/Sources/Services/LiveActivity/LiveActivityAttributes+Helper.swift

+ 1 - 0
LiveActivity/LiveActivity.swift

@@ -84,6 +84,7 @@ private extension LiveActivityAttributes.ContentState {
         rotationDegrees: 0,
         cob: 20,
         iob: 1.5,
+        tdd: 43.21,
         isOverrideActive: false,
         overrideName: "Exercise",
         overrideDate: Date().addingTimeInterval(-3600),

+ 0 - 1
LiveActivity/Views/LiveActivityGlucoseDeltaLabelView.swift

@@ -11,7 +11,6 @@ import WidgetKit
 struct LiveActivityGlucoseDeltaLabelView: View {
     var context: ActivityViewContext<LiveActivityAttributes>
     var glucoseColor: Color
-    var isDetailed: Bool = false
 
     var body: some View {
         if !context.state.change.isEmpty {

+ 13 - 6
LiveActivity/Views/LiveActivityView.swift

@@ -63,23 +63,31 @@ struct LiveActivityView: View {
                             case .currentGlucose:
                                 VStack {
                                     LiveActivityBGLabelView(context: context, additionalState: detailedViewState)
+
                                     HStack {
                                         LiveActivityGlucoseDeltaLabelView(
                                             context: context,
-                                            glucoseColor: .primary,
-                                            isDetailed: true
+                                            glucoseColor: .primary
                                         )
                                         if !context.isStale, let direction = context.state.direction {
                                             Text(direction).font(.headline)
                                         }
                                     }
                                 }
+                            case .currentGlucoseLarge:
+                                LiveActivityBGLabelLargeView(
+                                    context: context,
+                                    additionalState: detailedViewState,
+                                    glucoseColor: glucoseColor
+                                )
                             case .iob:
                                 LiveActivityIOBLabelView(context: context, additionalState: detailedViewState)
                             case .cob:
                                 LiveActivityCOBLabelView(context: context, additionalState: detailedViewState)
                             case .updatedLabel:
                                 LiveActivityUpdatedLabelView(context: context, isDetailedLayout: true)
+                            case .totalDailyDose:
+                                LiveActivityTotalDailyDoseView(context: context, additionalState: detailedViewState)
                             case .empty:
                                 Text("").frame(width: 50, height: 50)
                             }
@@ -120,8 +128,7 @@ struct LiveActivityView: View {
                         VStack(alignment: .trailing, spacing: 5) {
                             LiveActivityGlucoseDeltaLabelView(
                                 context: context,
-                                glucoseColor: hasStaticColorScheme ? .primary : glucoseColor,
-                                isDetailed: false
+                                glucoseColor: hasStaticColorScheme ? .primary : glucoseColor
                             ).font(.title3)
                             LiveActivityUpdatedLabelView(context: context, isDetailedLayout: false).font(.caption)
                                 .foregroundStyle(.primary.opacity(0.7))
@@ -158,7 +165,7 @@ struct LiveActivityExpandedTrailingView: View {
     var glucoseColor: Color
 
     var body: some View {
-        LiveActivityGlucoseDeltaLabelView(context: context, glucoseColor: glucoseColor, isDetailed: false).font(.title2)
+        LiveActivityGlucoseDeltaLabelView(context: context, glucoseColor: glucoseColor).font(.title2)
             .padding(.trailing, 5)
     }
 }
@@ -198,7 +205,7 @@ struct LiveActivityCompactTrailingView: View {
     var glucoseColor: Color
 
     var body: some View {
-        LiveActivityGlucoseDeltaLabelView(context: context, glucoseColor: glucoseColor, isDetailed: false).padding(.trailing, 4)
+        LiveActivityGlucoseDeltaLabelView(context: context, glucoseColor: glucoseColor).padding(.trailing, 4)
     }
 }
 

+ 32 - 0
LiveActivity/Views/WidgetItems/LiveActivityBGLabelLargeView.swift

@@ -0,0 +1,32 @@
+import Foundation
+import SwiftUI
+import WidgetKit
+
+struct LiveActivityBGLabelLargeView: View {
+    var context: ActivityViewContext<LiveActivityAttributes>
+    var additionalState: LiveActivityAttributes.ContentAdditionalState
+    var glucoseColor: Color
+
+    var body: some View {
+        HStack(alignment: .center) {
+            if let trendArrow = context.state.direction {
+                Text(context.state.bg)
+                    .fontWeight(.bold)
+                    .font(.title)
+                    .foregroundStyle(context.isStale ? .secondary : glucoseColor)
+                    .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
+
+                Text(trendArrow)
+                    .foregroundStyle(context.isStale ? .secondary : glucoseColor)
+                    .fontWeight(.bold)
+                    .font(.headline)
+            } else {
+                Text(context.state.bg)
+                    .fontWeight(.bold)
+                    .font(.title)
+                    .foregroundStyle(context.isStale ? .secondary : glucoseColor)
+                    .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
+            }
+        }
+    }
+}

+ 35 - 0
LiveActivity/Views/WidgetItems/LiveActivityTotalDailyDoseView.swift

@@ -0,0 +1,35 @@
+import Foundation
+import SwiftUI
+import WidgetKit
+
+struct LiveActivityTotalDailyDoseView: View {
+    var context: ActivityViewContext<LiveActivityAttributes>
+    var additionalState: LiveActivityAttributes.ContentAdditionalState
+
+    private var bolusFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 1
+        return formatter
+    }
+
+    var body: some View {
+        VStack(spacing: 2) {
+            HStack {
+                Text(
+                    bolusFormatter.string(from: additionalState.tdd as NSNumber) ?? "--"
+                )
+                .fontWeight(.bold)
+                .font(.title3)
+                .foregroundStyle(context.isStale ? .secondary : .primary)
+                .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
+
+                Text("U")
+                    .font(.headline).fontWeight(.bold)
+                    .foregroundStyle(context.isStale ? .secondary : .primary)
+                    .strikethrough(context.isStale, pattern: .solid, color: .red.opacity(0.6))
+            }
+            Text("TDD").font(.subheadline).foregroundStyle(.primary)
+        }
+    }
+}

+ 1 - 1
Trio/Sources/APS/Storage/DeterminationStorage.swift

@@ -169,7 +169,7 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
                         rate: self.decimal(from: orefDetermination.rate),
                         duration: self.decimal(from: orefDetermination.duration),
                         iob: self.decimal(from: orefDetermination.iob),
-                        cob: orefDetermination.cob != 0 ? Decimal(orefDetermination.cob) : nil,
+                        cob: Decimal(orefDetermination.cob),
                         predictions: predictions,
                         deliverAt: orefDetermination.deliverAt,
                         carbsReq: orefDetermination.carbsRequired != 0 ? Decimal(orefDetermination.carbsRequired) : nil,

+ 1 - 0
Trio/Sources/Helpers/Formatters.swift

@@ -63,6 +63,7 @@ extension Formatter {
 
         switch units {
         case .mmolL:
+            formatter.minimumFractionDigits = 1
             formatter.maximumFractionDigits = 1
         case .mgdL:
             formatter.maximumFractionDigits = 0

+ 1 - 0
Trio/Sources/Modules/Adjustments/AdjustmentsStateModel+Extensions/AdjustmentsStateModel+TempTargets.swift

@@ -355,6 +355,7 @@ extension Adjustments.StateModel {
         tempTargetDuration = 0
         percentage = 100
         halfBasalTarget = settingHalfBasalTarget
+        date = Date()
     }
 
     // MARK: - Calculations

+ 0 - 2
Trio/Sources/Modules/Adjustments/View/TempTargets/AddTempTargetForm.swift

@@ -107,8 +107,6 @@ struct AddTempTargetForm: View {
             .listRowBackground(Color.chart)
 
             if state.tempTargetTarget != state.normalTarget {
-                let computedHalfBasalTarget = Decimal(state.computeHalfBasalTarget())
-
                 if state.isAdjustSensEnabled() {
                     Section(
                         footer: state.percentageDescription(state.percentage),

+ 1 - 2
Trio/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift

@@ -21,8 +21,7 @@ extension AlgorithmAdvancedSettings {
         @Published var remainingCarbsFraction: Decimal = 1.0
         @Published var remainingCarbsCap: Decimal = 90
         @Published var noisyCGMTargetMultiplier: Decimal = 1.3
-
-        var insulinActionCurve: Decimal = 10
+        @Published var insulinActionCurve: Decimal = 10
 
         var pumpSettings: PumpSettings {
             provider.settings()

+ 1 - 1
Trio/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift

@@ -12,7 +12,7 @@ extension AutosensSettings {
 
         private(set) var autosensISF: Decimal?
         private(set) var autosensRatio: Decimal = 0
-        var determinationsFromPersistence: [OrefDetermination] = []
+        @Published var determinationsFromPersistence: [OrefDetermination] = []
 
         let viewContext = CoreDataStack.shared.persistentContainer.viewContext
 

+ 21 - 1
Trio/Sources/Modules/BasalProfileEditor/View/BasalProfileEditorRootView.swift

@@ -24,6 +24,7 @@ extension BasalProfileEditor {
         private var rateFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             formatter.numberStyle = .decimal
+
             return formatter
         }
 
@@ -140,6 +141,20 @@ extension BasalProfileEditor {
                             .foregroundColor(.secondary)
                     }
                 }.listRowBackground(Color.chart)
+
+                Section {} header: {
+                    VStack(alignment: .leading, spacing: 10) {
+                        HStack {
+                            Image(systemName: "note.text.badge.plus").foregroundStyle(.primary)
+                            Text("Add an entry by tapping 'Add Rate +' in the top right-hand corner of the screen.")
+                        }
+                        HStack {
+                            Image(systemName: "hand.draw.fill").foregroundStyle(.primary)
+                            Text("Swipe to delete a single entry. Tap on it, to edit its time or rate.")
+                        }
+                    }
+                    .textCase(nil)
+                }
             }
             .safeAreaInset(edge: .bottom, spacing: 30) { saveButton }
             .alert(isPresented: $state.showAlert) {
@@ -163,7 +178,12 @@ extension BasalProfileEditor {
                     }
                 }
                 ToolbarItem(placement: .topBarTrailing) {
-                    Button(action: { state.add() }) { Image(systemName: "plus") }.disabled(!state.canAdd)
+                    Button(action: { state.add() }) {
+                        HStack {
+                            Text("Add Rate")
+                            Image(systemName: "plus")
+                        }
+                    }.disabled(!state.canAdd)
                 }
             })
             .environment(\.editMode, $editMode)

+ 20 - 1
Trio/Sources/Modules/CarbRatioEditor/View/CarbRatioEditorRootView.swift

@@ -83,6 +83,20 @@ extension CarbRatioEditor {
                 Section(header: Text("Schedule")) {
                     list
                 }.listRowBackground(Color.chart)
+
+                Section {} header: {
+                    VStack(alignment: .leading, spacing: 10) {
+                        HStack {
+                            Image(systemName: "note.text.badge.plus").foregroundStyle(.primary)
+                            Text("Add an entry by tapping 'Add Ratio +' in the top right-hand corner of the screen.")
+                        }
+                        HStack {
+                            Image(systemName: "hand.draw.fill").foregroundStyle(.primary)
+                            Text("Swipe to delete a single entry. Tap on it, to edit its time or rate.")
+                        }
+                    }
+                    .textCase(nil)
+                }
             }
             .safeAreaInset(edge: .bottom, spacing: 30) { saveButton }
             .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
@@ -96,7 +110,12 @@ extension CarbRatioEditor {
                     }
                 }
                 ToolbarItem(placement: .topBarTrailing) {
-                    Button(action: { state.add() }) { Image(systemName: "plus") }.disabled(!state.canAdd)
+                    Button(action: { state.add() }) {
+                        HStack {
+                            Text("Add Ratio")
+                            Image(systemName: "plus")
+                        }
+                    }.disabled(!state.canAdd)
                 }
             })
             .environment(\.editMode, $editMode)

+ 1 - 1
Trio/Sources/Modules/DynamicSettings/DynamicSettingsStateModel.swift

@@ -27,7 +27,7 @@ extension DynamicSettings {
             subscribePreferencesSetting(\.adjustmentFactorSigmoid, on: $adjustmentFactorSigmoid) { adjustmentFactorSigmoid = $0 }
             subscribePreferencesSetting(\.weightPercentage, on: $weightPercentage) { weightPercentage = $0 }
             subscribePreferencesSetting(\.tddAdjBasal, on: $tddAdjBasal) { tddAdjBasal = $0 }
-            subscribePreferencesSetting(\.tddAdjBasal, on: $tddAdjBasal) { tddAdjBasal = $0 }
+            subscribePreferencesSetting(\.threshold_setting, on: $threshold_setting) { threshold_setting = $0 }
         }
     }
 }

+ 1 - 1
Trio/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift

@@ -154,7 +154,7 @@ extension UnitsLimitsSettings {
             }
             .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .onAppear(perform: configureView)
-            .navigationTitle("General")
+            .navigationTitle("Units and Limits")
             .navigationBarTitleDisplayMode(.automatic)
             .onDisappear {
                 state.saveIfChanged()

+ 66 - 28
Trio/Sources/Modules/Home/View/Header/LoopStatusView.swift

@@ -6,7 +6,7 @@ struct LoopStatusView: View {
 
     var state: Home.StateModel
 
-    @State var sheetDetent = PresentationDetent.fraction(0.8)
+    @State private var sheetContentHeight = CGFloat.zero
     // Help Sheet
     @State var isHelpSheetPresented: Bool = false
     @State var helpSheetDetent = PresentationDetent.fraction(0.9)
@@ -14,23 +14,49 @@ struct LoopStatusView: View {
     @State private var statusTitle: String = ""
 
     var body: some View {
-        NavigationStack {
+        ScrollView {
             VStack(alignment: .leading, spacing: 10) {
-                Text("Current Loop Status").bold().padding(.top, 20)
+                HStack(alignment: .top) {
+                    VStack(alignment: .leading, spacing: 10) {
+                        Text("Current Loop Status").bold()
 
-                Text(statusTitle)
-                    .font(.headline)
-                    .bold()
-                    .padding(.horizontal, 12)
-                    .padding(.vertical, 6)
-                    .foregroundColor(statusBadgeTextColor)
-                    .background(statusBadgeColor)
-                    .clipShape(Capsule())
+                        Text(statusTitle)
+                            .font(.headline)
+                            .bold()
+                            .padding(.horizontal, 12)
+                            .padding(.vertical, 6)
+                            .foregroundColor(statusBadgeTextColor)
+                            .background(statusBadgeColor)
+                            .clipShape(Capsule())
+                    }
+
+                    Spacer()
+
+                    Button(
+                        action: {
+                            isHelpSheetPresented.toggle()
+                        },
+                        label: {
+                            Image(systemName: "questionmark.circle")
+                        }
+                    )
+                }.padding(.top, 20)
+//                Text("Current Loop Status").bold().padding(.top, 20)
+//
+//                Text(statusTitle)
+//                    .font(.headline)
+//                    .bold()
+//                    .padding(.horizontal, 12)
+//                    .padding(.vertical, 6)
+//                    .foregroundColor(statusBadgeTextColor)
+//                    .background(statusBadgeColor)
+//                    .clipShape(Capsule())
 
                 if let errorMessage = state.errorMessage, let date = state.errorDate {
                     Group {
                         Text("Error During Algorithm Run at \(Formatter.dateFormatter.string(from: date))").font(.headline)
-                        Text(errorMessage).font(.caption)
+                            .fixedSize(horizontal: false, vertical: true)
+                        Text(errorMessage).font(.caption).fixedSize(horizontal: false, vertical: true)
                     }.foregroundColor(.loopRed)
                 }
 
@@ -40,9 +66,11 @@ struct LoopStatusView: View {
                             .bold()
                             .padding(.top)
                             .foregroundStyle(Color.loopRed)
+                            .fixedSize(horizontal: false, vertical: true)
 
                         Text("SMBs and Non-Zero Temp. Basal Rates are disabled.")
                             .font(.subheadline)
+                            .fixedSize(horizontal: false, vertical: true)
 
                     } else {
                         Text("Latest Raw Algorithm Output")
@@ -94,24 +122,13 @@ struct LoopStatusView: View {
             }
             .padding(.vertical)
             .padding(.horizontal, 20)
-            .presentationDetents(
-                [.fraction(0.8), .large],
-                selection: $sheetDetent
-            )
             .ignoresSafeArea(edges: .top)
-            .background(appState.trioBackgroundColor(for: colorScheme))
-            .toolbar(content: {
-                ToolbarItem(placement: .topBarTrailing) {
-                    Button(
-                        action: {
-                            isHelpSheetPresented.toggle()
-                        },
-                        label: {
-                            Image(systemName: "questionmark.circle")
-                        }
-                    )
+            .background {
+                GeometryReader { geo in
+                    Color.clear
+                        .preference(key: ContentSizeKey.self, value: geo.size)
                 }
-            })
+            }
             .onAppear {
                 setStatusTitle()
             }
@@ -119,6 +136,12 @@ struct LoopStatusView: View {
                 LoopStatusHelpView(state: state, helpSheetDetent: $helpSheetDetent, isHelpSheetPresented: $isHelpSheetPresented)
             }
         }
+        .presentationDetents([.height(sheetContentHeight)])
+        .presentationDragIndicator(.visible)
+        .onPreferenceChange(ContentSizeKey.self) { newSize in
+            sheetContentHeight = newSize.height
+        }
+        .background(appState.trioBackgroundColor(for: colorScheme))
         .scrollContentBackground(.hidden)
     }
 
@@ -252,3 +275,18 @@ struct LoopStatusView: View {
         return updatedConclusion.capitalizingFirstLetter()
     }
 }
+
+struct ContentSizeKey: PreferenceKey {
+    static var defaultValue: CGSize = .zero
+
+    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
+        // If multiple views report sizes, pick whichever logic you prefer:
+        // - The largest height
+        // - The sum
+        // For a single child, just use nextValue() directly if you like.
+        let next = nextValue()
+        if next.height > value.height {
+            value = next
+        }
+    }
+}

+ 12 - 2
Trio/Sources/Modules/Home/View/Header/PumpView.swift

@@ -102,10 +102,20 @@ struct PumpView: View {
                             .font(.callout)
                             .foregroundStyle(timerColor)
 
-                        Text(remainingTimeString(time: date.timeIntervalSince(timerDate)))
-                            .font(.callout)
+                        let remainingTimeString = remainingTimeString(time: date.timeIntervalSince(timerDate))
+
+                        Text(remainingTimeString)
+                            .font(date.timeIntervalSince(timerDate) > 0 ? .callout : .subheadline)
                             .fontWeight(.bold)
                             .fontDesign(.rounded)
+                            .lineLimit(2)
+                            .multilineTextAlignment(.leading)
+                            .frame(
+                                // If the string is > 6 chars, i.e., exceeds "xd yh", limit width to 80 pts
+                                // This forces the "Replace pod" string to wrap to 2 lines.
+                                maxWidth: remainingTimeString.count > 6 ? 80 : .infinity,
+                                alignment: .leading
+                            )
                     }
                     // aligns the stopwatch icon exactly with the first pixel of the reservoir icon
                     .padding(.leading, date.timeIntervalSince(timerDate) > 0 ? 12 : 0)

+ 20 - 1
Trio/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift

@@ -81,6 +81,20 @@ extension ISFEditor {
                 Section(header: Text("Schedule")) {
                     list
                 }.listRowBackground(Color.chart)
+
+                Section {} header: {
+                    VStack(alignment: .leading, spacing: 10) {
+                        HStack {
+                            Image(systemName: "note.text.badge.plus").foregroundStyle(.primary)
+                            Text("Add an entry by tapping 'Add Sensitivity +' in the top right-hand corner of the screen.")
+                        }
+                        HStack {
+                            Image(systemName: "hand.draw.fill").foregroundStyle(.primary)
+                            Text("Swipe left to delete a single entry. Tap on it, to edit its time or rate.")
+                        }
+                    }
+                    .textCase(nil)
+                }
             }
             .safeAreaInset(edge: .bottom, spacing: 30) { saveButton }
             .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
@@ -94,7 +108,12 @@ extension ISFEditor {
                     }
                 }
                 ToolbarItem(placement: .topBarTrailing) {
-                    Button(action: { state.add() }) { Image(systemName: "plus") }.disabled(!state.canAdd)
+                    Button(action: { state.add() }) {
+                        HStack {
+                            Text("Add Sensitivity")
+                            Image(systemName: "plus")
+                        }
+                    }.disabled(!state.canAdd)
                 }
             })
             .environment(\.editMode, $editMode)

+ 35 - 4
Trio/Sources/Modules/LiveActivitySettings/View/LiveActivityWidgetConfiguration.swift

@@ -199,6 +199,8 @@ struct LiveActivityWidgetConfiguration: BaseView {
 
     private func getItemPreview(for item: LiveActivityItem) -> some View {
         switch item {
+        case .currentGlucoseLarge:
+            return AnyView(currentGlucoseLargePreview)
         case .currentGlucose:
             return AnyView(currentGlucosePreview)
         case .cob:
@@ -207,6 +209,8 @@ struct LiveActivityWidgetConfiguration: BaseView {
             return AnyView(iobPreview)
         case .updatedLabel:
             return AnyView(updatedLabelPreview)
+        case .totalDailyDose:
+            return AnyView(totalDailyDosePreview)
         }
     }
 
@@ -253,6 +257,16 @@ struct LiveActivityWidgetConfiguration: BaseView {
         .frame(height: 100)
     }
 
+    private var currentGlucoseLargePreview: some View {
+        HStack(alignment: .center) {
+            Text("123")
+                + Text("\u{2192}")
+        }
+        .foregroundStyle(Color.loopGreen)
+        .fontWeight(.bold)
+        .font(.subheadline)
+    }
+
     private var currentGlucosePreview: some View {
         VStack {
             HStack(alignment: .center) {
@@ -294,6 +308,17 @@ struct LiveActivityWidgetConfiguration: BaseView {
         }
     }
 
+    private var totalDailyDosePreview: some View {
+        VStack {
+            Text("43.21 U")
+                .fontWeight(.bold)
+                .font(.caption)
+                .foregroundStyle(.primary)
+
+            Text("TDD").font(.caption2).foregroundStyle(.primary)
+        }
+    }
+
     private func loadOrder() {
         if let savedItems = UserDefaults.standard.loadLiveActivityOrder() {
             selectedItems = savedItems.count == 4 ? savedItems : savedItems + Array(repeating: nil, count: 4 - savedItems.count)
@@ -342,27 +367,33 @@ extension UserDefaults {
 
 // Enum to represent each live activity item
 enum LiveActivityItem: String, CaseIterable, Identifiable {
+    case currentGlucoseLarge
     case currentGlucose
     case iob
     case cob
     case updatedLabel
+    case totalDailyDose
 
     var id: String { rawValue }
 
     static var defaultItems: [LiveActivityItem] {
-        [.currentGlucose, .iob, .cob, .updatedLabel]
+        [.currentGlucoseLarge, .iob, .cob, .updatedLabel]
     }
 
     var displayName: String {
         switch self {
+        case .currentGlucoseLarge:
+            return "Glucose and Trend, no Delta"
         case .currentGlucose:
-            return "Current Glucose"
+            return "Glucose, Trend, Delta"
         case .iob:
-            return "IOB"
+            return "Insulin on Board (IOB)"
         case .cob:
-            return "COB"
+            return "Carbs on Board (IOB)"
         case .updatedLabel:
             return "Last Updated"
+        case .totalDailyDose:
+            return "Total Daily Dose"
         }
     }
 }

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

@@ -81,26 +81,26 @@ extension MealSettings {
                             if state.useFPUconversion {
                                 VStack {
                                     HStack {
-                                        Text("Max Fat")
+                                        Text("Max Protein")
 
                                         Spacer()
 
                                         Group {
-                                            Text(state.maxFat.description)
-                                                .foregroundColor(!displayPickerMaxFat ? .primary : .accentColor)
+                                            Text(state.maxProtein.description)
+                                                .foregroundColor(!displayPickerMaxProtein ? .primary : .accentColor)
 
                                             Text(" g").foregroundColor(.secondary)
                                         }
                                     }
                                     .onTapGesture {
-                                        displayPickerMaxFat.toggle()
+                                        displayPickerMaxProtein.toggle()
                                     }
                                 }
                                 .padding(.top)
 
-                                if displayPickerMaxFat {
-                                    let setting = PickerSettingsProvider.shared.settings.maxFat
-                                    Picker(selection: $state.maxFat, label: Text("")) {
+                                if displayPickerMaxProtein {
+                                    let setting = PickerSettingsProvider.shared.settings.maxProtein
+                                    Picker(selection: $state.maxProtein, label: Text("")) {
                                         ForEach(
                                             PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
                                             id: \.self
@@ -114,26 +114,26 @@ extension MealSettings {
 
                                 VStack {
                                     HStack {
-                                        Text("Max Protein")
+                                        Text("Max Fat")
 
                                         Spacer()
 
                                         Group {
-                                            Text(state.maxProtein.description)
-                                                .foregroundColor(!displayPickerMaxProtein ? .primary : .accentColor)
+                                            Text(state.maxFat.description)
+                                                .foregroundColor(!displayPickerMaxFat ? .primary : .accentColor)
 
                                             Text(" g").foregroundColor(.secondary)
                                         }
                                     }
                                     .onTapGesture {
-                                        displayPickerMaxProtein.toggle()
+                                        displayPickerMaxFat.toggle()
                                     }
                                 }
                                 .padding(.top)
 
-                                if displayPickerMaxProtein {
-                                    let setting = PickerSettingsProvider.shared.settings.maxProtein
-                                    Picker(selection: $state.maxProtein, label: Text("")) {
+                                if displayPickerMaxFat {
+                                    let setting = PickerSettingsProvider.shared.settings.maxFat
+                                    Picker(selection: $state.maxFat, label: Text("")) {
                                         ForEach(
                                             PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
                                             id: \.self

+ 1 - 1
Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift

@@ -332,7 +332,7 @@ extension SMBSettings {
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: 20% increase").bold()
                         Text(
-                            "Maximum allowed positive percentual change in glucose level to permit SMBs. If the difference in glucose is greater than this, Trio will disable SMBs."
+                            "Maximum allowed positive percent change in glucose level to permit SMBs. If the difference in glucose is greater than this, Trio will disable SMBs."
                         )
                         Text("Note: This setting has a hard-coded cap of 40%")
                     }

+ 15 - 2
Trio/Sources/Modules/Settings/SettingItems.swift

@@ -88,6 +88,13 @@ enum SettingItems {
             view: .smbSettings,
             searchContents: [
                 "Enable SMB Always",
+                "Enable SMB With COB",
+                "Enable SMB With Temporary Target",
+                "Enable SMB After Carbs",
+                "Enable SMB With High BG",
+                "High BG Target",
+                "Allow SMB With High Temporary Target",
+                "Enable UAM",
                 "Max SMB Basal Minutes",
                 "Max UAM SMB Basal Minutes",
                 "Max Delta-BG Threshold SMB",
@@ -104,6 +111,7 @@ enum SettingItems {
                 "Activate Dynamic Carb Ratio (CR)",
                 "Use Sigmoid Formula",
                 "Adjustment Factor",
+                "AF",
                 "Sigmoid Adjustment Factor",
                 "Weighted Average of TDD",
                 "Adjust Basal",
@@ -169,7 +177,8 @@ enum SettingItems {
                 "Fat and Protein Delay",
                 "Maximum Duration (hours)",
                 "Spread Interval (minutes)",
-                "Fat and Protein Factor"
+                "Fat and Protein Factor",
+                "FPU"
             ],
             path: ["Features", "Meal Settings"]
         ),
@@ -205,7 +214,11 @@ enum SettingItems {
                 "Forecast Display Type",
                 "Cone",
                 "Lines",
-                "Trio Color Scheme",
+                "Dark Mode",
+                "Light Mode",
+                "Appearance",
+                "Dark Scheme",
+                "Light Scheme",
                 "Glucose Color Scheme"
             ],
             path: ["Features", "User Interface"]

+ 20 - 1
Trio/Sources/Modules/TargetsEditor/View/TargetsEditorRootView.swift

@@ -65,6 +65,20 @@ extension TargetsEditor {
                 Section(header: Text("Schedule")) {
                     list
                 }.listRowBackground(Color.chart)
+
+                Section {} header: {
+                    VStack(alignment: .leading, spacing: 10) {
+                        HStack {
+                            Image(systemName: "note.text.badge.plus").foregroundStyle(.primary)
+                            Text("Add an entry by tapping 'Add Target +' in the top right-hand corner of the screen.")
+                        }
+                        HStack {
+                            Image(systemName: "hand.draw.fill").foregroundStyle(.primary)
+                            Text("Swipe to delete a single entry. Tap on it, to edit its time or rate.")
+                        }
+                    }
+                    .textCase(nil)
+                }
             }
             .safeAreaInset(edge: .bottom, spacing: 30) { saveButton }
             .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
@@ -78,7 +92,12 @@ extension TargetsEditor {
                     }
                 }
                 ToolbarItem(placement: .topBarTrailing) {
-                    Button(action: { state.add() }) { Image(systemName: "plus") }.disabled(!state.canAdd)
+                    Button(action: { state.add() }) {
+                        HStack {
+                            Text("Add Target")
+                            Image(systemName: "plus")
+                        }
+                    }.disabled(!state.canAdd)
                 }
             })
             .environment(\.editMode, $editMode)

+ 9 - 4
Trio/Sources/Modules/Treatments/View/MealPreset/AddMealPresetView.swift

@@ -74,16 +74,21 @@ struct AddMealPresetView: View {
 
     @ViewBuilder private func proteinAndFat() -> some View {
         HStack {
-            Text("Fat").foregroundColor(.orange)
+            Text("Protein").foregroundColor(.red)
             Spacer()
-            TextFieldWithToolBar(text: $presetFat, placeholder: "0", keyboardType: .numberPad, numberFormatter: mealFormatter)
+            TextFieldWithToolBar(
+                text: $presetProtein,
+                placeholder: "0",
+                keyboardType: .numberPad,
+                numberFormatter: mealFormatter
+            )
             Text("g").foregroundColor(.secondary)
         }
         HStack {
-            Text("Protein").foregroundColor(.red)
+            Text("Fat").foregroundColor(.orange)
             Spacer()
             TextFieldWithToolBar(
-                text: $presetProtein,
+                text: $presetFat,
                 placeholder: "0",
                 keyboardType: .numberPad,
                 numberFormatter: mealFormatter

+ 13 - 4
Trio/Sources/Modules/Treatments/View/MealPreset/MealPresetView.swift

@@ -115,6 +115,15 @@ struct MealPresetView: View {
                         Text(preset.dish ?? "").tag(preset as MealPresetStored?)
                     }
                 }
+                .onChange(of: state.selection) {
+                    carbs += ((state.selection?.carbs ?? 0) as NSDecimalNumber) as Decimal
+                    if state.useFPUconversion {
+                        fat += ((state.selection?.fat ?? 0) as NSDecimalNumber) as Decimal
+                        protein += ((state.selection?.protein ?? 0) as NSDecimalNumber) as Decimal
+                    }
+
+                    state.addPresetToNewMeal()
+                }
                 .labelsHidden()
                 .frame(maxWidth: .infinity, alignment: .center)
                 if state.selection != nil {
@@ -207,11 +216,11 @@ struct MealPresetView: View {
 
                     if state.useFPUconversion {
                         Group {
-                            Text("Fat: ")
+                            Text("Protein: ")
                                 .font(.footnote)
                                 .foregroundStyle(.secondary)
                             HStack(spacing: 2) {
-                                Text("\(fat as NSNumber, formatter: mealFormatter)")
+                                Text("\(protein as NSNumber, formatter: mealFormatter)")
                                     .font(.footnote)
                                 Text(" g")
                                     .font(.footnote)
@@ -220,11 +229,11 @@ struct MealPresetView: View {
                         }
 
                         Group {
-                            Text("Protein: ")
+                            Text("Fat: ")
                                 .font(.footnote)
                                 .foregroundStyle(.secondary)
                             HStack(spacing: 2) {
-                                Text("\(protein as NSNumber, formatter: mealFormatter)")
+                                Text("\(fat as NSNumber, formatter: mealFormatter)")
                                     .font(.footnote)
                                 Text(" g")
                                     .font(.footnote)

+ 3 - 3
Trio/Sources/Modules/UserInterfaceSettings/View/UserInterfaceSettingsRootView.swift

@@ -47,7 +47,7 @@ extension UserInterfaceSettings {
                         VStack {
                             Picker(
                                 selection: $colorSchemePreference,
-                                label: Text("Trio Color Scheme")
+                                label: Text("Appearance")
                             ) {
                                 ForEach(ColorSchemeOption.allCases) { selection in
                                     Text(selection.displayName).tag(selection)
@@ -56,7 +56,7 @@ extension UserInterfaceSettings {
 
                             HStack(alignment: .center) {
                                 Text(
-                                    "Choose app color scheme. See hint for more details."
+                                    "Choose Trio's appearance. See hint for more details."
                                 )
                                 .font(.footnote)
                                 .foregroundColor(.secondary)
@@ -69,7 +69,7 @@ extension UserInterfaceSettings {
                                             AnyView(
                                                 VStack(alignment: .leading, spacing: 10) {
                                                     Text(
-                                                        "Set the app color scheme. Descriptions of each option found below."
+                                                        "Sets Trio's appearance. Descriptions of each option found below."
                                                     )
                                                     VStack(alignment: .leading, spacing: 5) {
                                                         Text("System Default:").bold()

+ 8 - 0
Trio/Sources/Services/Calendar/CalendarManager.swift

@@ -23,6 +23,7 @@ final class BaseCalendarManager: CalendarManager, Injectable {
     private let queue = DispatchQueue(label: "BaseCalendarManager.queue", qos: .background)
     private var coreDataPublisher: AnyPublisher<Set<NSManagedObjectID>, Never>?
     private var subscriptions = Set<AnyCancellable>()
+    private var previousDeterminationId: NSManagedObjectID?
 
     private var glucoseFormatter: NumberFormatter {
         let formatter = NumberFormatter()
@@ -213,6 +214,11 @@ final class BaseCalendarManager: CalendarManager, Injectable {
         guard settingsManager.settings.useCalendar, let calendar = currentCalendar,
               let determinationId = await getLastDetermination() else { return }
 
+        // Ignore the update if the determinationId is the same as it was at last update
+        if determinationId == previousDeterminationId {
+            return
+        }
+
         let glucoseIds = await fetchGlucose()
 
         deleteAllEvents(in: calendar)
@@ -293,6 +299,8 @@ final class BaseCalendarManager: CalendarManager, Injectable {
 
             try eventStore.save(event, span: .thisEvent)
 
+            previousDeterminationId = determinationId
+
         } catch {
             debugPrint(
                 "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to create calendar event: \(error.localizedDescription)"

+ 4 - 2
Trio/Sources/Services/ContactImage/ContactImageManager.swift

@@ -234,9 +234,11 @@ final class BaseContactImageManager: NSObject, ContactImageManager, Injectable {
         let hardCodedLow = Decimal(55)
         let hardCodedHigh = Decimal(220)
         let isDynamicColorScheme = settingsManager.settings.glucoseColorScheme == .dynamicColor
+        let highGlucoseColorValue = isDynamicColorScheme ? hardCodedHigh : settingsManager.settings.highGlucose
+        let lowGlucoseColorValue = isDynamicColorScheme ? hardCodedLow : settingsManager.settings.lowGlucose
 
-        state.highGlucoseColorValue = isDynamicColorScheme ? hardCodedHigh : settingsManager.settings.highGlucose
-        state.lowGlucoseColorValue = isDynamicColorScheme ? hardCodedLow : settingsManager.settings.lowGlucose
+        state.highGlucoseColorValue = units == .mgdL ? highGlucoseColorValue : highGlucoseColorValue.asMmolL
+        state.lowGlucoseColorValue = units == .mgdL ? lowGlucoseColorValue : lowGlucoseColorValue.asMmolL
         state
             .targetGlucose = await getCurrentGlucoseTarget() ??
             (settingsManager.settings.units == .mgdL ? Decimal(100) : 100.asMmolL)

+ 2 - 1
Trio/Sources/Services/LiveActivity/Data/DataManager.swift

@@ -33,7 +33,7 @@ extension LiveActivityBridge {
             key: "deliverAt",
             ascending: false,
             fetchLimit: 1,
-            propertiesToFetch: ["iob", "cob", "currentTarget", "deliverAt"]
+            propertiesToFetch: ["iob", "cob", "totalDailyDose", "currentTarget", "deliverAt"]
         )
 
         return await context.perform {
@@ -45,6 +45,7 @@ extension LiveActivityBridge {
                 DeterminationData(
                     cob: ($0["cob"] as? Int) ?? 0,
                     iob: ($0["iob"] as? NSDecimalNumber)?.decimalValue ?? 0,
+                    tdd: ($0["totalDailyDose"] as? NSDecimalNumber)?.decimalValue ?? 0,
                     target: ($0["currentTarget"] as? NSDecimalNumber)?.decimalValue ?? 0,
                     date: $0["deliverAt"] as? Date ?? nil
                 )

+ 1 - 0
Trio/Sources/Services/LiveActivity/Data/DeterminationData.swift

@@ -3,6 +3,7 @@ import Foundation
 struct DeterminationData {
     let cob: Int
     let iob: Decimal
+    let tdd: Decimal
     let target: Decimal
     let date: Date?
 }

+ 4 - 1
Trio/Sources/Services/LiveActivity/LiveActitiyAttributes.swift

@@ -3,13 +3,15 @@ import Foundation
 
 struct LiveActivityAttributes: ActivityAttributes {
     enum LiveActivityItem: String, Hashable, Codable, Equatable {
+        case currentGlucoseLarge
         case currentGlucose
         case iob
         case cob
         case updatedLabel
+        case totalDailyDose
         case empty
 
-        static let defaultItems: [Self] = [.currentGlucose, .iob, .cob, .updatedLabel]
+        static let defaultItems: [Self] = [.currentGlucoseLarge, .iob, .cob, .updatedLabel]
     }
 
     struct ContentState: Codable, Hashable {
@@ -34,6 +36,7 @@ struct LiveActivityAttributes: ActivityAttributes {
         let rotationDegrees: Double
         let cob: Decimal
         let iob: Decimal
+        let tdd: Decimal
         let isOverrideActive: Bool
         let overrideName: String
         let overrideDate: Date

+ 1 - 0
Trio/Sources/Services/LiveActivity/LiveActivityAttributes+Helper.swift

@@ -109,6 +109,7 @@ extension LiveActivityAttributes.ContentState {
                 rotationDegrees: rotationDegrees,
                 cob: Decimal(determination?.cob ?? 0),
                 iob: determination?.iob ?? 0 as Decimal,
+                tdd: determination?.tdd ?? 0 as Decimal,
                 isOverrideActive: override?.isActive ?? false,
                 overrideName: override?.overrideName ?? "Override",
                 overrideDate: override?.date ?? Date(),