Kaynağa Gözat

feat: Add Display Glucose Forecasts toggle to Live Activity settings

Adds a new on/off setting under Settings > Notifications > Live Activity,
styled identically to "Enable Live Activity". Default is OFF.

When enabled, the lock screen live activity chart shows the glucose
forecast from the Oref algorithm (cone or lines, controlled by the
existing Forecast Display Type setting under Features > User Interface).
When disabled, all forecast arrays are sent empty so no data is
included in the content state payload.

Changes:
- TrioSettings: add displayGlucoseForecasts Bool property (default OFF)
  with custom decoder entry alongside other Live Activity settings
- LiveActivitySettingsStateModel: add @Published property and
  subscribeSetting binding
- LiveActivitySettingsRootView: add SettingInputSection with miniHint,
  verboseHint, and hint sheet following the existing pattern; placed
  first inside the if-useLiveActivity block
- LiveActivityAttributes+Helper: gate all forecast arrays on
  settings.displayGlucoseForecasts
- SettingsExportStateModel: add export entry under Live Activity subcategory
Magnus Reintz 1 ay önce
ebeveyn
işleme
b1b5a5f281

+ 5 - 0
Trio/Sources/Models/TrioSettings.swift

@@ -66,6 +66,7 @@ struct TrioSettings: JSON, Equatable, Encodable {
     var useLiveActivity: Bool = false
     var useLiveActivity: Bool = false
     var lockScreenView: LockScreenView = .simple
     var lockScreenView: LockScreenView = .simple
     var smartStackView: LockScreenView = .simple
     var smartStackView: LockScreenView = .simple
+    var displayGlucoseForecasts: Bool = false
     var bolusShortcut: BolusShortcutLimit = .notAllowed
     var bolusShortcut: BolusShortcutLimit = .notAllowed
     var timeInRangeType: TimeInRangeType = .timeInTightRange
     var timeInRangeType: TimeInRangeType = .timeInTightRange
     var requireAdjustmentsConfirmation: Bool = false
     var requireAdjustmentsConfirmation: Bool = false
@@ -311,6 +312,10 @@ extension TrioSettings: Decodable {
             settings.smartStackView = smartStackView
             settings.smartStackView = smartStackView
         }
         }
 
 
+        if let displayGlucoseForecasts = try? container.decode(Bool.self, forKey: .displayGlucoseForecasts) {
+            settings.displayGlucoseForecasts = displayGlucoseForecasts
+        }
+
         if let bolusShortcut = try? container.decode(BolusShortcutLimit.self, forKey: .bolusShortcut) {
         if let bolusShortcut = try? container.decode(BolusShortcutLimit.self, forKey: .bolusShortcut) {
             settings.bolusShortcut = bolusShortcut
             settings.bolusShortcut = bolusShortcut
         }
         }

+ 2 - 0
Trio/Sources/Modules/LiveActivitySettings/LiveActivitySettingsStateModel.swift

@@ -9,11 +9,13 @@ extension LiveActivitySettings {
         @Published var useLiveActivity = false
         @Published var useLiveActivity = false
         @Published var lockScreenView: LockScreenView = .simple
         @Published var lockScreenView: LockScreenView = .simple
         @Published var smartStackView: LockScreenView = .simple
         @Published var smartStackView: LockScreenView = .simple
+        @Published var displayGlucoseForecasts = false
         override func subscribe() {
         override func subscribe() {
             units = settingsManager.settings.units
             units = settingsManager.settings.units
             subscribeSetting(\.useLiveActivity, on: $useLiveActivity) { useLiveActivity = $0 }
             subscribeSetting(\.useLiveActivity, on: $useLiveActivity) { useLiveActivity = $0 }
             subscribeSetting(\.lockScreenView, on: $lockScreenView) { lockScreenView = $0 }
             subscribeSetting(\.lockScreenView, on: $lockScreenView) { lockScreenView = $0 }
             subscribeSetting(\.smartStackView, on: $smartStackView) { smartStackView = $0 }
             subscribeSetting(\.smartStackView, on: $smartStackView) { smartStackView = $0 }
+            subscribeSetting(\.displayGlucoseForecasts, on: $displayGlucoseForecasts) { displayGlucoseForecasts = $0 }
         }
         }
     }
     }
 }
 }

+ 34 - 0
Trio/Sources/Modules/LiveActivitySettings/View/LiveActivitySettingsRootView.swift

@@ -9,6 +9,7 @@ extension LiveActivitySettings {
 
 
         @State private var shouldDisplayHintLockScreen: Bool = false
         @State private var shouldDisplayHintLockScreen: Bool = false
         @State private var shouldDisplayHintSmartStack: Bool = false
         @State private var shouldDisplayHintSmartStack: Bool = false
+        @State private var shouldDisplayHintGlucoseForecasts: Bool = false
         @State var hintDetent = PresentationDetent.large
         @State var hintDetent = PresentationDetent.large
         @State var selectedVerboseHint: AnyView?
         @State var selectedVerboseHint: AnyView?
         @State var hintLabel: String?
         @State var hintLabel: String?
@@ -83,6 +84,30 @@ extension LiveActivitySettings {
                     )
                     )
 
 
                     if state.useLiveActivity {
                     if state.useLiveActivity {
+                        SettingInputSection(
+                            decimalValue: $decimalPlaceholder,
+                            booleanValue: $state.displayGlucoseForecasts,
+                            shouldDisplayHint: $shouldDisplayHintGlucoseForecasts,
+                            selectedVerboseHint: Binding(
+                                get: { selectedVerboseHint },
+                                set: {
+                                    selectedVerboseHint = $0.map { AnyView($0) }
+                                    hintLabel = String(localized: "Display Glucose Forecasts")
+                                }
+                            ),
+                            units: state.units,
+                            type: .boolean,
+                            label: String(localized: "Display Glucose Forecasts"),
+                            miniHint: String(localized: "Display Glucose Forecasts made by the Oref algorithm."),
+                            verboseHint: VStack(alignment: .leading, spacing: 10) {
+                                Text("Default: OFF").bold()
+                                Text(
+                                    "When enabled, the live activity widget on the lock screen will show glucose forecasts. The visual representation will be the same as in the Main view. To change between Cone and Lines, change the setting under Features - User Interface - Forecast Display Type."
+                                )
+                            },
+                            headerText: String(localized: "Display Glucose Forecasts")
+                        )
+
                         Section {
                         Section {
                             VStack {
                             VStack {
                                 Picker(
                                 Picker(
@@ -229,6 +254,15 @@ extension LiveActivitySettings {
                     sheetTitle: String(localized: "Help", comment: "Help sheet title")
                     sheetTitle: String(localized: "Help", comment: "Help sheet title")
                 )
                 )
             }
             }
+            .sheet(isPresented: $shouldDisplayHintGlucoseForecasts) {
+                SettingInputHintView(
+                    hintDetent: $hintDetent,
+                    shouldDisplayHint: $shouldDisplayHintGlucoseForecasts,
+                    hintLabel: hintLabel ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
+                    sheetTitle: String(localized: "Help", comment: "Help sheet title")
+                )
+            }
             .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Live Activity")
             .navigationTitle("Live Activity")

+ 6 - 0
Trio/Sources/Modules/SettingsExport/SettingsExportStateModel.swift

@@ -871,6 +871,12 @@ extension SettingsExport {
                     name: String(localized: "Lock Screen Widget Style"),
                     name: String(localized: "Lock Screen Widget Style"),
                     value: trioSettings.lockScreenView.rawValue
                     value: trioSettings.lockScreenView.rawValue
                 )
                 )
+                addSetting(
+                    category: notificationsCategory,
+                    subcategory: liveActivitySubcategory,
+                    name: String(localized: "Display Glucose Forecasts"),
+                    value: trioSettings.displayGlucoseForecasts ? String(localized: "Enabled") : String(localized: "Disabled")
+                )
             }
             }
 
 
             // Services
             // Services

+ 5 - 3
Trio/Sources/Services/LiveActivity/LiveActivityAttributes+Helper.swift

@@ -119,9 +119,11 @@ extension LiveActivityAttributes.ContentState {
             tempTargetDuration: tempTarget?.duration ?? 0,
             tempTargetDuration: tempTarget?.duration ?? 0,
             tempTargetTarget: tempTarget?.target ?? 0,
             tempTargetTarget: tempTarget?.target ?? 0,
             widgetItems: widgetItems ?? [], // set empty array here to silence compiler; this can never be nil
             widgetItems: widgetItems ?? [], // set empty array here to silence compiler; this can never be nil
-            minForecast: settings.forecastDisplayType == .cone ? (determination?.minForecast ?? []) : [],
-            maxForecast: settings.forecastDisplayType == .cone ? (determination?.maxForecast ?? []) : [],
-            forecastLines: settings.forecastDisplayType == .lines
+            minForecast: settings.displayGlucoseForecasts && settings.forecastDisplayType == .cone
+                ? (determination?.minForecast ?? []) : [],
+            maxForecast: settings.displayGlucoseForecasts && settings.forecastDisplayType == .cone
+                ? (determination?.maxForecast ?? []) : [],
+            forecastLines: settings.displayGlucoseForecasts && settings.forecastDisplayType == .lines
                 ? (determination?.forecastLines ?? [])
                 ? (determination?.forecastLines ?? [])
                 .map { LiveActivityAttributes.ForecastLine(type: $0.type, values: $0.values) }
                 .map { LiveActivityAttributes.ForecastLine(type: $0.type, values: $0.values) }
                 : [],
                 : [],